github.com/btcsuite/btcd@v0.24.0/btcjson/jsonrpc.go (about) 1 // Copyright (c) 2014 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package btcjson 6 7 import ( 8 "encoding/json" 9 "fmt" 10 ) 11 12 // RPCVersion is a type to indicate RPC versions. 13 type RPCVersion string 14 15 const ( 16 // version 1 of rpc 17 RpcVersion1 RPCVersion = RPCVersion("1.0") 18 // version 2 of rpc 19 RpcVersion2 RPCVersion = RPCVersion("2.0") 20 ) 21 22 var validRpcVersions = []RPCVersion{RpcVersion1, RpcVersion2} 23 24 // check if the rpc version is a valid version 25 func (r RPCVersion) IsValid() bool { 26 for _, version := range validRpcVersions { 27 if version == r { 28 return true 29 } 30 } 31 return false 32 } 33 34 // cast rpc version to a string 35 func (r RPCVersion) String() string { 36 return string(r) 37 } 38 39 // RPCErrorCode represents an error code to be used as a part of an RPCError 40 // which is in turn used in a JSON-RPC Response object. 41 // 42 // A specific type is used to help ensure the wrong errors aren't used. 43 type RPCErrorCode int 44 45 // RPCError represents an error that is used as a part of a JSON-RPC Response 46 // object. 47 type RPCError struct { 48 Code RPCErrorCode `json:"code,omitempty"` 49 Message string `json:"message,omitempty"` 50 } 51 52 // Guarantee RPCError satisfies the builtin error interface. 53 var _, _ error = RPCError{}, (*RPCError)(nil) 54 55 // Error returns a string describing the RPC error. This satisfies the 56 // builtin error interface. 57 func (e RPCError) Error() string { 58 return fmt.Sprintf("%d: %s", e.Code, e.Message) 59 } 60 61 // NewRPCError constructs and returns a new JSON-RPC error that is suitable 62 // for use in a JSON-RPC Response object. 63 func NewRPCError(code RPCErrorCode, message string) *RPCError { 64 return &RPCError{ 65 Code: code, 66 Message: message, 67 } 68 } 69 70 // IsValidIDType checks that the ID field (which can go in any of the JSON-RPC 71 // requests, responses, or notifications) is valid. JSON-RPC 1.0 allows any 72 // valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only 73 // allows string, number, or null, so this function restricts the allowed types 74 // to that list. This function is only provided in case the caller is manually 75 // marshalling for some reason. The functions which accept an ID in this 76 // package already call this function to ensure the provided id is valid. 77 func IsValidIDType(id interface{}) bool { 78 switch id.(type) { 79 case int, int8, int16, int32, int64, 80 uint, uint8, uint16, uint32, uint64, 81 float32, float64, 82 string, 83 nil: 84 return true 85 default: 86 return false 87 } 88 } 89 90 // Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies 91 // the specific command type which in turns leads to different parameters. 92 // Callers typically will not use this directly since this package provides a 93 // statically typed command infrastructure which handles creation of these 94 // requests, however this struct it being exported in case the caller wants to 95 // construct raw requests for some reason. 96 type Request struct { 97 Jsonrpc RPCVersion `json:"jsonrpc"` 98 Method string `json:"method"` 99 Params []json.RawMessage `json:"params"` 100 ID interface{} `json:"id"` 101 } 102 103 // UnmarshalJSON is a custom unmarshal func for the Request struct. The param 104 // field defaults to an empty json.RawMessage array it is omitted by the request 105 // or nil if the supplied value is invalid. 106 func (request *Request) UnmarshalJSON(b []byte) error { 107 // Step 1: Create a type alias of the original struct. 108 type Alias Request 109 110 // Step 2: Create an anonymous struct with raw replacements for the special 111 // fields. 112 aux := &struct { 113 Jsonrpc string `json:"jsonrpc"` 114 Params []interface{} `json:"params"` 115 *Alias 116 }{ 117 Alias: (*Alias)(request), 118 } 119 120 // Step 3: Unmarshal the data into the anonymous struct. 121 err := json.Unmarshal(b, &aux) 122 if err != nil { 123 return err 124 } 125 126 // Step 4: Convert the raw fields to the desired types 127 128 version := RPCVersion(aux.Jsonrpc) 129 if version.IsValid() { 130 request.Jsonrpc = version 131 } 132 133 rawParams := make([]json.RawMessage, 0) 134 135 for _, param := range aux.Params { 136 marshalledParam, err := json.Marshal(param) 137 if err != nil { 138 return err 139 } 140 141 rawMessage := json.RawMessage(marshalledParam) 142 rawParams = append(rawParams, rawMessage) 143 } 144 145 request.Params = rawParams 146 147 return nil 148 } 149 150 // NewRequest returns a new JSON-RPC request object given the provided rpc 151 // version, id, method, and parameters. The parameters are marshalled into a 152 // json.RawMessage for the Params field of the returned request object. This 153 // function is only provided in case the caller wants to construct raw requests 154 // for some reason. Typically callers will instead want to create a registered 155 // concrete command type with the NewCmd or New<Foo>Cmd functions and call the 156 // MarshalCmd function with that command to generate the marshalled JSON-RPC 157 // request. 158 func NewRequest(rpcVersion RPCVersion, id interface{}, method string, params []interface{}) (*Request, error) { 159 // default to JSON-RPC 1.0 if RPC type is not specified 160 if rpcVersion == "" { 161 rpcVersion = RpcVersion1 162 } 163 if !rpcVersion.IsValid() { 164 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 165 return nil, makeError(ErrInvalidType, str) 166 } 167 168 if !IsValidIDType(id) { 169 str := fmt.Sprintf("the id of type '%T' is invalid", id) 170 return nil, makeError(ErrInvalidType, str) 171 } 172 173 rawParams := make([]json.RawMessage, 0, len(params)) 174 for _, param := range params { 175 marshalledParam, err := json.Marshal(param) 176 if err != nil { 177 return nil, err 178 } 179 rawMessage := json.RawMessage(marshalledParam) 180 rawParams = append(rawParams, rawMessage) 181 } 182 183 return &Request{ 184 Jsonrpc: rpcVersion, 185 ID: id, 186 Method: method, 187 Params: rawParams, 188 }, nil 189 } 190 191 // Response is the general form of a JSON-RPC response. The type of the 192 // Result field varies from one command to the next, so it is implemented as an 193 // interface. The ID field has to be a pointer to allow for a nil value when 194 // empty. 195 type Response struct { 196 Jsonrpc RPCVersion `json:"jsonrpc"` 197 Result json.RawMessage `json:"result"` 198 Error *RPCError `json:"error"` 199 ID *interface{} `json:"id"` 200 } 201 202 // NewResponse returns a new JSON-RPC response object given the provided rpc 203 // version, id, marshalled result, and RPC error. This function is only 204 // provided in case the caller wants to construct raw responses for some reason. 205 // Typically callers will instead want to create the fully marshalled JSON-RPC 206 // response to send over the wire with the MarshalResponse function. 207 func NewResponse(rpcVersion RPCVersion, id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) { 208 if !rpcVersion.IsValid() { 209 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 210 return nil, makeError(ErrInvalidType, str) 211 } 212 213 if !IsValidIDType(id) { 214 str := fmt.Sprintf("the id of type '%T' is invalid", id) 215 return nil, makeError(ErrInvalidType, str) 216 } 217 218 pid := &id 219 return &Response{ 220 Jsonrpc: rpcVersion, 221 Result: marshalledResult, 222 Error: rpcErr, 223 ID: pid, 224 }, nil 225 } 226 227 // MarshalResponse marshals the passed rpc version, id, result, and RPCError to 228 // a JSON-RPC response byte slice that is suitable for transmission to a 229 // JSON-RPC client. 230 func MarshalResponse(rpcVersion RPCVersion, id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) { 231 if !rpcVersion.IsValid() { 232 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 233 return nil, makeError(ErrInvalidType, str) 234 } 235 236 marshalledResult, err := json.Marshal(result) 237 if err != nil { 238 return nil, err 239 } 240 response, err := NewResponse(rpcVersion, id, marshalledResult, rpcErr) 241 if err != nil { 242 return nil, err 243 } 244 return json.Marshal(&response) 245 }