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