infra/proxyd/proxyd/rpc.go
Chris Wessels 15a59bad99 fix(proxyd): Fix compliance with JSON-RPC 2.0 spec by adding optional RPCError.Data (#3683)
* fix: add optional data field to RPCError struct

* fix: formatting lint

* feat(proxyd): add changeset
2022-10-11 08:51:02 -06:00

171 lines
3.4 KiB
Go

package proxyd
import (
"encoding/json"
"io"
"strings"
)
type RPCReq struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID json.RawMessage `json:"id"`
}
type RPCRes struct {
JSONRPC string
Result interface{}
Error *RPCErr
ID json.RawMessage
}
type rpcResJSON struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *RPCErr `json:"error,omitempty"`
ID json.RawMessage `json:"id"`
}
type nullResultRPCRes struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result"`
ID json.RawMessage `json:"id"`
}
func (r *RPCRes) IsError() bool {
return r.Error != nil
}
func (r *RPCRes) MarshalJSON() ([]byte, error) {
if r.Result == nil && r.Error == nil {
return json.Marshal(&nullResultRPCRes{
JSONRPC: r.JSONRPC,
Result: nil,
ID: r.ID,
})
}
return json.Marshal(&rpcResJSON{
JSONRPC: r.JSONRPC,
Result: r.Result,
Error: r.Error,
ID: r.ID,
})
}
type RPCErr struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data,omitempty"`
HTTPErrorCode int `json:"-"`
}
func (r *RPCErr) Error() string {
return r.Message
}
func (r *RPCErr) Clone() *RPCErr {
return &RPCErr{
Code: r.Code,
Message: r.Message,
HTTPErrorCode: r.HTTPErrorCode,
}
}
func IsValidID(id json.RawMessage) bool {
// handle the case where the ID is a string
if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") {
return len(id) > 2
}
// technically allows a boolean/null ID, but so does Geth
// https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L72
return len(id) > 0 && id[0] != '{' && id[0] != '['
}
func ParseRPCReq(body []byte) (*RPCReq, error) {
req := new(RPCReq)
if err := json.Unmarshal(body, req); err != nil {
return nil, ErrParseErr
}
return req, nil
}
func ParseBatchRPCReq(body []byte) ([]json.RawMessage, error) {
batch := make([]json.RawMessage, 0)
if err := json.Unmarshal(body, &batch); err != nil {
return nil, err
}
return batch, nil
}
func ParseRPCRes(r io.Reader) (*RPCRes, error) {
body, err := io.ReadAll(r)
if err != nil {
return nil, wrapErr(err, "error reading RPC response")
}
res := new(RPCRes)
if err := json.Unmarshal(body, res); err != nil {
return nil, wrapErr(err, "error unmarshaling RPC response")
}
return res, nil
}
func ValidateRPCReq(req *RPCReq) error {
if req.JSONRPC != JSONRPCVersion {
return ErrInvalidRequest("invalid JSON-RPC version")
}
if req.Method == "" {
return ErrInvalidRequest("no method specified")
}
if !IsValidID(req.ID) {
return ErrInvalidRequest("invalid ID")
}
return nil
}
func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes {
var rpcErr *RPCErr
if rr, ok := err.(*RPCErr); ok {
rpcErr = rr
} else {
rpcErr = &RPCErr{
Code: JSONRPCErrorInternal,
Message: err.Error(),
}
}
return &RPCRes{
JSONRPC: JSONRPCVersion,
Error: rpcErr,
ID: id,
}
}
func NewRPCRes(id json.RawMessage, result interface{}) *RPCRes {
return &RPCRes{
JSONRPC: JSONRPCVersion,
Result: result,
ID: id,
}
}
func IsBatch(raw []byte) bool {
for _, c := range raw {
// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
continue
}
return c == '['
}
return false
}