good morning!!!!

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

import (
	"bytes"
a's avatar
a committed
	"encoding/json"
a's avatar
a committed
	"gfx.cafe/open/jrpc/pkg/codec/codecs/websocket/wsjson"
a's avatar
a committed
	"strconv"
a's avatar
rpc
a committed
)

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

a's avatar
a committed
var Null = json.RawMessage("null")
a's avatar
rpc
a committed

// A value of this type can a JSON-RPC request, notification, successful response or
// error response. Which one it is depends on the fields.
a's avatar
a committed
type Message struct {
	Version Version         `json:"jsonrpc,omitempty"`
a's avatar
a committed
	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

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

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

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

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

func (msg *Message) 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
}
a's avatar
a committed

a's avatar
a committed
func (msg *Message) hasValidID() bool {
a's avatar
a committed
	return msg.ID != nil && !msg.ID.IsNull()
a's avatar
a committed
}
a's avatar
rpc
a committed

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

a's avatar
a committed
func (msg *Message) ErrorResponse(err error) *Message {
	resp := ErrorMessage(err)
a's avatar
a committed
	if resp.ID != nil {
		resp.ID = msg.ID
	}
a's avatar
rpc
a committed
	return resp
}
a's avatar
a committed
func (msg *Message) response(result any) *Message {
a's avatar
rpc
a committed
	// do a funny marshaling
	enc, err := jzon.Marshal(result)
a's avatar
rpc
a committed
	if err != nil {
a's avatar
a committed
		return msg.ErrorResponse(err)
a's avatar
rpc
a committed
	}
a's avatar
a committed
	if len(enc) == 0 {
		enc = []byte("null")
	}
a's avatar
a committed
	return &Message{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
a committed
type JsonError struct {
a's avatar
rpc
a committed
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitempty"`
}

a's avatar
a committed
func (err *JsonError) Error() string {
a's avatar
rpc
a committed
	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
}

a's avatar
a committed
func (err *JsonError) ErrorCode() int {
a's avatar
rpc
a committed
	return err.Code
}

a's avatar
a committed
func (err *JsonError) ErrorData() any {
a's avatar
rpc
a committed
	return err.Data
}

a's avatar
a committed
// error message produces json rpc message with error message
a's avatar
a committed
func ErrorMessage(err error) *Message {
	msg := &Message{
a's avatar
a committed
		ID: NewNullIDPtr(),
a's avatar
a committed
		Error: &JsonError{
			Code:    ErrorCodeDefault,
a's avatar
a committed
			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
a committed
// isBatch returns true when the first non-whitespace characters is '['
a's avatar
a committed
func IsBatchMessage(raw json.RawMessage) bool {
a's avatar
a committed
	for _, c := range raw {
		// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
		switch c {
		case 0x20, 0x09, 0x0a, 0x0d:
			continue
a's avatar
rpc
a committed
		}
a's avatar
a committed
		return c == '['
a's avatar
rpc
a committed
	}
a's avatar
a committed
	return false
a's avatar
rpc
a committed
}

// 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
a's avatar
a committed
// Message.
func ParseMessage(raw json.RawMessage) ([]*Message, bool) {
	if !IsBatchMessage(raw) {
		msgs := []*Message{{}}
		jzon.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
a's avatar
a committed
	dec := json.NewDecoder(bytes.NewReader(raw))
a's avatar
rpc
a committed
	dec.Token() // skip '['
a's avatar
a committed
	var msgs []*Message
a's avatar
rpc
a committed
	for dec.More() {
a's avatar
a committed
		msgs = append(msgs, new(Message))
a's avatar
rpc
a committed
		dec.Decode(&msgs[len(msgs)-1])
	}
	return msgs, true
}