Skip to content

Commit f10a25b

Browse files
committed
chore: add unknown usage event type error
1 parent 0d467bb commit f10a25b

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

coderd/usage/usagetypes/events.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ package usagetypes
1313
import (
1414
"bytes"
1515
"encoding/json"
16+
"fmt"
1617
"strings"
1718

1819
"golang.org/x/xerrors"
@@ -22,6 +23,10 @@ import (
2223
// type `usage_event_type`.
2324
type UsageEventType string
2425

26+
// All event types.
27+
//
28+
// When adding a new event type, ensure you add it to the Valid method and the
29+
// ParseEventWithType function.
2530
const (
2631
UsageEventTypeDCManagedAgentsV1 UsageEventType = "dc_managed_agents_v1"
2732
)
@@ -43,38 +48,56 @@ func (e UsageEventType) IsHeartbeat() bool {
4348
return e.Valid() && strings.HasPrefix(string(e), "hb_")
4449
}
4550

46-
// ParseEvent parses the raw event data into the specified Go type. It fails if
47-
// there is any unknown fields or extra data after the event. The returned event
48-
// is validated.
49-
func ParseEvent[T Event](data json.RawMessage) (T, error) {
51+
// ParseEvent parses the raw event data into the provided event. It fails if
52+
// there is any unknown fields or extra data at the end of the JSON. The
53+
// returned event is validated.
54+
func ParseEvent(data json.RawMessage, out Event) error {
5055
dec := json.NewDecoder(bytes.NewReader(data))
5156
dec.DisallowUnknownFields()
5257

53-
var event T
54-
err := dec.Decode(&event)
58+
err := dec.Decode(out)
5559
if err != nil {
56-
return event, xerrors.Errorf("unmarshal %T event: %w", event, err)
60+
return xerrors.Errorf("unmarshal %T event: %w", out, err)
5761
}
5862
if dec.More() {
59-
return event, xerrors.Errorf("extra data after %T event", event)
63+
return xerrors.Errorf("extra data after %T event", out)
6064
}
61-
err = event.Valid()
65+
err = out.Valid()
6266
if err != nil {
63-
return event, xerrors.Errorf("invalid %T event: %w", event, err)
67+
return xerrors.Errorf("invalid %T event: %w", out, err)
6468
}
6569

66-
return event, nil
70+
return nil
71+
}
72+
73+
// UnknownEventTypeError is returned by ParseEventWithType when an unknown event
74+
// type is encountered.
75+
type UnknownEventTypeError struct {
76+
EventType string
77+
}
78+
79+
var _ error = UnknownEventTypeError{}
80+
81+
// Error implements error.
82+
func (e UnknownEventTypeError) Error() string {
83+
return fmt.Sprintf("unknown usage event type: %q", e.EventType)
6784
}
6885

6986
// ParseEventWithType parses the raw event data into the specified Go type. It
7087
// fails if there is any unknown fields or extra data after the event. The
7188
// returned event is validated.
89+
//
90+
// If the event type is unknown, UnknownEventTypeError is returned.
7291
func ParseEventWithType(eventType UsageEventType, data json.RawMessage) (Event, error) {
7392
switch eventType {
7493
case UsageEventTypeDCManagedAgentsV1:
75-
return ParseEvent[DCManagedAgentsV1](data)
94+
var event DCManagedAgentsV1
95+
if err := ParseEvent(data, &event); err != nil {
96+
return nil, err
97+
}
98+
return event, nil
7699
default:
77-
return nil, xerrors.Errorf("unknown event type: %s", eventType)
100+
return nil, UnknownEventTypeError{EventType: string(eventType)}
78101
}
79102
}
80103

coderd/usage/usagetypes/events_test.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,34 @@ func TestParseEvent(t *testing.T) {
1313

1414
t.Run("ExtraFields", func(t *testing.T) {
1515
t.Parallel()
16-
_, err := usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1, "extra": "field"}`))
17-
require.ErrorContains(t, err, "unmarshal usagetypes.DCManagedAgentsV1 event")
16+
var event usagetypes.DCManagedAgentsV1
17+
err := usagetypes.ParseEvent([]byte(`{"count": 1, "extra": "field"}`), &event)
18+
require.ErrorContains(t, err, "unmarshal *usagetypes.DCManagedAgentsV1 event")
1819
})
1920

2021
t.Run("ExtraData", func(t *testing.T) {
2122
t.Parallel()
22-
_, err := usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1}{"count": 2}`))
23-
require.ErrorContains(t, err, "extra data after usagetypes.DCManagedAgentsV1 event")
23+
var event usagetypes.DCManagedAgentsV1
24+
err := usagetypes.ParseEvent([]byte(`{"count": 1}{"count": 2}`), &event)
25+
require.ErrorContains(t, err, "extra data after *usagetypes.DCManagedAgentsV1 event")
2426
})
2527

2628
t.Run("DCManagedAgentsV1", func(t *testing.T) {
2729
t.Parallel()
2830

29-
event, err := usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": 1}`))
31+
var event usagetypes.DCManagedAgentsV1
32+
err := usagetypes.ParseEvent([]byte(`{"count": 1}`), &event)
3033
require.NoError(t, err)
3134
require.Equal(t, usagetypes.DCManagedAgentsV1{Count: 1}, event)
3235
require.Equal(t, map[string]any{"count": uint64(1)}, event.Fields())
3336

34-
_, err = usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{"count": "invalid"}`))
35-
require.ErrorContains(t, err, "unmarshal usagetypes.DCManagedAgentsV1 event")
37+
event = usagetypes.DCManagedAgentsV1{}
38+
err = usagetypes.ParseEvent([]byte(`{"count": "invalid"}`), &event)
39+
require.ErrorContains(t, err, "unmarshal *usagetypes.DCManagedAgentsV1 event")
3640

37-
_, err = usagetypes.ParseEvent[usagetypes.DCManagedAgentsV1]([]byte(`{}`))
38-
require.ErrorContains(t, err, "invalid usagetypes.DCManagedAgentsV1 event: count must be greater than 0")
41+
event = usagetypes.DCManagedAgentsV1{}
42+
err = usagetypes.ParseEvent([]byte(`{}`), &event)
43+
require.ErrorContains(t, err, "invalid *usagetypes.DCManagedAgentsV1 event: count must be greater than 0")
3944
})
4045
}
4146

@@ -45,7 +50,9 @@ func TestParseEventWithType(t *testing.T) {
4550
t.Run("UnknownEvent", func(t *testing.T) {
4651
t.Parallel()
4752
_, err := usagetypes.ParseEventWithType(usagetypes.UsageEventType("fake"), []byte(`{}`))
48-
require.ErrorContains(t, err, "unknown event type: fake")
53+
var unknownEventTypeError usagetypes.UnknownEventTypeError
54+
require.ErrorAs(t, err, &unknownEventTypeError)
55+
require.Equal(t, "fake", unknownEventTypeError.EventType)
4956
})
5057

5158
t.Run("DCManagedAgentsV1", func(t *testing.T) {

enterprise/coderd/usage/publisher.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/xerrors"
1515

1616
"cdr.dev/slog"
17+
"github.com/coder/coder/v2/buildinfo"
1718
"github.com/coder/coder/v2/coderd/database"
1819
"github.com/coder/coder/v2/coderd/database/dbauthz"
1920
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -396,6 +397,7 @@ func (p *tallymanPublisher) sendPublishRequest(ctx context.Context, deploymentID
396397
if err != nil {
397398
return usagetypes.TallymanV1IngestResponse{}, err
398399
}
400+
r.Header.Set("User-Agent", "coderd/"+buildinfo.Version())
399401
r.Header.Set(usagetypes.TallymanCoderLicenseKeyHeader, licenseJwt)
400402
r.Header.Set(usagetypes.TallymanCoderDeploymentIDHeader, deploymentID.String())
401403

0 commit comments

Comments
 (0)