104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
// 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]
|
|
}
|