github.com/palcoin-project/palcd@v1.0.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.IsValid() { 161 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 162 return nil, makeError(ErrInvalidType, str) 163 } 164 165 if !IsValidIDType(id) { 166 str := fmt.Sprintf("the id of type '%T' is invalid", id) 167 return nil, makeError(ErrInvalidType, str) 168 } 169 170 rawParams := make([]json.RawMessage, 0, len(params)) 171 for _, param := range params { 172 marshalledParam, err := json.Marshal(param) 173 if err != nil { 174 return nil, err 175 } 176 rawMessage := json.RawMessage(marshalledParam) 177 rawParams = append(rawParams, rawMessage) 178 } 179 180 return &Request{ 181 Jsonrpc: rpcVersion, 182 ID: id, 183 Method: method, 184 Params: rawParams, 185 }, nil 186 } 187 188 // Response is the general form of a JSON-RPC response. The type of the 189 // Result field varies from one command to the next, so it is implemented as an 190 // interface. The ID field has to be a pointer to allow for a nil value when 191 // empty. 192 type Response struct { 193 Jsonrpc RPCVersion `json:"jsonrpc"` 194 Result json.RawMessage `json:"result"` 195 Error *RPCError `json:"error"` 196 ID *interface{} `json:"id"` 197 } 198 199 // NewResponse returns a new JSON-RPC response object given the provided rpc 200 // version, id, marshalled result, and RPC error. This function is only 201 // provided in case the caller wants to construct raw responses for some reason. 202 // Typically callers will instead want to create the fully marshalled JSON-RPC 203 // response to send over the wire with the MarshalResponse function. 204 func NewResponse(rpcVersion RPCVersion, id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) { 205 if !rpcVersion.IsValid() { 206 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 207 return nil, makeError(ErrInvalidType, str) 208 } 209 210 if !IsValidIDType(id) { 211 str := fmt.Sprintf("the id of type '%T' is invalid", id) 212 return nil, makeError(ErrInvalidType, str) 213 } 214 215 pid := &id 216 return &Response{ 217 Jsonrpc: rpcVersion, 218 Result: marshalledResult, 219 Error: rpcErr, 220 ID: pid, 221 }, nil 222 } 223 224 // MarshalResponse marshals the passed rpc version, id, result, and RPCError to 225 // a JSON-RPC response byte slice that is suitable for transmission to a 226 // JSON-RPC client. 227 func MarshalResponse(rpcVersion RPCVersion, id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) { 228 if !rpcVersion.IsValid() { 229 str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) 230 return nil, makeError(ErrInvalidType, str) 231 } 232 233 marshalledResult, err := json.Marshal(result) 234 if err != nil { 235 return nil, err 236 } 237 response, err := NewResponse(rpcVersion, id, marshalledResult, rpcErr) 238 if err != nil { 239 return nil, err 240 } 241 return json.Marshal(&response) 242 }