| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package jsonrpc2 |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| ) |
| |
| // Message is the interface to all jsonrpc2 message types. |
| // They share no common functionality, but are a closed set of concrete types |
| // that are allowed to implement this interface. The message types are *Call, |
| // *Notification and *Response. |
| type Message interface { |
| // isJSONRPC2Message is used to make the set of message implementations a |
| // closed set. |
| isJSONRPC2Message() |
| } |
| |
| // Request is the shared interface to jsonrpc2 messages that request |
| // a method be invoked. |
| // The request types are a closed set of *Call and *Notification. |
| type Request interface { |
| Message |
| // Method is a string containing the method name to invoke. |
| Method() string |
| // Params is an JSON value (object, array, null, or "") with the parameters of the method. |
| Params() json.RawMessage |
| // isJSONRPC2Request is used to make the set of request implementations closed. |
| isJSONRPC2Request() |
| } |
| |
| // Notification is a request for which a response cannot occur, and as such |
| // it has not ID. |
| type Notification struct { |
| // Method is a string containing the method name to invoke. |
| method string |
| params json.RawMessage |
| } |
| |
| // Call is a request that expects a response. |
| // The response will have a matching ID. |
| type Call struct { |
| // Method is a string containing the method name to invoke. |
| method string |
| // Params is a JSON value (object, array, null, or "") with the parameters of the method. |
| params json.RawMessage |
| // id of this request, used to tie the Response back to the request. |
| id ID |
| } |
| |
| // Response is a reply to a Call. |
| // It will have the same ID as the call it is a response to. |
| type Response struct { |
| // result is the content of the response. |
| result json.RawMessage |
| // err is set only if the call failed. |
| err error |
| // ID of the request this is a response to. |
| id ID |
| } |
| |
| // NewNotification constructs a new Notification message for the supplied |
| // method and parameters. |
| func NewNotification(method string, params interface{}) (*Notification, error) { |
| p, merr := marshalToRaw(params) |
| return &Notification{method: method, params: p}, merr |
| } |
| |
| func (msg *Notification) Method() string { return msg.method } |
| func (msg *Notification) Params() json.RawMessage { return msg.params } |
| func (msg *Notification) isJSONRPC2Message() {} |
| func (msg *Notification) isJSONRPC2Request() {} |
| |
| func (n *Notification) MarshalJSON() ([]byte, error) { |
| msg := wireRequest{Method: n.method, Params: &n.params} |
| data, err := json.Marshal(msg) |
| if err != nil { |
| return data, fmt.Errorf("marshaling notification: %w", err) |
| } |
| return data, nil |
| } |
| |
| func (n *Notification) UnmarshalJSON(data []byte) error { |
| msg := wireRequest{} |
| if err := json.Unmarshal(data, &msg); err != nil { |
| return fmt.Errorf("unmarshaling notification: %w", err) |
| } |
| n.method = msg.Method |
| if msg.Params != nil { |
| n.params = *msg.Params |
| } |
| return nil |
| } |
| |
| // NewCall constructs a new Call message for the supplied ID, method and |
| // parameters. |
| func NewCall(id ID, method string, params interface{}) (*Call, error) { |
| p, merr := marshalToRaw(params) |
| return &Call{id: id, method: method, params: p}, merr |
| } |
| |
| func (msg *Call) Method() string { return msg.method } |
| func (msg *Call) Params() json.RawMessage { return msg.params } |
| func (msg *Call) ID() ID { return msg.id } |
| func (msg *Call) isJSONRPC2Message() {} |
| func (msg *Call) isJSONRPC2Request() {} |
| |
| func (c *Call) MarshalJSON() ([]byte, error) { |
| msg := wireRequest{Method: c.method, Params: &c.params, ID: &c.id} |
| data, err := json.Marshal(msg) |
| if err != nil { |
| return data, fmt.Errorf("marshaling call: %w", err) |
| } |
| return data, nil |
| } |
| |
| func (c *Call) UnmarshalJSON(data []byte) error { |
| msg := wireRequest{} |
| if err := json.Unmarshal(data, &msg); err != nil { |
| return fmt.Errorf("unmarshaling call: %w", err) |
| } |
| c.method = msg.Method |
| if msg.Params != nil { |
| c.params = *msg.Params |
| } |
| if msg.ID != nil { |
| c.id = *msg.ID |
| } |
| return nil |
| } |
| |
| // NewResponse constructs a new Response message that is a reply to the |
| // supplied. If err is set result may be ignored. |
| func NewResponse(id ID, result interface{}, err error) (*Response, error) { |
| r, merr := marshalToRaw(result) |
| return &Response{id: id, result: r, err: err}, merr |
| } |
| |
| func (msg *Response) ID() ID { return msg.id } |
| func (msg *Response) Result() json.RawMessage { return msg.result } |
| func (msg *Response) Err() error { return msg.err } |
| func (msg *Response) isJSONRPC2Message() {} |
| |
| func (r *Response) MarshalJSON() ([]byte, error) { |
| msg := &wireResponse{Error: toWireError(r.err), ID: &r.id} |
| if msg.Error == nil { |
| msg.Result = &r.result |
| } |
| data, err := json.Marshal(msg) |
| if err != nil { |
| return data, fmt.Errorf("marshaling notification: %w", err) |
| } |
| return data, nil |
| } |
| |
| func toWireError(err error) *WireError { |
| if err == nil { |
| // no error, the response is complete |
| return nil |
| } |
| if err, ok := err.(*WireError); ok { |
| // already a wire error, just use it |
| return err |
| } |
| result := &WireError{Message: err.Error()} |
| var wrapped *WireError |
| if errors.As(err, &wrapped) { |
| // if we wrapped a wire error, keep the code from the wrapped error |
| // but the message from the outer error |
| result.Code = wrapped.Code |
| } |
| return result |
| } |
| |
| func (r *Response) UnmarshalJSON(data []byte) error { |
| msg := wireResponse{} |
| if err := json.Unmarshal(data, &msg); err != nil { |
| return fmt.Errorf("unmarshaling jsonrpc response: %w", err) |
| } |
| if msg.Result != nil { |
| r.result = *msg.Result |
| } |
| if msg.Error != nil { |
| r.err = msg.Error |
| } |
| if msg.ID != nil { |
| r.id = *msg.ID |
| } |
| return nil |
| } |
| |
| func DecodeMessage(data []byte) (Message, error) { |
| msg := wireCombined{} |
| if err := json.Unmarshal(data, &msg); err != nil { |
| return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) |
| } |
| if msg.Method == "" { |
| // no method, should be a response |
| if msg.ID == nil { |
| return nil, ErrInvalidRequest |
| } |
| response := &Response{id: *msg.ID} |
| if msg.Error != nil { |
| response.err = msg.Error |
| } |
| if msg.Result != nil { |
| response.result = *msg.Result |
| } |
| return response, nil |
| } |
| // has a method, must be a request |
| if msg.ID == nil { |
| // request with no ID is a notify |
| notify := &Notification{method: msg.Method} |
| if msg.Params != nil { |
| notify.params = *msg.Params |
| } |
| return notify, nil |
| } |
| // request with an ID, must be a call |
| call := &Call{method: msg.Method, id: *msg.ID} |
| if msg.Params != nil { |
| call.params = *msg.Params |
| } |
| return call, nil |
| } |
| |
| func marshalToRaw(obj interface{}) (json.RawMessage, error) { |
| data, err := json.Marshal(obj) |
| if err != nil { |
| return json.RawMessage{}, err |
| } |
| return json.RawMessage(data), nil |
| } |