-
Notifications
You must be signed in to change notification settings - Fork 5
/
wire.go
249 lines (210 loc) · 6.56 KB
/
wire.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package jmap
import (
"encoding/json"
"errors"
"fmt"
"io"
)
type UnknownMethodError struct {
MethodName string
}
func (ume UnknownMethodError) Error() string {
return fmt.Sprintf("jmap: unknown method name: %s", ume.MethodName)
}
type Invocation struct {
Name string
CallID string
Args interface{}
}
type Request struct {
// The set of capabilities the client wishes to use. The client may include
// capability identifiers even if the method calls it makes do not utilise
// those capabilities.
Using []string `json:"using"`
// An array of method calls to process on the server. The method calls will
// be processed sequentially, in order.
Calls []Invocation `json:"-"`
// A map of (client-specified) creation id to the id the server assigned
// when a record was successfully created. Can be nil.
CreatedIDs map[ID]ID `json:"createdIds,omitempty"`
}
// The request type is defined to trick encoding/json to not call
// Request.MarshalJSON when marshalling rawRequest.
// This allows us to use struct tags and custom marshalling logic for Calls at
// the same time.
type request Request
type rawInvocation struct {
Name string
CallID string
Args json.RawMessage
}
// The rawRequest is an intermediate result of request JSON deserialization.
type rawRequest struct {
request
RawCalls []rawInvocation `json:"methodCalls"`
}
func (r Request) MarshalJSON() ([]byte, error) {
raw := rawRequest{}
raw.Using = r.Using
raw.Calls = r.Calls
raw.CreatedIDs = r.CreatedIDs
raw.RawCalls = make([]rawInvocation, 0, len(r.Calls))
for _, call := range r.Calls {
argsBlob, err := json.Marshal(call.Args)
if err != nil {
return nil, err
}
raw.RawCalls = append(raw.RawCalls, rawInvocation{
Name: call.Name,
CallID: call.CallID,
Args: argsBlob,
})
}
return json.Marshal(raw)
}
// RawUnmarshallers creates FuncArgsUnmarshal mapping functions that return json.RawMessage.
//
// It allows to disable JSON decoding for Invocation arguments.
func RawUnmarshallers(methodNames []string) map[string]FuncArgsUnmarshal {
res := map[string]FuncArgsUnmarshal{}
for _, name := range methodNames {
res[name] = func(args json.RawMessage) (interface{}, error) { return args, nil }
}
return res
}
// Unmarshal deserializes Request object from JSON, calling functions from
// invocationCtors to deserialize Invocation objects. Key is method name.
//
// If error is returned, Request object is not changed.
func (r *Request) Unmarshal(data io.Reader, argsUnmarshallers map[string]FuncArgsUnmarshal) error {
raw := rawRequest{}
if err := json.NewDecoder(data).Decode(&raw); err != nil {
return err
}
raw.Calls = make([]Invocation, 0, len(raw.RawCalls))
for _, rawCall := range raw.RawCalls {
unmarshal, ok := argsUnmarshallers[rawCall.Name]
if !ok {
return UnknownMethodError{MethodName: rawCall.Name}
}
args, err := unmarshal(rawCall.Args)
if err != nil {
return err
}
raw.Calls = append(raw.Calls, Invocation{
Name: rawCall.Name,
CallID: rawCall.CallID,
Args: args,
})
}
// We will not change r if something goes wrong.
r.Using = raw.Using
r.Calls = raw.Calls
r.CreatedIDs = raw.CreatedIDs
return nil
}
type Response struct {
// An array of responses, in the same format as the Calls on the
// Request object. The output of the methods will be added to the
// methodResponses array in the same order as the methods are processed.
Responses []Invocation `json:"-"`
// A map of (client-specified) creation id to the id the server assigned
// when a record was successfully created.
CreatedIDs map[ID]ID `json:"createdIds,omitempty"`
// The current value of the “state” string on the JMAP Session object, as
// described in section 2. Clients may use this to detect if this object
// has changed and needs to be refetched.
SessionState string `json:"sessionState"`
}
type response Response
type rawResponse struct {
response
RawResponses []rawInvocation `json:"methodResponses"`
}
func (r Response) MarshalJSON() ([]byte, error) {
raw := rawResponse{response: response(r)}
raw.RawResponses = make([]rawInvocation, 0, len(r.Responses))
for _, response := range r.Responses {
argsBlob, err := json.Marshal(response.Args)
if err != nil {
return nil, err
}
raw.RawResponses = append(raw.RawResponses, rawInvocation{
Name: response.Name,
CallID: response.CallID,
Args: argsBlob,
})
}
return json.Marshal(raw)
}
// Unmarshal deserializes Response object from JSON, calling functions from
// invocationCtors to deserialize Invocation objects. Key is method name.
// Callback for error decoding is added to invocationCtors implicitly.
//
// If error is returned, Response object is not changed.
func (r *Response) Unmarshal(data io.Reader, argsUnmarshallers map[string]FuncArgsUnmarshal) error {
raw := rawResponse{}
if err := json.NewDecoder(data).Decode(&raw); err != nil {
return err
}
raw.Responses = make([]Invocation, 0, len(raw.RawResponses))
for _, rawResp := range raw.RawResponses {
var unmarshal FuncArgsUnmarshal
if rawResp.Name != "error" {
var ok bool
unmarshal, ok = argsUnmarshallers[rawResp.Name]
if !ok {
return UnknownMethodError{MethodName: rawResp.Name}
}
} else {
unmarshal = UnmarshalMethodErrorArgs
}
args, err := unmarshal(rawResp.Args)
if err != nil {
return err
}
raw.Responses = append(raw.Responses, Invocation{
Name: rawResp.Name,
CallID: rawResp.CallID,
Args: args,
})
}
// We will not change r if something goes wrong.
r.CreatedIDs = raw.CreatedIDs
r.Responses = raw.Responses
r.SessionState = raw.SessionState
return nil
}
func (i *rawInvocation) UnmarshalJSON(data []byte) error {
var methodName, callId string
var args json.RawMessage
// Slice so we can detect invalid size.
triplet := make([]json.RawMessage, 0, 3)
if err := json.Unmarshal(data, &triplet); err != nil {
return err
}
if len(triplet) != 3 {
return errors.New("jmap: malformed Invocation object, need exactly 3 elements")
}
if err := json.Unmarshal(triplet[0], &methodName); err != nil {
return err
}
if err := json.Unmarshal(triplet[2], &callId); err != nil {
return err
}
args = triplet[1]
if args[0] != '{' {
return errors.New("jmap: malformed Invocation object, arguments must be object")
}
i.Name = methodName
i.CallID = callId
i.Args = args
return nil
}
func (i rawInvocation) MarshalJSON() ([]byte, error) {
if i.Args[0] != '{' {
return nil, errors.New("jmap: malformed Invocation object, arguments must be object")
}
return json.Marshal([3]interface{}{i.Name, i.Args, i.CallID})
}
type FuncArgsUnmarshal func(args json.RawMessage) (interface{}, error)