1 // Copyright 2018 Adam S Levy
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 // IN THE SOFTWARE.
20 21 package jsonrpc2
22 23 import (
24 "bytes"
25 "encoding/json"
26 "fmt"
27 )
28 29 // version is the valid version string for the "jsonrpc" field required in all
30 // JSON RPC 2.0 objects.
31 const version = "2.0"
32 33 // Response represents a JSON-RPC 2.0 Response object.
34 //
35 // This type is not needed to use the Client or to write MethodFuncs for the
36 // HTTPRequestHandler.
37 //
38 // Response is intended to be used externally to UnmarshalJSON for custom
39 // clients, and internally to MarshalJSON for the provided HTTPRequestHandler.
40 //
41 // To receive a Response, it is recommended to set Result to a pointer to a
42 // value that the "result" can be unmarshaled into, if known prior to
43 // unmarshaling. Similarly, it is recommended to set ID to a pointer to a value
44 // that the "id" can be unmarshaled into, which should be the same type as the
45 // Request ID.
46 type Response struct {
47 // Result is REQUIRED on success. This member MUST NOT exist if there
48 // was an error invoking the method. The value of this member is
49 // determined by the method invoked on the Server.
50 Result interface{} `json:"result,omitempty"`
51 52 // Error is REQUIRED on error. This member MUST NOT exist if there was
53 // no error triggered during invocation. The value for this member MUST
54 // be an Object as defined in section 5.1.
55 //
56 // See Response.HasError.
57 Error Error `json:"error,omitempty"`
58 59 // ID is an identifier established by the client that MUST contain a
60 // String, Number, or NULL value if included.
61 //
62 // Since a Request without an ID is not responded to, this member is
63 // REQUIRED. It MUST be the same as the value of the id member in the
64 // Request Object. If there was an error in detecting the id in the
65 // Request Object (e.g. Parse error/Invalid Request), it MUST be Null.
66 ID interface{} `json:"id"`
67 }
68 69 // jResponse adds the required "jsonrpc" field and allows for detecting if the
70 // "error" field is present so that the rule that a Response may not contain
71 // both an "error and "result" can be enforced.
72 type jResponse struct {
73 // JSONRPC specifies the version of the JSON-RPC protocol. It MUST be
74 // exactly "2.0".
75 JSONRPC string `json:"jsonrpc"`
76 77 // Error allows UnmarshalJSON to detect if "error" was omitted or set
78 // to null which is invalid if "result" is included. Additionally it
79 // allows MarshalJSON to explicitly omit it or include it during
80 // marhsaling.
81 Error *Error `json:"error,omitempty"`
82 83 // *request allows a Request to be used directly while masking its
84 // Un/MarshalJSON methods.
85 *response
86 }
87 88 // response masks the Response Un/MarshalJSON methods to avoid recursion.
89 type response Response
90 91 // MarshalJSON attempts to marshal r into a valid JSON-RPC 2.0 Response.
92 //
93 // If r.HasError(), then "result" is omitted from the JSON, and if r.ID is nil,
94 // it is set to json.RawMessage("null"). An error is only returned if
95 // r.Error.Data or r.ID is not marshalable.
96 //
97 // If !r.HasError(), then if r.ID is nil, an error is returned. If r.Result is
98 // nil, it is populated with json.RawMessage("null").
99 //
100 // Also, an error is returned if r.Result or r.ID is not marshalable.
101 func (r Response) MarshalJSON() ([]byte, error) {
102 jR := jResponse{
103 JSONRPC: version,
104 response: (*response)(&r),
105 }
106 if r.HasError() {
107 jR.Error = &r.Error
108 r.Result = nil
109 if r.ID == nil {
110 r.ID = json.RawMessage("null")
111 }
112 } else {
113 if r.ID == nil {
114 return nil, fmt.Errorf("r.ID == nil && !r.HasError()")
115 }
116 if r.Result == nil {
117 r.Result = json.RawMessage("null")
118 }
119 }
120 return json.Marshal(jR)
121 }
122 123 // UnmarshalJSON attempts to unmarshal a JSON-RPC 2.0 Response into r and then
124 // validates it.
125 //
126 // If any fields are unknown other than the application specific fields in the
127 // "result" object, an error is returned.
128 //
129 // If "error" and "result" are both present or not null, a `contains both ...`
130 // error is returned.
131 //
132 // If the "jsonrpc" field is not set to the string "2.0", an `invalid "jsonrpc"
133 // version: ...` error is returned.
134 func (r *Response) UnmarshalJSON(data []byte) error {
135 // There may be fields in the result not defined in the user provided
136 // r.Result, which will cause errors with the json.Decoder below. So
137 // first unmarshal any "result" to a json.RawMessage, which will later
138 // be unmarshaled into the userResult.
139 userResult := r.Result
140 var resultData json.RawMessage
141 r.Result = &resultData
142 jR := jResponse{Error: &r.Error, response: (*response)(r)}
143 144 // Catch any unknown fields in the top level JSON RPC Response object.
145 d := json.NewDecoder(bytes.NewBuffer(data))
146 d.DisallowUnknownFields()
147 if err := d.Decode(&jR); err != nil {
148 return err
149 }
150 151 if jR.JSONRPC != version {
152 return fmt.Errorf(`invalid "jsonrpc" version: %q`, jR.JSONRPC)
153 }
154 155 if r.HasError() {
156 if resultData != nil {
157 return fmt.Errorf(`contains both "result" and "error"`)
158 }
159 return nil
160 }
161 162 // Restore the userResult and finish unmarshaling.
163 r.Result = userResult
164 return json.Unmarshal(resultData, &r.Result)
165 }
166 167 // HasError returns true is r.Error has any non-zero values.
168 func (r Response) HasError() bool {
169 return !r.Error.IsZero()
170 }
171 172 // String returns r as a JSON object prefixed with "<-- " to indicate an
173 // incoming Response.
174 //
175 // If r.MarshalJSON returns an error then the error string is returned with
176 // some context.
177 func (r Response) String() string {
178 b, err := r.MarshalJSON()
179 if err != nil {
180 return fmt.Sprintf("%#v.MarshalJSON(): %v", r, err)
181 }
182 return "<-- " + string(b)
183 }
184 185 // BatchResponse is a type that implements fmt.Stringer for a slice of
186 // Responses.
187 type BatchResponse []Response
188 189 // String returns br as a JSON array prefixed with "<-- " to indicate an
190 // incoming BatchResponse and with newlines separating the elements of br.
191 func (br BatchResponse) String() string {
192 s := "<-- [\n"
193 for i, res := range br {
194 s += " " + res.String()[4:]
195 if i < len(br)-1 {
196 s += ","
197 }
198 s += "\n"
199 }
200 return s + "]"
201 }
202