jsonapi/request.go

657 lines
17 KiB
Go
Raw Normal View History

package jsonapi
import (
"bytes"
"encoding/json"
"errors"
2015-07-07 18:46:20 -04:00
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
const (
2018-09-28 17:57:05 -04:00
unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
)
2016-01-05 16:13:24 -05:00
var (
// ErrInvalidTime is returned when a struct has a time.Time type field, but
// the JSON value was not a unix timestamp integer.
ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps")
// ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes
// "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string.
ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps")
// ErrInvalidRFC3339 is returned when a struct has a time.Time type field and includes
// "rfc3339" in the tag spec, but the JSON value was not an RFC3339 timestamp string.
ErrInvalidRFC3339 = errors.New("Only strings can be parsed as dates, RFC3339 timestamps")
// ErrUnknownFieldNumberType is returned when the JSON value was a float
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
// float, etc)
ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type")
// ErrInvalidType is returned when the given type is incompatible with the expected type.
ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation.
2016-01-05 16:13:24 -05:00
)
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
// the JSON value was of a different type
type ErrUnsupportedPtrType struct {
rf reflect.Value
t reflect.Type
structField reflect.StructField
}
func (eupt ErrUnsupportedPtrType) Error() string {
typeName := eupt.t.Elem().Name()
kind := eupt.t.Elem().Kind()
if kind.String() != "" && kind.String() != typeName {
typeName = fmt.Sprintf("%s (%s)", typeName, kind.String())
}
return fmt.Sprintf(
"jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`",
eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName,
)
}
func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error {
return ErrUnsupportedPtrType{rf, t, structField}
}
// UnmarshalPayload converts an io into a struct instance using jsonapi tags on
// struct fields. This method supports single request payloads only, at the
// moment. Bulk creates and updates are not supported yet.
2015-07-13 12:48:26 -04:00
//
2015-07-13 14:23:03 -04:00
// Will Unmarshal embedded and sideloaded payloads. The latter is only possible if the
// object graph is complete. That is, in the "relationships" data there are type and id,
// keys that correspond to records in the "included" array.
//
2015-07-13 12:48:26 -04:00
// For example you could pass it, in, req.Body and, model, a BlogPost
// struct instance to populate in an http handler,
//
2015-07-13 18:04:21 -04:00
// func CreateBlog(w http.ResponseWriter, r *http.Request) {
// blog := new(Blog)
2015-07-13 12:48:26 -04:00
//
2015-07-13 18:04:21 -04:00
// if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
// http.Error(w, err.Error(), 500)
// return
// }
2015-07-13 12:48:26 -04:00
//
2015-07-13 18:04:21 -04:00
// // ...do stuff with your blog...
2015-07-13 12:48:26 -04:00
//
// w.Header().Set("Content-Type", jsonapi.MediaType)
// w.WriteHeader(201)
2015-07-13 12:48:26 -04:00
//
// if err := jsonapi.MarshalPayload(w, blog); err != nil {
2015-07-13 18:04:21 -04:00
// http.Error(w, err.Error(), 500)
// }
// }
2015-07-13 12:48:26 -04:00
//
//
// Visit https://github.com/google/jsonapi#create for more info.
2015-07-13 12:48:26 -04:00
//
2015-07-13 14:23:03 -04:00
// model interface{} should be a pointer to a struct.
func UnmarshalPayload(in io.Reader, model interface{}) error {
payload := new(OnePayload)
if err := json.NewDecoder(in).Decode(payload); err != nil {
return err
}
if payload.Included != nil {
includedMap := make(map[string]*Node)
for _, included := range payload.Included {
2016-07-05 21:32:15 -04:00
key := fmt.Sprintf("%s,%s", included.Type, included.ID)
includedMap[key] = included
}
return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap)
}
return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
2015-07-07 18:46:20 -04:00
}
// UnmarshalManyPayload converts an io into a set of struct instances using
// jsonapi tags on the type's struct fields.
func UnmarshalManyPayload[T any](in io.Reader) ([]T, error) {
2015-10-27 12:29:56 -04:00
payload := new(ManyPayload)
if err := json.NewDecoder(in).Decode(payload); err != nil {
return nil, err
}
models := make([]T, 0, len(payload.Data)) // will be populated from the "data"
includedMap := map[string]*Node{} // will be populate from the "included"
2015-10-27 12:29:56 -04:00
if payload.Included != nil {
for _, included := range payload.Included {
2016-07-05 21:32:15 -04:00
key := fmt.Sprintf("%s,%s", included.Type, included.ID)
2015-10-27 12:29:56 -04:00
includedMap[key] = included
}
}
2015-10-27 12:29:56 -04:00
for _, data := range payload.Data {
model := new(T)
err := unmarshalNode(data, reflect.ValueOf(model), &includedMap)
if err != nil {
return nil, err
}
models = append(models, *model)
2015-10-27 12:29:56 -04:00
}
return models, nil
2015-10-27 12:29:56 -04:00
}
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type())
}
}()
2015-07-07 18:46:20 -04:00
modelValue := model.Elem()
2018-09-28 17:57:05 -04:00
modelType := modelValue.Type()
var er error
2015-07-06 16:41:38 -04:00
2016-01-05 16:13:24 -05:00
for i := 0; i < modelValue.NumField(); i++ {
2015-07-07 18:46:20 -04:00
fieldType := modelType.Field(i)
tag := fieldType.Tag.Get("jsonapi")
if tag == "" {
2016-01-05 16:13:24 -05:00
continue
}
fieldValue := modelValue.Field(i)
args := strings.Split(tag, ",")
2015-09-10 18:55:51 -04:00
if len(args) < 1 {
2016-01-05 16:13:24 -05:00
er = ErrBadJSONAPIStructTag
break
}
2015-09-10 18:55:51 -04:00
annotation := args[0]
if (annotation == annotationClientID && len(args) != 1) ||
(annotation != annotationClientID && len(args) < 2) {
2016-01-05 16:13:24 -05:00
er = ErrBadJSONAPIStructTag
break
2015-09-10 18:55:51 -04:00
}
if annotation == annotationPrimary {
// Check the JSON API Type
2015-09-10 18:55:51 -04:00
if data.Type != args[1] {
er = fmt.Errorf(
"Trying to Unmarshal an object of type %#v, but %#v does not match",
data.Type,
args[1],
)
2016-01-05 16:13:24 -05:00
break
2015-09-10 18:55:51 -04:00
}
2015-07-09 15:27:03 -04:00
if data.ID == "" {
continue
}
// ID will have to be transmitted as astring per the JSON API spec
v := reflect.ValueOf(data.ID)
// Deal with PTRS
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Type.Elem().Kind()
2015-09-10 18:55:51 -04:00
} else {
kind = fieldType.Type.Kind()
}
// Handle String case
if kind == reflect.String {
assign(fieldValue, v)
continue
}
// Value was not a string... only other supported type was a numeric,
// which would have been sent as a float value.
floatValue, err := strconv.ParseFloat(data.ID, 64)
if err != nil {
// Could not convert the value in the "id" attr to a float
2016-01-05 16:13:24 -05:00
er = ErrBadJSONAPIID
break
2015-09-10 18:55:51 -04:00
}
// Convert the numeric float to one of the supported ID numeric types
// (int[8,16,32,64] or uint[8,16,32,64])
2018-09-28 17:57:05 -04:00
idValue, err := handleNumeric(floatValue, fieldType.Type, fieldValue)
if err != nil {
// We had a JSON float (numeric), but our field was not one of the
// allowed numeric types
er = ErrBadJSONAPIID
break
}
assign(fieldValue, idValue)
} else if annotation == annotationClientID {
2016-07-05 21:32:15 -04:00
if data.ClientID == "" {
2016-01-05 16:13:24 -05:00
continue
2015-09-10 18:55:51 -04:00
}
2015-07-09 15:27:03 -04:00
2016-07-05 21:32:15 -04:00
fieldValue.Set(reflect.ValueOf(data.ClientID))
} else if annotation == annotationAttribute {
2015-09-10 18:55:51 -04:00
attributes := data.Attributes
2017-07-10 16:50:43 -04:00
2016-01-05 16:13:24 -05:00
if attributes == nil || len(data.Attributes) == 0 {
continue
2015-09-10 18:55:51 -04:00
}
2017-07-10 16:09:01 -04:00
attribute := attributes[args[1]]
2015-07-09 15:27:03 -04:00
2016-01-05 16:13:24 -05:00
// continue if the attribute was not included in the request
2017-07-10 16:09:01 -04:00
if attribute == nil {
2016-01-05 16:13:24 -05:00
continue
2015-09-10 18:55:51 -04:00
}
2015-07-07 18:46:20 -04:00
structField := fieldType
value, err := unmarshalAttribute(attribute, args, structField, fieldValue)
if err != nil {
er = err
break
}
assign(fieldValue, value)
} else if annotation == annotationRelation {
2015-09-10 18:55:51 -04:00
isSlice := fieldValue.Type().Kind() == reflect.Slice
2015-07-07 18:46:20 -04:00
2015-09-10 18:55:51 -04:00
if data.Relationships == nil || data.Relationships[args[1]] == nil {
2016-01-05 16:13:24 -05:00
continue
2015-09-10 18:55:51 -04:00
}
2015-07-07 18:46:20 -04:00
2015-09-10 18:55:51 -04:00
if isSlice {
// to-many relationship
relationship := new(RelationshipManyNode)
buf := bytes.NewBuffer(nil)
json.NewEncoder(buf).Encode(data.Relationships[args[1]])
json.NewDecoder(buf).Decode(relationship)
data := relationship.Data
2015-09-10 18:55:51 -04:00
models := reflect.New(fieldValue.Type()).Elem()
2015-07-10 11:30:59 -04:00
for _, n := range data {
2015-09-10 18:55:51 -04:00
m := reflect.New(fieldValue.Type().Elem().Elem())
2015-07-07 18:46:20 -04:00
if err := unmarshalNode(
fullNode(n, included),
m,
included,
); err != nil {
2015-07-07 18:46:20 -04:00
er = err
2016-01-05 16:13:24 -05:00
break
2015-07-07 18:46:20 -04:00
}
2015-09-10 18:55:51 -04:00
models = reflect.Append(models, m)
2015-07-07 18:46:20 -04:00
}
2015-09-10 18:55:51 -04:00
fieldValue.Set(models)
2015-07-07 18:46:20 -04:00
} else {
// to-one relationships
relationship := new(RelationshipOneNode)
buf := bytes.NewBuffer(nil)
json.NewEncoder(buf).Encode(
data.Relationships[args[1]],
)
json.NewDecoder(buf).Decode(relationship)
/*
http://jsonapi.org/format/#document-resource-object-relationships
http://jsonapi.org/format/#document-resource-object-linkage
relationship can have a data node set to null (e.g. to disassociate the relationship)
so unmarshal and set fieldValue only if data obj is not null
*/
if relationship.Data == nil {
continue
}
m := reflect.New(fieldValue.Type().Elem())
if err := unmarshalNode(
fullNode(relationship.Data, included),
m,
included,
); err != nil {
2015-09-10 18:55:51 -04:00
er = err
2016-01-05 16:13:24 -05:00
break
2015-09-10 18:55:51 -04:00
}
fieldValue.Set(m)
}
2015-09-10 18:55:51 -04:00
} else {
2018-09-28 17:57:05 -04:00
er = fmt.Errorf(unsupportedStructTagMsg, annotation)
}
2016-01-05 16:13:24 -05:00
}
return er
}
2015-07-07 18:46:20 -04:00
func fullNode(n *Node, included *map[string]*Node) *Node {
2016-07-05 21:32:15 -04:00
includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID)
2015-07-07 18:46:20 -04:00
if included != nil && (*included)[includedKey] != nil {
return (*included)[includedKey]
2015-07-07 18:46:20 -04:00
}
return n
2015-07-07 18:46:20 -04:00
}
// assign will take the value specified and assign it to the field; if
// field is expecting a ptr assign will assign a ptr.
func assign(field, value reflect.Value) {
value = reflect.Indirect(value)
if field.Kind() == reflect.Ptr {
// initialize pointer so it's value
// can be set by assignValue
field.Set(reflect.New(field.Type().Elem()))
field = field.Elem()
}
assignValue(field, value)
}
// assign assigns the specified value to the field,
// expecting both values not to be pointer types.
func assignValue(field, value reflect.Value) {
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
field.SetInt(value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
field.SetUint(value.Uint())
case reflect.Float32, reflect.Float64:
field.SetFloat(value.Float())
case reflect.String:
field.SetString(value.String())
case reflect.Bool:
field.SetBool(value.Bool())
default:
field.Set(value)
}
}
2017-07-10 16:09:01 -04:00
func unmarshalAttribute(
attribute interface{},
args []string,
structField reflect.StructField,
fieldValue reflect.Value) (value reflect.Value, err error) {
2017-07-15 09:22:03 -04:00
value = reflect.ValueOf(attribute)
fieldType := structField.Type
// Handle field of type []string
2017-07-15 09:22:03 -04:00
if fieldValue.Type() == reflect.TypeOf([]string{}) {
2018-09-28 17:57:05 -04:00
value, err = handleStringSlice(attribute)
return
}
// Handle field of type time.Time
if fieldValue.Type() == reflect.TypeOf(time.Time{}) ||
fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
2018-09-28 17:57:05 -04:00
value, err = handleTime(attribute, args, fieldValue)
2017-07-15 09:22:03 -04:00
return
}
// Handle field of type struct
if fieldValue.Type().Kind() == reflect.Struct {
2018-09-28 17:57:05 -04:00
value, err = handleStruct(attribute, fieldValue)
2017-07-15 09:22:03 -04:00
return
}
// Handle field containing slice of structs
if fieldValue.Type().Kind() == reflect.Slice &&
reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct {
2018-09-28 17:57:05 -04:00
value, err = handleStructSlice(attribute, fieldValue)
return
}
// JSON value was a float (numeric)
if value.Kind() == reflect.Float64 {
2018-09-28 17:57:05 -04:00
value, err = handleNumeric(attribute, fieldType, fieldValue)
return
}
// Field was a Pointer type
2017-07-15 09:22:03 -04:00
if fieldValue.Kind() == reflect.Ptr {
value, err = handlePointer(attribute, args, fieldType, fieldValue, structField)
return
}
// As a final catch-all, ensure types line up to avoid a runtime panic.
2017-07-15 09:22:03 -04:00
if fieldValue.Kind() != value.Kind() {
err = ErrInvalidType
return
}
return
}
2018-09-28 17:57:05 -04:00
func handleStringSlice(attribute interface{}) (reflect.Value, error) {
2017-07-15 09:22:03 -04:00
v := reflect.ValueOf(attribute)
2017-07-10 16:09:01 -04:00
values := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
values[i] = v.Index(i).Interface().(string)
}
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(values), nil
2017-07-10 16:09:01 -04:00
}
2018-09-28 17:57:05 -04:00
func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
var isISO8601, isRFC3339 bool
2017-07-15 09:22:03 -04:00
v := reflect.ValueOf(attribute)
2017-07-10 16:50:43 -04:00
2017-07-15 09:22:03 -04:00
if len(args) > 2 {
for _, arg := range args[2:] {
2017-07-10 16:50:43 -04:00
if arg == annotationISO8601 {
isISO8601 = true
} else if arg == annotationRFC3339 {
isRFC3339 = true
2017-07-10 16:50:43 -04:00
}
}
}
if isISO8601 {
if v.Kind() != reflect.String {
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
2017-07-10 16:09:01 -04:00
}
t, err := time.Parse(iso8601TimeFormat, v.Interface().(string))
2017-07-10 16:09:01 -04:00
if err != nil {
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
}
2017-07-15 09:22:03 -04:00
if fieldValue.Kind() == reflect.Ptr {
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(&t), nil
2017-07-10 16:09:01 -04:00
}
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(t), nil
2017-07-10 16:09:01 -04:00
}
if isRFC3339 {
if v.Kind() != reflect.String {
return reflect.ValueOf(time.Now()), ErrInvalidRFC3339
}
t, err := time.Parse(time.RFC3339, v.Interface().(string))
if err != nil {
return reflect.ValueOf(time.Now()), ErrInvalidRFC3339
}
if fieldValue.Kind() == reflect.Ptr {
return reflect.ValueOf(&t), nil
}
return reflect.ValueOf(t), nil
}
2017-07-10 16:09:01 -04:00
var at int64
if v.Kind() == reflect.Float64 {
at = int64(v.Interface().(float64))
} else if v.Kind() == reflect.Int {
at = v.Int()
} else {
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(time.Now()), ErrInvalidTime
2017-07-10 16:09:01 -04:00
}
t := time.Unix(at, 0)
2017-07-10 16:50:43 -04:00
return reflect.ValueOf(t), nil
2017-07-10 16:09:01 -04:00
}
func handleNumeric(
attribute interface{},
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
2017-07-15 09:22:03 -04:00
v := reflect.ValueOf(attribute)
2017-07-10 16:09:01 -04:00
floatValue := v.Interface().(float64)
2017-07-10 16:50:43 -04:00
var kind reflect.Kind
2017-07-15 09:22:03 -04:00
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Elem().Kind()
2017-07-10 16:50:43 -04:00
} else {
2017-07-15 09:22:03 -04:00
kind = fieldType.Kind()
2017-07-10 16:50:43 -04:00
}
2017-07-10 16:09:01 -04:00
var numericValue reflect.Value
switch kind {
case reflect.Int:
n := int(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int8:
n := int8(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int16:
n := int16(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int32:
n := int32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int64:
n := int64(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint:
n := uint(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint8:
n := uint8(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint16:
n := uint16(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint32:
n := uint32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint64:
n := uint64(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Float32:
n := float32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Float64:
n := floatValue
numericValue = reflect.ValueOf(&n)
default:
return reflect.Value{}, ErrUnknownFieldNumberType
}
return numericValue, nil
}
func handlePointer(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value,
structField reflect.StructField) (reflect.Value, error) {
2017-07-15 09:22:03 -04:00
t := fieldValue.Type()
2017-07-10 16:09:01 -04:00
var concreteVal reflect.Value
2017-07-15 09:22:03 -04:00
switch cVal := attribute.(type) {
2017-07-10 16:09:01 -04:00
case string:
concreteVal = reflect.ValueOf(&cVal)
case bool:
concreteVal = reflect.ValueOf(&cVal)
case complex64, complex128, uintptr:
2017-07-10 16:09:01 -04:00
concreteVal = reflect.ValueOf(&cVal)
2018-01-17 14:40:21 -05:00
case map[string]interface{}:
var err error
2018-09-28 17:57:05 -04:00
concreteVal, err = handleStruct(attribute, fieldValue)
2018-01-25 10:28:20 -05:00
if err != nil {
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
2018-01-25 10:28:20 -05:00
}
2018-09-28 17:57:05 -04:00
return concreteVal, err
2017-07-10 16:09:01 -04:00
default:
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
2017-07-10 16:09:01 -04:00
}
if t != concreteVal.Type() {
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
2017-07-10 16:09:01 -04:00
}
return concreteVal, nil
}
2017-07-15 09:22:03 -04:00
func handleStruct(
attribute interface{},
fieldValue reflect.Value) (reflect.Value, error) {
2017-07-15 09:22:03 -04:00
data, err := json.Marshal(attribute)
if err != nil {
2018-09-28 17:57:05 -04:00
return reflect.Value{}, err
2017-08-14 14:39:14 -04:00
}
2017-07-15 09:22:03 -04:00
2018-09-28 17:57:05 -04:00
node := new(Node)
if err := json.Unmarshal(data, &node.Attributes); err != nil {
return reflect.Value{}, err
}
2017-07-15 09:22:03 -04:00
2018-09-28 17:57:05 -04:00
var model reflect.Value
if fieldValue.Kind() == reflect.Ptr {
model = reflect.New(fieldValue.Type().Elem())
} else {
model = reflect.New(fieldValue.Type())
2017-07-15 09:22:03 -04:00
}
2018-09-28 17:57:05 -04:00
if err := unmarshalNode(node, model, nil); err != nil {
return reflect.Value{}, err
}
return model, nil
2017-07-15 09:22:03 -04:00
}
func handleStructSlice(
attribute interface{},
fieldValue reflect.Value) (reflect.Value, error) {
2017-07-15 09:22:03 -04:00
models := reflect.New(fieldValue.Type()).Elem()
dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
for _, data := range dataMap {
model := reflect.New(fieldValue.Type().Elem()).Elem()
2018-09-28 17:57:05 -04:00
value, err := handleStruct(data, model)
2017-07-15 09:22:03 -04:00
if err != nil {
continue
}
models = reflect.Append(models, reflect.Indirect(value))
}
return models, nil
}