isle/go/daemon/jsonrpc2/jsonrpc2.go

146 lines
3.6 KiB
Go

// Package jsonrpc2 implements server/client interfaces for a JSONRPC2
// transport, as defined by https://www.jsonrpc.org/specification.
package jsonrpc2
import (
"crypto/rand"
"encoding/json"
"fmt"
"time"
)
const version = "2.0"
// 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 json.RawMessage `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(
enc *json.Encoder, method string, params any,
) (
string, error,
) {
paramsB, err := json.Marshal(params)
if err != nil {
return "", fmt.Errorf("encoding params as JSON: %w", err)
}
var (
id = newID()
reqEnvelope = Request{
Version: version,
Method: method,
Params: paramsB,
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
}