| // 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 "golang.org/x/xerrors" |
| ) |
| |
| // ID is a Request identifier. |
| type ID struct { |
| value interface{} |
| } |
| |
| // 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 *Request |
| // and *Response. |
| type Message interface { |
| // marshal builds the wire form from the API form. |
| // It is private, which makes the set of Message implementations closed. |
| marshal(to *wireCombined) |
| } |
| |
| // Request is a Message sent to a peer to request behavior. |
| // If it has an ID it is a call, otherwise it is a notification. |
| type Request struct { |
| // ID of this request, used to tie the Response back to the request. |
| // This will be nil for notifications. |
| ID ID |
| // Method is a string containing the method name to invoke. |
| Method string |
| // Params is either a struct or an array with the parameters of the method. |
| Params json.RawMessage |
| } |
| |
| // Response is a Message used as a reply to a call Request. |
| // 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. |
| Error error |
| // id of the request this is a response to. |
| ID ID |
| } |
| |
| // StringID creates a new string request identifier. |
| func StringID(s string) ID { return ID{value: s} } |
| |
| // Int64ID creates a new integer request identifier. |
| func Int64ID(i int64) ID { return ID{value: i} } |
| |
| // IsValid returns true if the ID is a valid identifier. |
| // The default value for ID will return false. |
| func (id ID) IsValid() bool { return id.value != nil } |
| |
| // Raw returns the underlying value of the ID. |
| func (id ID) Raw() interface{} { return id.value } |
| |
| // NewNotification constructs a new Notification message for the supplied |
| // method and parameters. |
| func NewNotification(method string, params interface{}) (*Request, error) { |
| p, merr := marshalToRaw(params) |
| return &Request{Method: method, Params: p}, merr |
| } |
| |
| // NewCall constructs a new Call message for the supplied ID, method and |
| // parameters. |
| func NewCall(id ID, method string, params interface{}) (*Request, error) { |
| p, merr := marshalToRaw(params) |
| return &Request{ID: id, Method: method, Params: p}, merr |
| } |
| |
| func (msg *Request) IsCall() bool { return msg.ID.IsValid() } |
| |
| func (msg *Request) marshal(to *wireCombined) { |
| to.ID = msg.ID.value |
| to.Method = msg.Method |
| to.Params = msg.Params |
| } |
| |
| // 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{}, rerr error) (*Response, error) { |
| r, merr := marshalToRaw(result) |
| return &Response{ID: id, Result: r, Error: rerr}, merr |
| } |
| |
| func (msg *Response) marshal(to *wireCombined) { |
| to.ID = msg.ID.value |
| to.Error = toWireError(msg.Error) |
| to.Result = msg.Result |
| } |
| |
| 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 EncodeMessage(msg Message) ([]byte, error) { |
| wire := wireCombined{VersionTag: wireVersion} |
| msg.marshal(&wire) |
| data, err := json.Marshal(&wire) |
| if err != nil { |
| return data, errors.Errorf("marshaling jsonrpc message: %w", err) |
| } |
| return data, nil |
| } |
| |
| func DecodeMessage(data []byte) (Message, error) { |
| msg := wireCombined{} |
| if err := json.Unmarshal(data, &msg); err != nil { |
| return nil, errors.Errorf("unmarshaling jsonrpc message: %w", err) |
| } |
| if msg.VersionTag != wireVersion { |
| return nil, errors.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion) |
| } |
| id := ID{} |
| switch v := msg.ID.(type) { |
| case nil: |
| case float64: |
| // coerce the id type to int64 if it is float64, the spec does not allow fractional parts |
| id = Int64ID(int64(v)) |
| case int64: |
| id = Int64ID(v) |
| case string: |
| id = StringID(v) |
| default: |
| return nil, errors.Errorf("invalid message id type <%T>%v", v, v) |
| } |
| if msg.Method != "" { |
| // has a method, must be a call |
| return &Request{ |
| Method: msg.Method, |
| ID: id, |
| Params: msg.Params, |
| }, nil |
| } |
| // no method, should be a response |
| if !id.IsValid() { |
| return nil, ErrInvalidRequest |
| } |
| resp := &Response{ |
| ID: id, |
| Result: msg.Result, |
| } |
| // we have to check if msg.Error is nil to avoid a typed error |
| if msg.Error != nil { |
| resp.Error = msg.Error |
| } |
| return resp, nil |
| } |
| |
| func marshalToRaw(obj interface{}) (json.RawMessage, error) { |
| if obj == nil { |
| return nil, nil |
| } |
| data, err := json.Marshal(obj) |
| if err != nil { |
| return nil, err |
| } |
| return json.RawMessage(data), nil |
| } |