104 lines
2.9 KiB
Go
Raw Permalink Normal View History

2022-02-06 07:06:32 +00:00
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
package handler
import (
"bytes"
"encoding/json"
"fmt"
)
// Args is a wrapper that decodes an array of positional parameters into
// concrete locations.
//
// Unmarshaling a JSON value into an Args value v succeeds if the JSON encodes
// an array with length len(v), and unmarshaling each subvalue i into the
// corresponding v[i] succeeds. As a special case, if v[i] == nil the
// corresponding value is discarded.
//
// Marshaling an Args value v into JSON succeeds if each element of the slice
// is JSON marshalable, and yields a JSON array of length len(v) containing the
// JSON values corresponding to the elements of v.
//
// Usage example:
//
// func Handler(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
// var x, y int
// var s string
//
// if err := req.UnmarshalParams(&handler.Args{&x, &y, &s}); err != nil {
// return nil, err
// }
// // do useful work with x, y, and s
// }
//
type Args []interface{}
// UnmarshalJSON supports JSON unmarshaling for a.
func (a Args) UnmarshalJSON(data []byte) error {
var elts []json.RawMessage
if err := json.Unmarshal(data, &elts); err != nil {
return filterJSONError("args", "array", err)
} else if len(elts) != len(a) {
return fmt.Errorf("wrong number of args (got %d, want %d)", len(elts), len(a))
}
for i, elt := range elts {
if a[i] == nil {
continue
} else if err := json.Unmarshal(elt, a[i]); err != nil {
return fmt.Errorf("decoding argument %d: %w", i+1, err)
}
}
return nil
}
// MarshalJSON supports JSON marshaling for a.
func (a Args) MarshalJSON() ([]byte, error) {
if len(a) == 0 {
return []byte(`[]`), nil
}
return json.Marshal([]interface{}(a))
}
// Obj is a wrapper that maps object fields into concrete locations.
//
// Unmarshaling a JSON text into an Obj value v succeeds if the JSON encodes an
// object, and unmarshaling the value for each key k of the object into v[k]
// succeeds. If k does not exist in v, it is ignored.
//
// Marshaling an Obj into JSON works as for an ordinary map.
type Obj map[string]interface{}
// UnmarshalJSON supports JSON unmarshaling into o.
func (o Obj) UnmarshalJSON(data []byte) error {
var base map[string]json.RawMessage
if err := json.Unmarshal(data, &base); err != nil {
return filterJSONError("decoding", "object", err)
}
for key, arg := range o {
val, ok := base[key]
if !ok {
continue
} else if err := json.Unmarshal(val, arg); err != nil {
return fmt.Errorf("decoding %q: %v", key, err)
}
}
return nil
}
func filterJSONError(tag, want string, err error) error {
if t, ok := err.(*json.UnmarshalTypeError); ok {
return fmt.Errorf("%s: cannot decode %s as %s", tag, t.Value, want)
}
return err
}
// 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]
}