305 lines
8.4 KiB
Go
Raw Normal View History

2022-02-06 07:06:32 +00:00
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
2021-12-04 16:42:11 +00:00
package jrpc2
import (
"bytes"
"encoding/json"
"github.com/creachadair/jrpc2/code"
)
// ErrInvalidVersion is returned by ParseRequests if one or more of the
// requests in the input has a missing or invalid version marker.
var ErrInvalidVersion error = &Error{Code: code.InvalidRequest, Message: "incorrect version marker"}
// ParseRequests parses a single request or a batch of requests from JSON.
//
// If msg is syntactically valid apart from one or more of the requests having
// a missing or invalid JSON-RPC version, ParseRequests returns ErrInvalidVersion
// along with the parsed results.
func ParseRequests(msg []byte) ([]*Request, error) {
var req jmessages
if err := req.parseJSON(msg); err != nil {
return nil, err
}
var err error
out := make([]*Request, len(req))
for i, req := range req {
if req.V != Version {
err = ErrInvalidVersion
}
out[i] = &Request{
id: fixID(req.ID),
method: req.M,
params: req.P,
}
}
return out, err
}
// jmessages is either a single protocol message or an array of protocol
// messages. This handles the decoding of batch requests in JSON-RPC 2.0.
type jmessages []*jmessage
func (j jmessages) toJSON() ([]byte, error) {
if len(j) == 1 && !j[0].batch {
return j[0].toJSON()
}
var sb bytes.Buffer
sb.WriteByte('[')
for i, msg := range j {
if i > 0 {
sb.WriteByte(',')
}
bits, err := msg.toJSON()
if err != nil {
return nil, err
}
sb.Write(bits)
}
sb.WriteByte(']')
return sb.Bytes(), nil
}
// N.B. Not UnmarshalJSON, because json.Unmarshal checks for validity early and
// here we want to control the error that is returned.
func (j *jmessages) parseJSON(data []byte) error {
*j = (*j)[:0] // reset state
// When parsing requests, validation checks are deferred: The only immediate
// mode of failure for unmarshaling is if the request is not a valid object
// or array.
var msgs []json.RawMessage
var batch bool
2022-02-06 07:06:32 +00:00
if firstByte(data) != '[' {
2021-12-04 16:42:11 +00:00
msgs = append(msgs, nil)
if err := json.Unmarshal(data, &msgs[0]); err != nil {
return errInvalidRequest
}
} else if err := json.Unmarshal(data, &msgs); err != nil {
return errInvalidRequest
} else {
batch = true
}
// Now parse the individual request messages, but do not fail on errors. We
// know that the messages are intact, but validity is checked at usage.
for _, raw := range msgs {
req := new(jmessage)
req.parseJSON(raw)
req.batch = batch
*j = append(*j, req)
}
return nil
}
// jmessage is the transmission format of a protocol message.
type jmessage struct {
V string // must be Version
ID json.RawMessage // may be nil
// Fields belonging to request or notification objects
M string
P json.RawMessage // may be nil
// Fields belonging to response or error objects
E *Error // set on error
R json.RawMessage // set on success
// N.B.: In a valid protocol message, M and P are mutually exclusive with E
// and R. Specifically, if M != "" then E and R must both be unset. This is
// checked during parsing.
batch bool // this message was part of a batch
err error // if not nil, this message is invalid and err is why
}
// isValidID reports whether v is a valid JSON encoding of a request ID.
// Precondition: v is a valid JSON value, or empty.
func isValidID(v json.RawMessage) bool {
if len(v) == 0 || isNull(v) {
return true // nil or empty is OK, as is "null"
} else if v[0] == '"' || v[0] == '-' || (v[0] >= '0' && v[0] <= '9') {
return true // strings and numbers are OK
// N.B. This definition does not reject fractional numbers, although the
// spec says numeric IDs should not have fractional parts.
}
return false // anything else is garbage
}
func (j *jmessage) fail(code code.Code, msg string) {
2022-02-06 07:06:32 +00:00
j.err = &Error{Code: code, Message: msg}
2021-12-04 16:42:11 +00:00
}
func (j *jmessage) toJSON() ([]byte, error) {
var sb bytes.Buffer
sb.WriteString(`{"jsonrpc":"2.0"`)
if len(j.ID) != 0 {
sb.WriteString(`,"id":`)
sb.Write(j.ID)
}
switch {
case j.M != "":
m, err := json.Marshal(j.M)
if err != nil {
return nil, err
}
sb.WriteString(`,"method":`)
sb.Write(m)
if len(j.P) != 0 {
sb.WriteString(`,"params":`)
sb.Write(j.P)
}
case len(j.R) != 0:
sb.WriteString(`,"result":`)
sb.Write(j.R)
case j.E != nil:
e, err := json.Marshal(j.E)
if err != nil {
return nil, err
}
sb.WriteString(`,"error":`)
sb.Write(e)
}
sb.WriteByte('}')
return sb.Bytes(), nil
}
func (j *jmessage) parseJSON(data []byte) error {
// Unmarshal into a map so we can check for extra keys. The json.Decoder
// has DisallowUnknownFields, but fails decoding eagerly for fields that do
// not map to known tags. We want to fully parse the object so we can
// propagate the "id" in error responses, if it is set. So we have to decode
// and check the fields ourselves.
var obj map[string]json.RawMessage
if err := json.Unmarshal(data, &obj); err != nil {
j.fail(code.ParseError, "request is not a JSON object")
return j.err
}
*j = jmessage{} // reset content
var extra []string // extra field names
for key, val := range obj {
switch key {
case "jsonrpc":
if json.Unmarshal(val, &j.V) != nil {
j.fail(code.ParseError, "invalid version key")
}
case "id":
if isValidID(val) {
j.ID = val
} else {
j.fail(code.InvalidRequest, "invalid request ID")
}
case "method":
if json.Unmarshal(val, &j.M) != nil {
j.fail(code.ParseError, "invalid method name")
}
case "params":
// As a special case, reduce "null" to nil in the parameters.
// Otherwise, require per spec that val is an array or object.
if !isNull(val) {
j.P = val
}
2022-02-06 07:06:32 +00:00
if fb := firstByte(j.P); fb != 0 && fb != '[' && fb != '{' {
2021-12-04 16:42:11 +00:00
j.fail(code.InvalidRequest, "parameters must be array or object")
}
case "error":
if json.Unmarshal(val, &j.E) != nil {
j.fail(code.ParseError, "invalid error value")
}
case "result":
j.R = val
default:
extra = append(extra, key)
}
}
// Report an error if request/response fields overlap.
if j.M != "" && (j.E != nil || j.R != nil) {
j.fail(code.InvalidRequest, "mixed request and reply fields")
}
// Report an error for extraneous fields.
if j.err == nil && len(extra) != 0 {
j.err = Errorf(code.InvalidRequest, "extra fields in request").WithData(extra)
}
return nil
}
// isRequestOrNotification reports whether j is a request or notification.
func (j *jmessage) isRequestOrNotification() bool { return j.M != "" && j.E == nil && j.R == nil }
// isNotification reports whether j is a notification
func (j *jmessage) isNotification() bool { return j.isRequestOrNotification() && fixID(j.ID) == nil }
// fixID filters id, treating "null" as a synonym for an unset ID. This
// supports interoperation with JSON-RPC v1 where "null" is used as an ID for
// notifications.
func fixID(id json.RawMessage) json.RawMessage {
if !isNull(id) {
return id
}
return nil
}
// sender is the subset of channel.Channel needed to send messages.
type sender interface{ Send([]byte) error }
// receiver is the subset of channel.Channel needed to receive messages.
type receiver interface{ Recv() ([]byte, error) }
// encode marshals rsps as JSON and forwards it to the channel.
func encode(ch sender, rsps jmessages) (int, error) {
bits, err := rsps.toJSON()
if err != nil {
return 0, err
}
return len(bits), ch.Send(bits)
}
// isNull reports whether msg is exactly the JSON "null" value.
func isNull(msg json.RawMessage) bool {
return len(msg) == 4 && msg[0] == 'n' && msg[1] == 'u' && msg[2] == 'l' && msg[3] == 'l'
}
2022-02-06 07:06:32 +00:00
// firstByte returns the first non-whitespace byte of data, or 0 if there is none.
func firstByte(data []byte) byte {
clean := bytes.TrimSpace(data)
if len(clean) == 0 {
return 0
}
return clean[0]
}
2021-12-04 16:42:11 +00:00
// strictFielder is an optional interface that can be implemented by a type to
// reject unknown fields when unmarshaling from JSON. If a type does not
// implement this interface, unknown fields are ignored.
type strictFielder interface {
DisallowUnknownFields()
}
2022-02-06 07:06:32 +00:00
// StrictFields wraps a value v to require unknown fields to be rejected when
// unmarshaling from JSON.
2021-12-04 16:42:11 +00:00
//
// For example:
//
// var obj RequestType
// err := req.UnmarshalParams(jrpc2.StrictFields(&obj))`
//
func StrictFields(v interface{}) interface{} { return &strict{v: v} }
type strict struct{ v interface{} }
2022-02-06 07:06:32 +00:00
func (s *strict) UnmarshalJSON(data []byte) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
return dec.Decode(s.v)
}