jsonapi/request.go

382 lines
8.9 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"
)
2016-01-05 16:13:24 -05:00
const unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
var (
ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps")
ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type")
2016-01-05 16:13:24 -05:00
)
2015-07-13 12:48:26 -04:00
// Convert an io into a struct instance using jsonapi tags on struct fields.
// Method supports single request payloads only, at the moment. Bulk creates and updates
// are not supported yet.
//
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
//
2015-07-13 18:04:21 -04:00
// w.WriteHeader(201)
// w.Header().Set("Content-Type", "application/vnd.api+json")
2015-07-13 12:48:26 -04:00
//
2015-07-13 18:04:21 -04:00
// if err := jsonapi.MarshalOnePayload(w, blog); err != nil {
// http.Error(w, err.Error(), 500)
// }
// }
2015-07-13 12:48:26 -04:00
//
//
// Visit https://github.com/shwoodard/jsonapi#create for more info.
//
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 {
key := fmt.Sprintf("%s,%s", included.Type, included.Id)
includedMap[key] = included
}
return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap)
} else {
return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
}
2015-07-07 18:46:20 -04:00
}
2015-10-27 12:29:56 -04:00
func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) {
payload := new(ManyPayload)
if err := json.NewDecoder(in).Decode(payload); err != nil {
return nil, err
}
if payload.Included != nil {
includedMap := make(map[string]*Node)
for _, included := range payload.Included {
key := fmt.Sprintf("%s,%s", included.Type, included.Id)
includedMap[key] = included
}
var models []interface{}
for _, data := range payload.Data {
model := reflect.New(t.Elem())
err := unmarshalNode(data, model, &includedMap)
2015-10-27 12:29:56 -04:00
if err != nil {
return nil, err
}
models = append(models, model.Interface())
}
return models, nil
} else {
var models []interface{}
for _, data := range payload.Data {
model := reflect.New(t.Elem())
err := unmarshalNode(data, model, nil)
if err != nil {
return nil, err
}
models = append(models, model.Interface())
}
return models, nil
}
}
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()
modelType := model.Type().Elem()
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]
2016-01-05 16:13:24 -05:00
if (annotation == "client-id" && len(args) != 1) || (annotation != "client-id" && len(args) < 2) {
er = ErrBadJSONAPIStructTag
break
2015-09-10 18:55:51 -04:00
}
2015-09-10 18:55:51 -04:00
if annotation == "primary" {
if data.Id == "" {
2016-01-05 16:13:24 -05:00
continue
2015-09-10 18:55:51 -04:00
}
2015-09-10 18:55:51 -04:00
if data.Type != args[1] {
2016-04-08 20:34:02 -04:00
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
2015-09-10 18:55:51 -04:00
if fieldValue.Kind() == reflect.String {
fieldValue.Set(reflect.ValueOf(data.Id))
} else if fieldValue.Kind() == reflect.Int {
id, err := strconv.Atoi(data.Id)
if err != nil {
er = err
2016-01-05 16:13:24 -05:00
break
}
2015-09-10 18:55:51 -04:00
fieldValue.SetInt(int64(id))
} else {
2016-01-05 16:13:24 -05:00
er = ErrBadJSONAPIID
break
2015-09-10 18:55:51 -04:00
}
} else if annotation == "client-id" {
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
2015-09-10 18:55:51 -04:00
fieldValue.Set(reflect.ValueOf(data.ClientId))
} else if annotation == "attr" {
attributes := data.Attributes
2016-01-05 16:13:24 -05:00
if attributes == nil || len(data.Attributes) == 0 {
continue
2015-09-10 18:55:51 -04:00
}
2015-09-10 18:55:51 -04:00
val := 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
2015-09-10 18:55:51 -04:00
if val == 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
v := reflect.ValueOf(val)
2015-09-10 18:55:51 -04:00
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
var at int64
2015-09-10 18:55:51 -04:00
if v.Kind() == reflect.Float64 {
at = int64(v.Interface().(float64))
} else if v.Kind() == reflect.Int {
at = v.Int()
} else {
2016-01-05 16:13:24 -05:00
er = ErrInvalidTime
break
}
2015-09-10 18:55:51 -04:00
t := time.Unix(at, 0)
2015-07-07 18:46:20 -04:00
2015-09-10 18:55:51 -04:00
fieldValue.Set(reflect.ValueOf(t))
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
if fieldValue.Type() == reflect.TypeOf([]string(nil)) {
values := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
values[i] = v.Index(i).Interface().(string)
}
fieldValue.Set(reflect.ValueOf(values))
2016-01-05 16:13:24 -05:00
continue
}
if fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
var at int64
if v.Kind() == reflect.Float64 {
at = int64(v.Interface().(float64))
} else if v.Kind() == reflect.Int {
at = v.Int()
} else {
2016-01-05 16:13:24 -05:00
er = ErrInvalidTime
break
}
v := time.Unix(at, 0)
t := &v
fieldValue.Set(reflect.ValueOf(t))
2016-01-05 16:13:24 -05:00
continue
}
if v.Kind() == reflect.Float64 {
// Handle JSON numeric case
floatValue := v.Interface().(float64)
// The field may or may not be a pointer to a numeric; the kind var
// will not contain a pointer type
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Type.Elem().Kind()
} else {
kind = fieldType.Type.Kind()
}
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 := float64(floatValue)
numericValue = reflect.ValueOf(&n)
default:
er = ErrUnknownFieldNumberType
break
}
if fieldValue.Kind() == reflect.Ptr {
fieldValue.Set(numericValue)
} else {
fieldValue.Set(reflect.Indirect(numericValue))
}
continue
2015-09-10 18:55:51 -04:00
}
fieldValue.Set(reflect.ValueOf(val))
2015-09-10 18:55:51 -04:00
} else if annotation == "relation" {
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 {
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 {
relationship := new(RelationshipOneNode)
buf := bytes.NewBuffer(nil)
json.NewEncoder(buf).Encode(data.Relationships[args[1]])
json.NewDecoder(buf).Decode(relationship)
2015-09-10 18:55:51 -04:00
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 {
2016-01-05 16:13:24 -05:00
er = fmt.Errorf(unsuportedStructTagMsg, annotation)
}
2016-01-05 16:13:24 -05:00
}
if er != nil {
return er
}
return nil
}
2015-07-07 18:46:20 -04:00
func fullNode(n *Node, included *map[string]*Node) *Node {
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
}