good morning!!!!

Skip to content
Snippets Groups Projects
json.go 9 KiB
Newer Older
a's avatar
rpc
a committed
package jrpc

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"reflect"
a's avatar
a committed
	"strconv"
a's avatar
a committed
	"strings"
a's avatar
rpc
a committed
	"sync"
	"time"

a's avatar
ok  
a committed
	"gfx.cafe/open/jrpc/wsjson"
	"github.com/goccy/go-json"
a's avatar
rpc
a committed
)

a's avatar
ok  
a committed
var jzon = wsjson.JZON
a's avatar
a committed

a's avatar
rpc
a committed
const (
	defaultWriteTimeout = 10 * time.Second // used if context has no deadline
)

var null = json.RawMessage("null")

// A value of this type can a JSON-RPC request, notification, successful response or
// error response. Which one it is depends on the fields.
type jsonrpcMessage struct {
a's avatar
a committed
	Version version         `json:"jsonrpc,omitempty"`
	ID      *ID             `json:"id,omitempty"`
a's avatar
rpc
a committed
	Method  string          `json:"method,omitempty"`
	Params  json.RawMessage `json:"params,omitempty"`
	Result  json.RawMessage `json:"result,omitempty"`
a's avatar
a committed

	Error *jsonError `json:"error,omitempty"`
a's avatar
rpc
a committed
}

func MakeCall(id int, method string, params []any) *JsonRpcMessage {
	return &JsonRpcMessage{
		ID: NewNumberIDPtr(int64(id)),
a's avatar
rpc
a committed
	}
}

type JsonRpcMessage = jsonrpcMessage

func (msg *jsonrpcMessage) isNotification() bool {
a's avatar
a committed
	return msg.ID == nil && len(msg.Method) > 0
a's avatar
rpc
a committed
}

func (msg *jsonrpcMessage) isCall() bool {
a's avatar
a committed
	return msg.hasValidID() && len(msg.Method) > 0
a's avatar
rpc
a committed
}

func (msg *jsonrpcMessage) isResponse() bool {
a's avatar
a committed
	return msg.hasValidID() && len(msg.Method) == 0 && msg.Params == nil && (msg.Result != nil || msg.Error != nil)
a's avatar
rpc
a committed
}

func (msg *jsonrpcMessage) hasValidID() bool {
a's avatar
a committed
	return msg.ID != nil && !msg.ID.null
a's avatar
rpc
a committed
}
a's avatar
a committed
func (msg *jsonrpcMessage) isSubscribe() bool {
	return strings.HasSuffix(msg.Method, subscribeMethodSuffix)
}

func (msg *jsonrpcMessage) isUnsubscribe() bool {
	return strings.HasSuffix(msg.Method, unsubscribeMethodSuffix)
}

func (msg *jsonrpcMessage) namespace() string {
	elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2)
	return elem[0]
}
a's avatar
rpc
a committed

func (msg *jsonrpcMessage) String() string {
	b, _ := json.Marshal(msg)
a's avatar
rpc
a committed
	return string(b)
}

func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage {
	resp := errorMessage(err)
a's avatar
a committed
	if resp.ID != nil {
		resp.ID = msg.ID
	}
a's avatar
rpc
a committed
	return resp
}

func (msg *jsonrpcMessage) response(result any) *jsonrpcMessage {
	// do a funny marshaling
	enc, err := jzon.Marshal(result)
a's avatar
rpc
a committed
	if err != nil {
		return msg.errorResponse(err)
	}
a's avatar
a committed
	if len(enc) == 0 {
		enc = []byte("null")
	}
a's avatar
a committed
	return &jsonrpcMessage{ID: msg.ID, Result: enc}
a's avatar
rpc
a committed
}

a's avatar
a committed
// encapsulate json rpc error into struct
a's avatar
rpc
a committed
type jsonError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitempty"`
}

type JsonError = jsonError

func (err *jsonError) Error() string {
	if err.Message == "" {
a's avatar
a committed
		return "json-rpc error " + strconv.Itoa(err.Code)
a's avatar
rpc
a committed
	}
	return err.Message
}

func (err *jsonError) ErrorCode() int {
	return err.Code
}

func (err *jsonError) ErrorData() any {
	return err.Data
}

