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  }