// Copyright (c) 2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcjson import ( "encoding/json" "fmt" ) // RPCVersion is a type to indicate RPC versions. type RPCVersion string const ( // version 1 of rpc RpcVersion1 RPCVersion = RPCVersion("1.0") // version 2 of rpc RpcVersion2 RPCVersion = RPCVersion("2.0") ) var validRpcVersions = []RPCVersion{RpcVersion1, RpcVersion2} // check if the rpc version is a valid version func (r RPCVersion) IsValid() bool { for _, version := range validRpcVersions { if version == r { return true } } return false } // cast rpc version to a string func (r RPCVersion) String() string { return string(r) } // RPCErrorCode represents an error code to be used as a part of an RPCError // which is in turn used in a JSON-RPC Response object. // // A specific type is used to help ensure the wrong errors aren't used. type RPCErrorCode int // RPCError represents an error that is used as a part of a JSON-RPC Response // object. type RPCError struct { Code RPCErrorCode `json:"code,omitempty"` Message string `json:"message,omitempty"` } // Guarantee RPCError satisfies the builtin error interface. var _, _ error = RPCError{}, (*RPCError)(nil) // Error returns a string describing the RPC error. This satisfies the // builtin error interface. func (e RPCError) Error() string { return fmt.Sprintf("%d: %s", e.Code, e.Message) } // NewRPCError constructs and returns a new JSON-RPC error that is suitable // for use in a JSON-RPC Response object. func NewRPCError(code RPCErrorCode, message string) *RPCError { return &RPCError{ Code: code, Message: message, } } // IsValidIDType checks that the ID field (which can go in any of the JSON-RPC // requests, responses, or notifications) is valid. JSON-RPC 1.0 allows any // valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only // allows string, number, or null, so this function restricts the allowed types // to that list. This function is only provided in case the caller is manually // marshalling for some reason. The functions which accept an ID in this // package already call this function to ensure the provided id is valid. func IsValidIDType(id interface{}) bool { switch id.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string, nil: return true default: return false } } // Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies // the specific command type which in turns leads to different parameters. // Callers typically will not use this directly since this package provides a // statically typed command infrastructure which handles creation of these // requests, however this struct it being exported in case the caller wants to // construct raw requests for some reason. type Request struct { Jsonrpc RPCVersion `json:"jsonrpc"` Method string `json:"method"` Params []json.RawMessage `json:"params"` ID interface{} `json:"id"` } // UnmarshalJSON is a custom unmarshal func for the Request struct. The param // field defaults to an empty json.RawMessage array it is omitted by the request // or nil if the supplied value is invalid. func (request *Request) UnmarshalJSON(b []byte) error { // Step 1: Create a type alias of the original struct. type Alias Request // Step 2: Create an anonymous struct with raw replacements for the special // fields. aux := &struct { Jsonrpc string `json:"jsonrpc"` Params []interface{} `json:"params"` *Alias }{ Alias: (*Alias)(request), } // Step 3: Unmarshal the data into the anonymous struct. err := json.Unmarshal(b, &aux) if err != nil { return err } // Step 4: Convert the raw fields to the desired types version := RPCVersion(aux.Jsonrpc) if version.IsValid() { request.Jsonrpc = version } rawParams := make([]json.RawMessage, 0) for _, param := range aux.Params { marshalledParam, err := json.Marshal(param) if err != nil { return err } rawMessage := json.RawMessage(marshalledParam) rawParams = append(rawParams, rawMessage) } request.Params = rawParams return nil } // NewRequest returns a new JSON-RPC request object given the provided rpc // version, id, method, and parameters. The parameters are marshalled into a // json.RawMessage for the Params field of the returned request object. This // function is only provided in case the caller wants to construct raw requests // for some reason. Typically callers will instead want to create a registered // concrete command type with the NewCmd or NewCmd functions and call the // MarshalCmd function with that command to generate the marshalled JSON-RPC // request. func NewRequest(rpcVersion RPCVersion, id interface{}, method string, params []interface{}) (*Request, error) { // default to JSON-RPC 1.0 if RPC type is not specified if !rpcVersion.IsValid() { str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) return nil, makeError(ErrInvalidType, str) } if !IsValidIDType(id) { str := fmt.Sprintf("the id of type '%T' is invalid", id) return nil, makeError(ErrInvalidType, str) } rawParams := make([]json.RawMessage, 0, len(params)) for _, param := range params { marshalledParam, err := json.Marshal(param) if err != nil { return nil, err } rawMessage := json.RawMessage(marshalledParam) rawParams = append(rawParams, rawMessage) } return &Request{ Jsonrpc: rpcVersion, ID: id, Method: method, Params: rawParams, }, nil } // Response is the general form of a JSON-RPC response. The type of the // Result field varies from one command to the next, so it is implemented as an // interface. The ID field has to be a pointer to allow for a nil value when // empty. type Response struct { Jsonrpc RPCVersion `json:"jsonrpc"` Result json.RawMessage `json:"result"` Error *RPCError `json:"error"` ID *interface{} `json:"id"` } // NewResponse returns a new JSON-RPC response object given the provided rpc // version, id, marshalled result, and RPC error. This function is only // provided in case the caller wants to construct raw responses for some reason. // Typically callers will instead want to create the fully marshalled JSON-RPC // response to send over the wire with the MarshalResponse function. func NewResponse(rpcVersion RPCVersion, id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) { if !rpcVersion.IsValid() { str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion) return nil, makeError(ErrInvalidType, str) } if !IsValidIDType(id) { str := fmt.Sprintf("the id of type '%T' is invalid", id) return nil, makeError(ErrInvalidType, str) } pid := &id return &Response{ Jsonrpc: rpcVersion, Result: marshalledResult, Error: rpcErr, ID: pid, }, nil } // MarshalResponse marshals the passed rpc version, id, result, and RPCError to // a JSON-RPC response byte slice that is suitable for transmission to a // JSON-RPC client. func MarshalResponse(rpcVersion RPCVersion, id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) { if !rpcVersion.IsValid() { if rpcVersion == "" { rpcVersion = RpcVersion1 } else { str := fmt.Sprintf("rpcversion '%s' is unsupported", rpcVersion) return nil, makeError(ErrInvalidType, str) } } marshalledResult, err := json.Marshal(result) if err != nil { return nil, err } response, err := NewResponse(rpcVersion, id, marshalledResult, rpcErr) if err != nil { return nil, err } return json.Marshal(&response) }