a's avatar
a committed
// error message produces json rpc message with error message
func errorMessage(err error) *jsonrpcMessage {
	msg := &jsonrpcMessage{
		ID: NewNullIDPtr(),
		Error: &jsonError{
			Code:    defaultErrorCode,
			Message: err.Error(),
		}}
	ec, ok := err.(Error)
	if ok {
		msg.Error.Code = ec.ErrorCode()
	}
	de, ok := err.(DataError)
	if ok {
		msg.Error.Data = de.ErrorData()
	}
	return msg
}

a's avatar
rpc
a committed
// Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec.
type Conn interface {
	io.ReadWriteCloser
	SetWriteDeadline(time.Time) error
}

// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has
// support for parsing arguments and serializing (result) objects.
type jsonCodec struct {
a's avatar
a committed
	remote    string
	closer    sync.Once // close closed channel once
	closeFunc func() error
	closeCh   chan any          // closed on Close
	decode    func(v any) error // decoder to allow multiple transports
	encMu     sync.Mutex        // guards the encoder
	encode    func(v any) error // encoder to allow multiple transports
a's avatar
a committed
	conn      Conn
a's avatar
rpc
a committed
}

// NewFuncCodec creates a codec which uses the given functions to read and write. If conn
// implements ConnRemoteAddr, log messages will use it to include the remote address of
// the connection.
a's avatar
a committed
func NewFuncCodec(
a's avatar
a committed
	conn Conn,
a's avatar
a committed
	encode, decode func(v any) error,
	closeFunc func() error,
) ServerCodec {
a's avatar
rpc
a committed
	codec := &jsonCodec{
a's avatar
a committed
		closeFunc: closeFunc,
		closeCh:   make(chan any),
		encode:    encode,
		decode:    decode,
		conn:      conn,
a's avatar
rpc
a committed
	}
a's avatar
a committed
	if ra, ok := conn.(interface{ RemoteAddr() string }); ok {
a's avatar
rpc
a committed
		codec.remote = ra.RemoteAddr()
	}
	return codec
}

// NewCodec creates a codec on the given connection. If conn implements ConnRemoteAddr, log
// messages will use it to include the remote address of the connection.
func NewCodec(conn Conn) ServerCodec {
	encr := func(v any) error {
a's avatar
a committed
		enc := jzon.BorrowStream(conn)
		defer jzon.ReturnStream(enc)
		enc.WriteVal(v)
		enc.WriteRaw("\n")
		enc.Flush()
		if enc.Error != nil {
			return enc.Error
		}
		return nil
	}
	// TODO:
	// for some reason other json decoders are incompatible with our test suite
	// pretty sure its how we handle EOFs and stuff
	dec := stdjson.NewDecoder(conn)
	dec.UseNumber()
	return NewFuncCodec(conn, encr, dec.Decode, func() error {
a's avatar
a committed
		return nil
	})
a's avatar
rpc
a committed
}

a's avatar
a committed
func (c *jsonCodec) PeerInfo() PeerInfo {
a's avatar
rpc
a committed
	// This returns "ipc" because all other built-in transports have a separate codec type.
	return PeerInfo{Transport: "ipc", RemoteAddr: c.remote}
}

a's avatar
a committed
func (c *jsonCodec) RemoteAddr() string {
a's avatar
rpc
a committed
	return c.remote
}

a's avatar
a committed
func (c *jsonCodec) ReadBatch() (messages []*jsonrpcMessage, batch bool, err error) {
a's avatar
rpc
a committed
	// Decode the next JSON object in the input stream.
	// This verifies basic syntax, etc.
	var rawmsg json.RawMessage
	if err := c.decode(&rawmsg); err != nil {
		return nil, false, err
	}
	messages, batch = parseMessage(rawmsg)
	for i, msg := range messages {
		if msg == nil {
			// Message is JSON 'null'. Replace with zero value so it
			// will be treated like any other invalid message.
			messages[i] = new(jsonrpcMessage)
		}
	}
	return messages, batch, nil
}

a's avatar
a committed
func (c *jsonCodec) WriteJSON(ctx context.Context, v any) error {
a's avatar
rpc
a committed
	c.encMu.Lock()
	defer c.encMu.Unlock()

	deadline, ok := ctx.Deadline()
	if !ok {
		deadline = time.Now().Add(defaultWriteTimeout)
	}
	c.conn.SetWriteDeadline(deadline)
	return c.encode(v)
}

