forked from btcsuite/btcd
/
jsonrpc.go
242 lines (208 loc) · 7.45 KB
/
jsonrpc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// 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 New<Foo>Cmd 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() {
str := fmt.Sprintf("rpcversion '%s' is invalid", 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)
}