1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package jsonrpc2 6 7import ( 8 "encoding/json" 9 10 errors "golang.org/x/xerrors" 11) 12 13// ID is a Request identifier. 14type ID struct { 15 value interface{} 16} 17 18// Message is the interface to all jsonrpc2 message types. 19// They share no common functionality, but are a closed set of concrete types 20// that are allowed to implement this interface. The message types are *Request 21// and *Response. 22type Message interface { 23 // marshal builds the wire form from the API form. 24 // It is private, which makes the set of Message implementations closed. 25 marshal(to *wireCombined) 26} 27 28// Request is a Message sent to a peer to request behavior. 29// If it has an ID it is a call, otherwise it is a notification. 30type Request struct { 31 // ID of this request, used to tie the Response back to the request. 32 // This will be nil for notifications. 33 ID ID 34 // Method is a string containing the method name to invoke. 35 Method string 36 // Params is either a struct or an array with the parameters of the method. 37 Params json.RawMessage 38} 39 40// Response is a Message used as a reply to a call Request. 41// It will have the same ID as the call it is a response to. 42type Response struct { 43 // result is the content of the response. 44 Result json.RawMessage 45 // err is set only if the call failed. 46 Error error 47 // id of the request this is a response to. 48 ID ID 49} 50 51// StringID creates a new string request identifier. 52func StringID(s string) ID { return ID{value: s} } 53 54// Int64ID creates a new integer request identifier. 55func Int64ID(i int64) ID { return ID{value: i} } 56 57// IsValid returns true if the ID is a valid identifier. 58// The default value for ID will return false. 59func (id ID) IsValid() bool { return id.value != nil } 60 61// Raw returns the underlying value of the ID. 62func (id ID) Raw() interface{} { return id.value } 63 64// NewNotification constructs a new Notification message for the supplied 65// method and parameters. 66func NewNotification(method string, params interface{}) (*Request, error) { 67 p, merr := marshalToRaw(params) 68 return &Request{Method: method, Params: p}, merr 69} 70 71// NewCall constructs a new Call message for the supplied ID, method and 72// parameters. 73func NewCall(id ID, method string, params interface{}) (*Request, error) { 74 p, merr := marshalToRaw(params) 75 return &Request{ID: id, Method: method, Params: p}, merr 76} 77 78func (msg *Request) IsCall() bool { return msg.ID.IsValid() } 79 80func (msg *Request) marshal(to *wireCombined) { 81 to.ID = msg.ID.value 82 to.Method = msg.Method 83 to.Params = msg.Params 84} 85 86// NewResponse constructs a new Response message that is a reply to the 87// supplied. If err is set result may be ignored. 88func NewResponse(id ID, result interface{}, rerr error) (*Response, error) { 89 r, merr := marshalToRaw(result) 90 return &Response{ID: id, Result: r, Error: rerr}, merr 91} 92 93func (msg *Response) marshal(to *wireCombined) { 94 to.ID = msg.ID.value 95 to.Error = toWireError(msg.Error) 96 to.Result = msg.Result 97} 98 99func toWireError(err error) *wireError { 100 if err == nil { 101 // no error, the response is complete 102 return nil 103 } 104 if err, ok := err.(*wireError); ok { 105 // already a wire error, just use it 106 return err 107 } 108 result := &wireError{Message: err.Error()} 109 var wrapped *wireError 110 if errors.As(err, &wrapped) { 111 // if we wrapped a wire error, keep the code from the wrapped error 112 // but the message from the outer error 113 result.Code = wrapped.Code 114 } 115 return result 116} 117 118func EncodeMessage(msg Message) ([]byte, error) { 119 wire := wireCombined{VersionTag: wireVersion} 120 msg.marshal(&wire) 121 data, err := json.Marshal(&wire) 122 if err != nil { 123 return data, errors.Errorf("marshaling jsonrpc message: %w", err) 124 } 125 return data, nil 126} 127 128func DecodeMessage(data []byte) (Message, error) { 129 msg := wireCombined{} 130 if err := json.Unmarshal(data, &msg); err != nil { 131 return nil, errors.Errorf("unmarshaling jsonrpc message: %w", err) 132 } 133 if msg.VersionTag != wireVersion { 134 return nil, errors.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion) 135 } 136 id := ID{} 137 switch v := msg.ID.(type) { 138 case nil: 139 case float64: 140 // coerce the id type to int64 if it is float64, the spec does not allow fractional parts 141 id = Int64ID(int64(v)) 142 case int64: 143 id = Int64ID(v) 144 case string: 145 id = StringID(v) 146 default: 147 return nil, errors.Errorf("invalid message id type <%T>%v", v, v) 148 } 149 if msg.Method != "" { 150 // has a method, must be a call 151 return &Request{ 152 Method: msg.Method, 153 ID: id, 154 Params: msg.Params, 155 }, nil 156 } 157 // no method, should be a response 158 if !id.IsValid() { 159 return nil, ErrInvalidRequest 160 } 161 resp := &Response{ 162 ID: id, 163 Result: msg.Result, 164 } 165 // we have to check if msg.Error is nil to avoid a typed error 166 if msg.Error != nil { 167 resp.Error = msg.Error 168 } 169 return resp, nil 170} 171 172func marshalToRaw(obj interface{}) (json.RawMessage, error) { 173 if obj == nil { 174 return nil, nil 175 } 176 data, err := json.Marshal(obj) 177 if err != nil { 178 return nil, err 179 } 180 return json.RawMessage(data), nil 181} 182