Brian Picciano
16aca610b4
It's still not possible to pick a network from the command-line, so this is a bit broken, but the daemon component should handle it correctly at least.
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
// Package jsonrpc2 implements server/client interfaces for a JSONRPC2
|
|
// transport, as defined by https://www.jsonrpc.org/specification.
|
|
package jsonrpc2
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
const version = "2.0"
|
|
|
|
// RequestParams are the parameters passed in a Request. Meta contains
|
|
// information that is not directly related to what is being requested, while
|
|
// Args are the request's actual arguments.
|
|
type RequestParams struct {
|
|
Meta map[string]any `json:"meta,omitempty"`
|
|
Args []json.RawMessage `json:"args,omitempty"`
|
|
}
|
|
|
|
// Request encodes an RPC request according to the spec.
|
|
type Request struct {
|
|
Version string `json:"jsonrpc"` // must be "2.0"
|
|
Method string `json:"method"`
|
|
Params RequestParams `json:"params,omitempty"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
type response[Result any] struct {
|
|
Version string `json:"jsonrpc"` // must be "2.0"
|
|
ID string `json:"id"`
|
|
Result Result `json:"result,omitempty"`
|
|
Error *Error `json:"error,omitempty"`
|
|
}
|
|
|
|
func newID() string {
|
|
b := make([]byte, 8)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
return fmt.Sprintf("%d-%x", time.Now().UnixNano(), b)
|
|
}
|
|
|
|
// encodeRequest writes a request to an io.Writer, returning the ID of the
|
|
// request.
|
|
func encodeRequest(
|
|
ctx context.Context, enc *json.Encoder, method string, args []any,
|
|
) (
|
|
string, error,
|
|
) {
|
|
var (
|
|
argsBs = make([]json.RawMessage, len(args))
|
|
err error
|
|
)
|
|
for i := range args {
|
|
argsBs[i], err = json.Marshal(args[i])
|
|
if err != nil {
|
|
return "", fmt.Errorf("encoding arg %d as JSON: %w", i, err)
|
|
}
|
|
}
|
|
|
|
var (
|
|
id = newID()
|
|
reqEnvelope = Request{
|
|
Version: version,
|
|
Method: method,
|
|
Params: RequestParams{
|
|
Meta: GetMeta(ctx),
|
|
Args: argsBs,
|
|
},
|
|
ID: id,
|
|
}
|
|
)
|
|
|
|
if err := enc.Encode(reqEnvelope); err != nil {
|
|
return "", fmt.Errorf("encoding as JSON: %w", err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// decodeRequest decodes a request from an io.Reader, or returns an error code
|
|
// and an error describing what went wrong.
|
|
func decodeRequest(dec *json.Decoder) (Request, Error) {
|
|
var reqEnvelope Request
|
|
err := dec.Decode(&reqEnvelope)
|
|
switch {
|
|
case err != nil:
|
|
return reqEnvelope, NewError(
|
|
errCodeParse, "decoding request as JSON: %v", err,
|
|
)
|
|
case reqEnvelope.Version != version:
|
|
return reqEnvelope, NewError(
|
|
errCodeInvalidRequest,
|
|
"unknown JSONRPC version: %q",
|
|
reqEnvelope.Version,
|
|
)
|
|
case reqEnvelope.ID == "":
|
|
return reqEnvelope, NewError(
|
|
errCodeInvalidRequest, "request is missing an ID",
|
|
)
|
|
}
|
|
|
|
return reqEnvelope, Error{}
|
|
}
|
|
|
|
// encodeRequest writes a successful response to an io.Writer.
|
|
func encodeResponse(enc *json.Encoder, id string, res any) error {
|
|
resEnvelope := response[any]{
|
|
Version: version,
|
|
ID: id,
|
|
Result: res,
|
|
}
|
|
|
|
if err := enc.Encode(resEnvelope); err != nil {
|
|
return fmt.Errorf("encoding as JSON: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// encodeErrorRequest writes an error response to an io.Writer.
|
|
func encodeErrorResponse(enc *json.Encoder, id string, err Error) error {
|
|
resEnvelope := response[any]{
|
|
Version: version,
|
|
ID: id,
|
|
Error: &err,
|
|
}
|
|
|
|
if err := enc.Encode(resEnvelope); err != nil {
|
|
return fmt.Errorf("encoding as JSON: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// decodeResponse decodes a response from an io.Reader, unmarshaling the result
|
|
// field into the given pointer receiver. If the pointer receiver is nil then
|
|
// the result is discarded. If an error response is decoded then an Error struct
|
|
// is returned. The ID of the response is returned in both cases.
|
|
func decodeResponse(dec *json.Decoder, rcv any) (string, error) {
|
|
var rcvEnvelope response[json.RawMessage]
|
|
err := dec.Decode(&rcvEnvelope)
|
|
switch {
|
|
case err != nil:
|
|
return "", fmt.Errorf("decoding response as JSON: %w", err)
|
|
case rcvEnvelope.Version != version:
|
|
return "", fmt.Errorf(
|
|
"unknown JSONRPC version: %q", rcvEnvelope.Version,
|
|
)
|
|
case rcvEnvelope.Error != nil:
|
|
return rcvEnvelope.ID, *rcvEnvelope.Error
|
|
}
|
|
|
|
if rcv != nil {
|
|
if err := json.Unmarshal(rcvEnvelope.Result, rcv); err != nil {
|
|
return rcvEnvelope.ID, fmt.Errorf(
|
|
"decoding result into receiver of type %T: %w", rcv, err,
|
|
)
|
|
}
|
|
}
|
|
|
|
return rcvEnvelope.ID, nil
|
|
}
|