Skip to content

Commit 04b10db

Browse files
authored
fix: Parse object recipients into new field rather than panicking (#32)
1 parent 8cf3a4a commit 04b10db

2 files changed

Lines changed: 158 additions & 18 deletions

File tree

knock/messages.go

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,96 @@ const (
6363
)
6464

6565
type Message struct {
66-
Cursor string `json:"__cursor"`
67-
ID string `json:"id"`
68-
ChannelID string `json:"channel_id"`
69-
Recipient string `json:"recipient"`
70-
Workflow string `json:"workflow"`
71-
Tenant string `json:"tenant"`
72-
Status EngagementStatus `json:"status"`
73-
ReadAt time.Time `json:"read_at"`
74-
SeenAt time.Time `json:"seen_at"`
75-
ArchivedAt time.Time `json:"archived_at"`
76-
InsertedAt time.Time `json:"inserted_at"`
77-
UpdatedAt time.Time `json:"updated_at"`
78-
Source *NotificationSource `json:"source"`
79-
Data map[string]interface{} `json:"data"`
66+
Cursor string
67+
ID string
68+
ChannelID string
69+
Workflow string
70+
Tenant string
71+
Status EngagementStatus
72+
ReadAt time.Time
73+
SeenAt time.Time
74+
ArchivedAt time.Time
75+
InsertedAt time.Time
76+
UpdatedAt time.Time
77+
Source *NotificationSource
78+
Data map[string]interface{}
79+
80+
// If the message recipient is an object, ObjectRecipient will be non-nil
81+
// and Recipient will be an empty string. Otherwise, ObjectRecipient will be nil
82+
// and the Recipient string will be non-empty.
83+
ObjectRecipient *ObjectRecipient
84+
Recipient string
85+
}
86+
87+
// The Knock Message json schema has a polymorphic type for the `recipient` field.
88+
// It can hold either a string value or a json object. In order to avoid an `interface{}`
89+
// type for Message.Recipient we use a custom unmarshaller.
90+
func (m *Message) UnmarshalJSON(b []byte) error {
91+
var msg struct {
92+
RawRecipient json.RawMessage `json:"recipient"`
93+
94+
Cursor string `json:"__cursor"`
95+
ID string `json:"id"`
96+
ChannelID string `json:"channel_id"`
97+
Workflow string `json:"workflow"`
98+
Tenant string `json:"tenant"`
99+
Status EngagementStatus `json:"status"`
100+
ReadAt time.Time `json:"read_at"`
101+
SeenAt time.Time `json:"seen_at"`
102+
ArchivedAt time.Time `json:"archived_at"`
103+
InsertedAt time.Time `json:"inserted_at"`
104+
UpdatedAt time.Time `json:"updated_at"`
105+
Source *NotificationSource `json:"source"`
106+
Data map[string]interface{} `json:"data"`
107+
}
108+
109+
err := json.Unmarshal(b, &msg)
110+
if err != nil {
111+
return err
112+
}
113+
114+
// these fields have known types
115+
m.Cursor = msg.Cursor
116+
m.ID = msg.ID
117+
m.ChannelID = msg.ChannelID
118+
m.Workflow = msg.Workflow
119+
m.Tenant = msg.Tenant
120+
m.Status = msg.Status
121+
m.ReadAt = msg.ReadAt
122+
m.SeenAt = msg.SeenAt
123+
m.ArchivedAt = msg.ArchivedAt
124+
m.InsertedAt = msg.InsertedAt
125+
m.UpdatedAt = msg.UpdatedAt
126+
m.Source = msg.Source
127+
m.Data = msg.Data
128+
129+
// first, attempt to parse the `recipient` field value into a string
130+
var stringRecip string
131+
err = json.Unmarshal(msg.RawRecipient, &stringRecip)
132+
if err != nil {
133+
if _, ok := err.(*json.UnmarshalTypeError); ok {
134+
// if we can't parse into a string, attempt to parse into an object
135+
var objectRecip ObjectRecipient
136+
err = json.Unmarshal(msg.RawRecipient, &objectRecip)
137+
if err != nil {
138+
return err
139+
}
140+
141+
// if we succesfully parse `recipient` into an object, set Message.ObjectRecipient
142+
m.ObjectRecipient = &objectRecip
143+
return nil
144+
}
145+
return err
146+
}
147+
148+
// if we succesfully parse `recipient` into a string, set Message.Recipient
149+
m.Recipient = stringRecip
150+
return nil
151+
}
152+
153+
type ObjectRecipient struct {
154+
Id string `json:"id"`
155+
Collection string `json:"collection"`
80156
}
81157

82158
type NotificationSource struct {

knock/messages_test.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,74 @@ func TestMessages_List(t *testing.T) {
3737
wantMessages :=
3838
[]*Message{
3939
{
40-
Cursor: "big-cursor",
41-
ID: "message-id",
42-
ChannelID: "5da042d7-02ee-46ed-8b91-9b5717da2028",
43-
Recipient: "tom",
40+
Cursor: "big-cursor",
41+
ID: "message-id",
42+
ChannelID: "5da042d7-02ee-46ed-8b91-9b5717da2028",
43+
Recipient: "tom",
44+
ObjectRecipient: nil,
45+
Workflow: "test",
46+
Tenant: "",
47+
Status: "delivered",
48+
ReadAt: time.Time{},
49+
SeenAt: time.Time{},
50+
ArchivedAt: time.Time{},
51+
InsertedAt: time.Date(2022, time.May, 17, 00, 34, 18, 277163000, time.UTC),
52+
UpdatedAt: time.Date(2022, time.May, 17, 00, 34, 18, 318283000, time.UTC),
53+
Source: &NotificationSource{
54+
Key: "test",
55+
VersionID: "4dae021a-ba51-473f-9038-77041da8131c",
56+
},
57+
Data: map[string]interface{}{
58+
"middle-name": "alfred",
59+
"welcome": "to jurassic park",
60+
},
61+
},
62+
}
63+
64+
wantPageInfo := &PageInfo{
65+
PageSize: 1,
66+
After: "big-after",
67+
}
68+
69+
c.Assert(err, qt.IsNil)
70+
c.Assert(haveMessages, qt.DeepEquals, wantMessages)
71+
c.Assert(havePageInfo, qt.DeepEquals, wantPageInfo)
72+
}
73+
74+
func TestMessages_List_object_recipients(t *testing.T) {
75+
c := qt.New(t)
76+
77+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78+
w.WriteHeader(200)
79+
out := `{"items":[{"__cursor":"big-cursor","__typename":"Message","archived_at":null,"channel_id":"5da042d7-02ee-46ed-8b91-9b5717da2028","data":{"middle-name":"alfred","welcome":"to jurassic park"},"id":"message-id","inserted_at":"2022-05-17T00:34:18.277163Z","read_at":null,"recipient":{"collection": "communities", "id": "community1"},"seen_at":null,"source":{"__typename":"NotificationSource","key":"test","version_id":"4dae021a-ba51-473f-9038-77041da8131c"},"status":"delivered","tenant":null,"updated_at":"2022-05-17T00:34:18.318283Z","workflow":"test"}],"page_info":{"__typename":"PageInfo","after":"big-after","before":null,"page_size":1}}`
80+
_, err := w.Write([]byte(out))
81+
c.Assert(err, qt.IsNil)
82+
}))
83+
84+
client, err := NewClient(WithBaseURL(ts.URL))
85+
c.Assert(err, qt.IsNil)
86+
87+
ctx := context.Background()
88+
89+
haveMessages, havePageInfo, err := client.Messages.List(ctx, &ListMessagesRequest{
90+
PageSize: 1,
91+
})
92+
93+
if err != nil {
94+
fmt.Println(err)
95+
}
96+
97+
wantMessages :=
98+
[]*Message{
99+
{
100+
Cursor: "big-cursor",
101+
ID: "message-id",
102+
ChannelID: "5da042d7-02ee-46ed-8b91-9b5717da2028",
103+
Recipient: "",
104+
ObjectRecipient: &ObjectRecipient{
105+
Collection: "communities",
106+
Id: "community1",
107+
},
44108
Workflow: "test",
45109
Tenant: "",
46110
Status: "delivered",

0 commit comments

Comments
 (0)