jsonrpc.go raw

   1  package btcjson
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  )
   7  
   8  type (
   9  	// RPCError represents an error that is used as a part of a JSON-RPC Response object.
  10  	RPCError struct {
  11  		Code    RPCErrorCode `json:"code,omitempty"`
  12  		Message string       `json:"message,omitempty"`
  13  	}
  14  	// RPCErrorCode represents an error code to be used as a part of an RPCError which is in turn used in a JSON-RPC
  15  	// Response object. A specific type is used to help ensure the wrong errors aren't used.
  16  	RPCErrorCode int
  17  	// Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies the specific command type which in
  18  	// turns leads to different parameters. Callers typically will not use this directly since this package provides a
  19  	// statically typed command infrastructure which handles creation of these requests, however this struct it being
  20  	// exported in case the caller wants to construct raw requests for some reason.
  21  	Request struct {
  22  		Jsonrpc string            `json:"jsonrpc"`
  23  		Method  string            `json:"method"`
  24  		Params  []json.RawMessage `json:"netparams"`
  25  		ID      interface{}       `json:"id"`
  26  	}
  27  	// Response is the general form of a JSON-RPC response. The type of the Result field varies from one command to the
  28  	// next, so it is implemented as an interface. The ID field has to be a pointer for Go to put a null in it when
  29  	// empty.
  30  	Response struct {
  31  		Result json.RawMessage `json:"result"`
  32  		Error  *RPCError       `json:"error"`
  33  		ID     *interface{}    `json:"id"`
  34  	}
  35  )
  36  
  37  // Guarantee RPCError satisfies the builtin error interface.
  38  var _, _ error = RPCError{}, (*RPCError)(nil)
  39  
  40  // BTCJSONError returns a string describing the RPC error.  This satisfies the builtin error interface.
  41  func (e RPCError) Error() string {
  42  	return fmt.Sprintf("%d: %s", e.Code, e.Message)
  43  }
  44  
  45  // IsValidIDType checks that the ID field (which can go in any of the JSON-RPC requests, responses, or notifications) is
  46  // valid. JSON-RPC 1.0 allows any valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only allows
  47  // string, number, or null, so this function restricts the allowed types to that list. This function is only provided in
  48  // case the caller is manually marshalling for some reason. The functions which accept an ID in this package already
  49  // call this function to ensure the provided id is valid.
  50  func IsValidIDType(id interface{}) bool {
  51  	switch id.(type) {
  52  	case int, int8, int16, int32, int64,
  53  		uint, uint8, uint16, uint32, uint64,
  54  		float32, float64,
  55  		string,
  56  		nil:
  57  		return true
  58  	default:
  59  		return false
  60  	}
  61  }
  62  
  63  // MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC response byte slice that is suitable for
  64  // transmission to a JSON-RPC client.
  65  func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
  66  	marshalledResult, e := json.Marshal(result)
  67  	if e != nil {
  68  		E.Ln(e)
  69  		return nil, e
  70  	}
  71  	response, e := NewResponse(id, marshalledResult, rpcErr)
  72  	if e != nil {
  73  		E.Ln(e)
  74  		return nil, e
  75  	}
  76  	return json.Marshal(&response)
  77  }
  78  
  79  // NewRPCError constructs and returns a new JSON-RPC error that is suitable for use in a JSON-RPC Response object.
  80  func NewRPCError(code RPCErrorCode, message string) *RPCError {
  81  	return &RPCError{
  82  		Code:    code,
  83  		Message: message,
  84  	}
  85  }
  86  
  87  // NewRequest returns a new JSON-RPC 1.0 request object given the provided id, method, and parameters. The parameters
  88  // are marshalled into a json.RawMessage for the Params field of the returned request object. This function is only
  89  // provided in case the caller wants to construct raw requests for some reason. Typically callers will instead want to
  90  // create a registered concrete command type with the NewCmd or New<Foo>Cmd functions and call the MarshalCmd function
  91  // with that command to generate the marshalled JSON-RPC request.
  92  func NewRequest(id interface{}, method string, params []interface{}) (rq *Request, e error) {
  93  	if !IsValidIDType(id) {
  94  		str := fmt.Sprintf("the id of type '%T' is invalid", id)
  95  		return nil, makeError(ErrInvalidType, str)
  96  	}
  97  	rawParams := make([]json.RawMessage, 0, len(params))
  98  	for _, param := range params {
  99  		var marshalledParam []byte
 100  		marshalledParam, e = json.Marshal(param)
 101  		if e != nil {
 102  			E.Ln(e)
 103  			return nil, e
 104  		}
 105  		rawMessage := json.RawMessage(marshalledParam)
 106  		rawParams = append(rawParams, rawMessage)
 107  	}
 108  	return &Request{
 109  		Jsonrpc: "1.0",
 110  		ID:      id,
 111  		Method:  method,
 112  		Params:  rawParams,
 113  	}, nil
 114  }
 115  
 116  // NewResponse returns a new JSON-RPC response object given the provided id, marshalled result, and RPC error. This
 117  // function is only provided in case the caller wants to construct raw responses for some reason. Typically callers will
 118  // instead want to create the fully marshalled JSON-RPC response to send over the wire with the MarshalResponse
 119  // function.
 120  func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
 121  	if !IsValidIDType(id) {
 122  		str := fmt.Sprintf("the id of type '%T' is invalid", id)
 123  		return nil, makeError(ErrInvalidType, str)
 124  	}
 125  	pid := &id
 126  	return &Response{
 127  		Result: marshalledResult,
 128  		Error:  rpcErr,
 129  		ID:     pid,
 130  	}, nil
 131  }
 132