// 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 }