github.com/noirx94/tendermintmp@v0.0.1/rpc/jsonrpc/types/types.go (about) 1 package types 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "reflect" 9 "strings" 10 11 tmjson "github.com/tendermint/tendermint/libs/json" 12 ) 13 14 // a wrapper to emulate a sum type: jsonrpcid = string | int 15 // TODO: refactor when Go 2.0 arrives https://github.com/golang/go/issues/19412 16 type jsonrpcid interface { 17 isJSONRPCID() 18 } 19 20 // JSONRPCStringID a wrapper for JSON-RPC string IDs 21 type JSONRPCStringID string 22 23 func (JSONRPCStringID) isJSONRPCID() {} 24 func (id JSONRPCStringID) String() string { return string(id) } 25 26 // JSONRPCIntID a wrapper for JSON-RPC integer IDs 27 type JSONRPCIntID int 28 29 func (JSONRPCIntID) isJSONRPCID() {} 30 func (id JSONRPCIntID) String() string { return fmt.Sprintf("%d", id) } 31 32 func idFromInterface(idInterface interface{}) (jsonrpcid, error) { 33 switch id := idInterface.(type) { 34 case string: 35 return JSONRPCStringID(id), nil 36 case float64: 37 // json.Unmarshal uses float64 for all numbers 38 // (https://golang.org/pkg/encoding/json/#Unmarshal), 39 // but the JSONRPC2.0 spec says the id SHOULD NOT contain 40 // decimals - so we truncate the decimals here. 41 return JSONRPCIntID(int(id)), nil 42 default: 43 typ := reflect.TypeOf(id) 44 return nil, fmt.Errorf("json-rpc ID (%v) is of unknown type (%v)", id, typ) 45 } 46 } 47 48 //---------------------------------------- 49 // REQUEST 50 51 type RPCRequest struct { 52 JSONRPC string `json:"jsonrpc"` 53 ID jsonrpcid `json:"id,omitempty"` 54 Method string `json:"method"` 55 Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} 56 } 57 58 // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int 59 func (req *RPCRequest) UnmarshalJSON(data []byte) error { 60 unsafeReq := struct { 61 JSONRPC string `json:"jsonrpc"` 62 ID interface{} `json:"id,omitempty"` 63 Method string `json:"method"` 64 Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} 65 }{} 66 67 err := json.Unmarshal(data, &unsafeReq) 68 if err != nil { 69 return err 70 } 71 72 if unsafeReq.ID == nil { // notification 73 return nil 74 } 75 76 req.JSONRPC = unsafeReq.JSONRPC 77 req.Method = unsafeReq.Method 78 req.Params = unsafeReq.Params 79 id, err := idFromInterface(unsafeReq.ID) 80 if err != nil { 81 return err 82 } 83 req.ID = id 84 85 return nil 86 } 87 88 func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest { 89 return RPCRequest{ 90 JSONRPC: "2.0", 91 ID: id, 92 Method: method, 93 Params: params, 94 } 95 } 96 97 func (req RPCRequest) String() string { 98 return fmt.Sprintf("RPCRequest{%s %s/%X}", req.ID, req.Method, req.Params) 99 } 100 101 func MapToRequest(id jsonrpcid, method string, params map[string]interface{}) (RPCRequest, error) { 102 var paramsMap = make(map[string]json.RawMessage, len(params)) 103 for name, value := range params { 104 valueJSON, err := tmjson.Marshal(value) 105 if err != nil { 106 return RPCRequest{}, err 107 } 108 paramsMap[name] = valueJSON 109 } 110 111 payload, err := json.Marshal(paramsMap) 112 if err != nil { 113 return RPCRequest{}, err 114 } 115 116 return NewRPCRequest(id, method, payload), nil 117 } 118 119 func ArrayToRequest(id jsonrpcid, method string, params []interface{}) (RPCRequest, error) { 120 var paramsMap = make([]json.RawMessage, len(params)) 121 for i, value := range params { 122 valueJSON, err := tmjson.Marshal(value) 123 if err != nil { 124 return RPCRequest{}, err 125 } 126 paramsMap[i] = valueJSON 127 } 128 129 payload, err := json.Marshal(paramsMap) 130 if err != nil { 131 return RPCRequest{}, err 132 } 133 134 return NewRPCRequest(id, method, payload), nil 135 } 136 137 //---------------------------------------- 138 // RESPONSE 139 140 type RPCError struct { 141 Code int `json:"code"` 142 Message string `json:"message"` 143 Data string `json:"data,omitempty"` 144 } 145 146 func (err RPCError) Error() string { 147 const baseFormat = "RPC error %v - %s" 148 if err.Data != "" { 149 return fmt.Sprintf(baseFormat+": %s", err.Code, err.Message, err.Data) 150 } 151 return fmt.Sprintf(baseFormat, err.Code, err.Message) 152 } 153 154 type RPCResponse struct { 155 JSONRPC string `json:"jsonrpc"` 156 ID jsonrpcid `json:"id,omitempty"` 157 Result json.RawMessage `json:"result,omitempty"` 158 Error *RPCError `json:"error,omitempty"` 159 } 160 161 // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int 162 func (resp *RPCResponse) UnmarshalJSON(data []byte) error { 163 unsafeResp := &struct { 164 JSONRPC string `json:"jsonrpc"` 165 ID interface{} `json:"id,omitempty"` 166 Result json.RawMessage `json:"result,omitempty"` 167 Error *RPCError `json:"error,omitempty"` 168 }{} 169 err := json.Unmarshal(data, &unsafeResp) 170 if err != nil { 171 return err 172 } 173 resp.JSONRPC = unsafeResp.JSONRPC 174 resp.Error = unsafeResp.Error 175 resp.Result = unsafeResp.Result 176 if unsafeResp.ID == nil { 177 return nil 178 } 179 id, err := idFromInterface(unsafeResp.ID) 180 if err != nil { 181 return err 182 } 183 resp.ID = id 184 return nil 185 } 186 187 func NewRPCSuccessResponse(id jsonrpcid, res interface{}) RPCResponse { 188 var rawMsg json.RawMessage 189 190 if res != nil { 191 var js []byte 192 js, err := tmjson.Marshal(res) 193 if err != nil { 194 return RPCInternalError(id, fmt.Errorf("error marshalling response: %w", err)) 195 } 196 rawMsg = json.RawMessage(js) 197 } 198 199 return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg} 200 } 201 202 func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse { 203 return RPCResponse{ 204 JSONRPC: "2.0", 205 ID: id, 206 Error: &RPCError{Code: code, Message: msg, Data: data}, 207 } 208 } 209 210 func (resp RPCResponse) String() string { 211 if resp.Error == nil { 212 return fmt.Sprintf("RPCResponse{%s %X}", resp.ID, resp.Result) 213 } 214 return fmt.Sprintf("RPCResponse{%s %v}", resp.ID, resp.Error) 215 } 216 217 // From the JSON-RPC 2.0 spec: 218 // If there was an error in detecting the id in the Request object (e.g. Parse 219 // error/Invalid Request), it MUST be Null. 220 func RPCParseError(err error) RPCResponse { 221 return NewRPCErrorResponse(nil, -32700, "Parse error. Invalid JSON", err.Error()) 222 } 223 224 // From the JSON-RPC 2.0 spec: 225 // If there was an error in detecting the id in the Request object (e.g. Parse 226 // error/Invalid Request), it MUST be Null. 227 func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse { 228 return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error()) 229 } 230 231 func RPCMethodNotFoundError(id jsonrpcid) RPCResponse { 232 return NewRPCErrorResponse(id, -32601, "Method not found", "") 233 } 234 235 func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse { 236 return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error()) 237 } 238 239 func RPCInternalError(id jsonrpcid, err error) RPCResponse { 240 return NewRPCErrorResponse(id, -32603, "Internal error", err.Error()) 241 } 242 243 func RPCServerError(id jsonrpcid, err error) RPCResponse { 244 return NewRPCErrorResponse(id, -32000, "Server error", err.Error()) 245 } 246 247 //---------------------------------------- 248 249 // WSRPCConnection represents a websocket connection. 250 type WSRPCConnection interface { 251 // GetRemoteAddr returns a remote address of the connection. 252 GetRemoteAddr() string 253 // WriteRPCResponse writes the response onto connection (BLOCKING). 254 WriteRPCResponse(context.Context, RPCResponse) error 255 // TryWriteRPCResponse tries to write the response onto connection (NON-BLOCKING). 256 TryWriteRPCResponse(RPCResponse) bool 257 // Context returns the connection's context. 258 Context() context.Context 259 } 260 261 // Context is the first parameter for all functions. It carries a json-rpc 262 // request, http request and websocket connection. 263 // 264 // - JSONReq is non-nil when JSONRPC is called over websocket or HTTP. 265 // - WSConn is non-nil when we're connected via a websocket. 266 // - HTTPReq is non-nil when URI or JSONRPC is called over HTTP. 267 type Context struct { 268 // json-rpc request 269 JSONReq *RPCRequest 270 // websocket connection 271 WSConn WSRPCConnection 272 // http request 273 HTTPReq *http.Request 274 } 275 276 // RemoteAddr returns the remote address (usually a string "IP:port"). 277 // If neither HTTPReq nor WSConn is set, an empty string is returned. 278 // HTTP: 279 // http.Request#RemoteAddr 280 // WS: 281 // result of GetRemoteAddr 282 func (ctx *Context) RemoteAddr() string { 283 if ctx.HTTPReq != nil { 284 return ctx.HTTPReq.RemoteAddr 285 } else if ctx.WSConn != nil { 286 return ctx.WSConn.GetRemoteAddr() 287 } 288 return "" 289 } 290 291 // Context returns the request's context. 292 // The returned context is always non-nil; it defaults to the background context. 293 // HTTP: 294 // The context is canceled when the client's connection closes, the request 295 // is canceled (with HTTP/2), or when the ServeHTTP method returns. 296 // WS: 297 // The context is canceled when the client's connections closes. 298 func (ctx *Context) Context() context.Context { 299 if ctx.HTTPReq != nil { 300 return ctx.HTTPReq.Context() 301 } else if ctx.WSConn != nil { 302 return ctx.WSConn.Context() 303 } 304 return context.Background() 305 } 306 307 //---------------------------------------- 308 // SOCKETS 309 310 // 311 // Determine if its a unix or tcp socket. 312 // If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port 313 // TODO: deprecate 314 func SocketType(listenAddr string) string { 315 socketType := "unix" 316 if len(strings.Split(listenAddr, ":")) >= 2 { 317 socketType = "tcp" 318 } 319 return socketType 320 }