good morning!!!!

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

import (
a's avatar
a committed
	"encoding/json"
a's avatar
a committed
	"strconv"
a's avatar
ok  
a committed

a's avatar
a committed
	"github.com/go-faster/jx"
a's avatar
a committed
	gojson "github.com/goccy/go-json"
a's avatar
rpc
a committed
)

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

a's avatar
a committed
func NewNull() json.RawMessage {
	return 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 (m *Message) MarshalJSON() ([]byte, error) {
	var enc jx.Encoder
	// use encoder
	enc.Obj(func(e *jx.Encoder) {
		e.Field("jsonrpc", func(e *jx.Encoder) {
			e.Str("2.0")
		})
		if m.ID != nil {
			e.Field("id", func(e *jx.Encoder) {
				e.Raw(m.ID.RawMessage())
			})
		}
Garet Halliday's avatar
Garet Halliday committed
		if m.Method != "" {
			e.Field("method", func(e *jx.Encoder) {
				e.Str(m.Method)
			})
		}
a's avatar
a committed
		if m.Error != nil {
a's avatar
a committed
			e.Field("error", func(e *jx.Encoder) {
				xs, _ := json.Marshal(m.Error)
				e.Raw(xs)
			})
		}
		if len(m.Params) != 0 {
			e.Field("params", func(e *jx.Encoder) {
				e.Raw(m.Params)
			})
		}
		if len(m.Result) != 0 {
			e.Field("result", func(e *jx.Encoder) {
				e.Raw(m.Result)
			})
		}
	})
	// output
	return enc.Bytes(), nil
}

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) String() string {
	b, _ := json.Marshal(msg)
a's avatar
rpc
a committed
	return string(b)
}

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
// 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{{}}
a's avatar
a committed
		gojson.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
a's avatar
a committed
	// pretty sure its how we horle EOFs and stuff
	dec := jx.DecodeBytes(raw)
a's avatar
a committed
	var msgs []*Message
a's avatar
a committed
	dec.Arr(func(d *jx.Decoder) error {
		msg := new(Message)
		raw, err := d.Raw()
		if err != nil {
			return nil
		}
		err = json.Unmarshal(raw, msg)
		if err != nil {
			msg = nil
		}
		msgs = append(msgs, msg)
		return nil
	})
a's avatar
rpc
a committed
	return msgs, true
}