a's avatar
a committed
func (c *jsonCodec) Close() error {
a's avatar
rpc
a committed
	c.closer.Do(func() {
		close(c.closeCh)
a's avatar
a committed
		if c.closeFunc != nil {
			c.closeFunc()
		}
a's avatar
rpc
a committed
		c.conn.Close()
	})
a's avatar
a committed
	return nil
a's avatar
rpc
a committed
}

// Closed returns a channel which will be closed when Close is called
a's avatar
a committed
func (c *jsonCodec) Closed() <-chan any {
a's avatar
rpc
a committed
	return c.closeCh
}

// parseMessage parses raw bytes as a (batch of) JSON-RPC message(s). There are no error
// checks in this function because the raw message has already been syntax-checked when it
// is called. Any non-JSON-RPC messages in the input return the zero value of
// jsonrpcMessage.
func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) {
	if !isBatch(raw) {
		msgs := []*jsonrpcMessage{{}}
a's avatar
a committed
		json.Unmarshal(raw, &msgs[0])
a's avatar
rpc
a committed
		return msgs, false
	}
	// TODO:
	// for some reason other json decoders are incompatible with our test suite
	// pretty sure its how we handle EOFs and stuff
	dec := stdjson.NewDecoder(bytes.NewReader(raw))
a's avatar
rpc
a committed
	dec.Token() // skip '['
	var msgs []*jsonrpcMessage
	for dec.More() {
		msgs = append(msgs, new(jsonrpcMessage))
		dec.Decode(&msgs[len(msgs)-1])
	}
	return msgs, true
}

// isBatch returns true when the first non-whitespace characters is '['
func isBatch(raw json.RawMessage) bool {
	for _, c := range raw {
		// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
a's avatar
a committed
		switch c {
		case 0x20, 0x09, 0x0a, 0x0d:
a's avatar
rpc
a committed
			continue
		}
		return c == '['
	}
	return false
}

// parsePositionalArguments tries to parse the given args to an array of values with the
// given types. It returns the parsed values or an error when the args could not be
// parsed. Missing optional arguments are returned as reflect.Zero values.
func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) {
	var args []reflect.Value
	switch {
a's avatar
a committed
	case len(rawArgs) == 0:
	case rawArgs[0] == '[':
a's avatar
rpc
a committed
		// Read argument array.
a's avatar
a committed
		var err error
		if args, err = parseArgumentArray(rawArgs, types); err != nil {
a's avatar
rpc
a committed
			return nil, err
		}
a's avatar
a committed
	case string(rawArgs) == "null":
		return nil, nil
a's avatar
rpc
a committed
	default:
		return nil, errors.New("non-array args")
	}
	// Set any missing args to nil.
	for i := len(args); i < len(types); i++ {
		if types[i].Kind() != reflect.Ptr {
			return nil, fmt.Errorf("missing value for required argument %d", i)
		}
		args = append(args, reflect.Zero(types[i]))
	}
	return args, nil
}

a's avatar
a committed
func parseArgumentArray(p json.RawMessage, types []reflect.Type) ([]reflect.Value, error) {
	dec := jzon.BorrowIterator(p)
	defer jzon.ReturnIterator(dec)
a's avatar
rpc
a committed
	args := make([]reflect.Value, 0, len(types))
a's avatar
a committed
	for i := 0; dec.ReadArray(); i++ {
a's avatar
rpc
a committed
		if i >= len(types) {
			return args, fmt.Errorf("too many arguments, want at most %d", len(types))
		}
		argval := reflect.New(types[i])
a's avatar
a committed
		dec.ReadVal(argval.Interface())
		if err := dec.Error; err != nil {
a's avatar
rpc
a committed
			return args, fmt.Errorf("invalid argument %d: %v", i, err)
		}
		if argval.IsNil() && types[i].Kind() != reflect.Ptr {
			return args, fmt.Errorf("missing value for required argument %d", i)
		}
		args = append(args, argval.Elem())
	}
a's avatar
a committed
	return args, nil
a's avatar
rpc
a committed
}