summaryrefslogtreecommitdiff
path: root/vendor/github.com/google/jsonapi/request.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/jsonapi/request.go')
-rw-r--r--vendor/github.com/google/jsonapi/request.go656
1 files changed, 656 insertions, 0 deletions
diff --git a/vendor/github.com/google/jsonapi/request.go b/vendor/github.com/google/jsonapi/request.go
new file mode 100644
index 0000000..f665857
--- /dev/null
+++ b/vendor/github.com/google/jsonapi/request.go
@@ -0,0 +1,656 @@
+package jsonapi
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
+)
+
+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.
+
+)
+
+// 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.
+//
+// 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.
+//
+// For example you could pass it, in, req.Body and, model, a BlogPost
+// struct instance to populate in an http handler,
+//
+// func CreateBlog(w http.ResponseWriter, r *http.Request) {
+// blog := new(Blog)
+//
+// if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
+// http.Error(w, err.Error(), 500)
+// return
+// }
+//
+// // ...do stuff with your blog...
+//
+// w.Header().Set("Content-Type", jsonapi.MediaType)
+// w.WriteHeader(201)
+//
+// if err := jsonapi.MarshalPayload(w, blog); err != nil {
+// http.Error(w, err.Error(), 500)
+// }
+// }
+//
+//
+// Visit https://github.com/google/jsonapi#create for more info.
+//
+// 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)
+ }
+ return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
+}
+
+// UnmarshalManyPayload converts an io into a set of struct instances using
+// jsonapi tags on the type's struct fields.
+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
+ }
+
+ models := []interface{}{} // will be populated from the "data"
+ includedMap := map[string]*Node{} // will be populate from the "included"
+
+ if payload.Included != nil {
+ for _, included := range payload.Included {
+ key := fmt.Sprintf("%s,%s", included.Type, included.ID)
+ includedMap[key] = included
+ }
+ }
+
+ for _, data := range payload.Data {
+ model := reflect.New(t.Elem())
+ err := unmarshalNode(data, model, &includedMap)
+ 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())
+ }
+ }()
+
+ modelValue := model.Elem()
+ modelType := modelValue.Type()
+
+ var er error
+
+ for i := 0; i < modelValue.NumField(); i++ {
+ fieldType := modelType.Field(i)
+ tag := fieldType.Tag.Get("jsonapi")
+ if tag == "" {
+ continue
+ }
+
+ fieldValue := modelValue.Field(i)
+
+ args := strings.Split(tag, ",")
+ if len(args) < 1 {
+ er = ErrBadJSONAPIStructTag
+ break
+ }
+
+ annotation := args[0]
+
+ if (annotation == annotationClientID && len(args) != 1) ||
+ (annotation != annotationClientID && len(args) < 2) {
+ er = ErrBadJSONAPIStructTag
+ break
+ }
+
+ if annotation == annotationPrimary {
+ // Check the JSON API Type
+ 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],
+ )
+ break
+ }
+
+ 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()
+ } 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
+ er = ErrBadJSONAPIID
+ break
+ }
+
+ // Convert the numeric float to one of the supported ID numeric types
+ // (int[8,16,32,64] or uint[8,16,32,64])
+ 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 {
+ if data.ClientID == "" {
+ continue
+ }
+
+ fieldValue.Set(reflect.ValueOf(data.ClientID))
+ } else if annotation == annotationAttribute {
+ attributes := data.Attributes
+
+ if attributes == nil || len(data.Attributes) == 0 {
+ continue
+ }
+
+ attribute := attributes[args[1]]
+
+ // continue if the attribute was not included in the request
+ if attribute == nil {
+ continue
+ }
+
+ structField := fieldType
+ value, err := unmarshalAttribute(attribute, args, structField, fieldValue)
+ if err != nil {
+ er = err
+ break
+ }
+
+ assign(fieldValue, value)
+ } else if annotation == annotationRelation {
+ isSlice := fieldValue.Type().Kind() == reflect.Slice
+
+ if data.Relationships == nil || data.Relationships[args[1]] == nil {
+ continue
+ }
+
+ 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
+ models := reflect.New(fieldValue.Type()).Elem()
+
+ for _, n := range data {
+ m := reflect.New(fieldValue.Type().Elem().Elem())
+
+ if err := unmarshalNode(
+ fullNode(n, included),
+ m,
+ included,
+ ); err != nil {
+ er = err
+ break
+ }
+
+ models = reflect.Append(models, m)
+ }
+
+ fieldValue.Set(models)
+ } 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 {
+ er = err
+ break
+ }
+
+ fieldValue.Set(m)
+
+ }
+
+ } else {
+ er = fmt.Errorf(unsupportedStructTagMsg, annotation)
+ }
+ }
+
+ return er
+}
+
+func fullNode(n *Node, included *map[string]*Node) *Node {
+ includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID)
+
+ if included != nil && (*included)[includedKey] != nil {
+ return (*included)[includedKey]
+ }
+
+ return n
+}
+
+// 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)
+ }
+}
+
+func unmarshalAttribute(
+ attribute interface{},
+ args []string,
+ structField reflect.StructField,
+ fieldValue reflect.Value) (value reflect.Value, err error) {
+ value = reflect.ValueOf(attribute)
+ fieldType := structField.Type
+
+ // Handle field of type []string
+ if fieldValue.Type() == reflect.TypeOf([]string{}) {
+ 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)) {
+ value, err = handleTime(attribute, args, fieldValue)
+ return
+ }
+
+ // Handle field of type struct
+ if fieldValue.Type().Kind() == reflect.Struct {
+ value, err = handleStruct(attribute, fieldValue)
+ return
+ }
+
+ // Handle field containing slice of structs
+ if fieldValue.Type().Kind() == reflect.Slice &&
+ reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct {
+ value, err = handleStructSlice(attribute, fieldValue)
+ return
+ }
+
+ // JSON value was a float (numeric)
+ if value.Kind() == reflect.Float64 {
+ value, err = handleNumeric(attribute, fieldType, fieldValue)
+ return
+ }
+
+ // Field was a Pointer type
+ 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.
+ if fieldValue.Kind() != value.Kind() {
+ err = ErrInvalidType
+ return
+ }
+
+ return
+}
+
+func handleStringSlice(attribute interface{}) (reflect.Value, error) {
+ v := reflect.ValueOf(attribute)
+ values := make([]string, v.Len())
+ for i := 0; i < v.Len(); i++ {
+ values[i] = v.Index(i).Interface().(string)
+ }
+
+ return reflect.ValueOf(values), nil
+}
+
+func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
+ var isISO8601, isRFC3339 bool
+ v := reflect.ValueOf(attribute)
+
+ if len(args) > 2 {
+ for _, arg := range args[2:] {
+ if arg == annotationISO8601 {
+ isISO8601 = true
+ } else if arg == annotationRFC3339 {
+ isRFC3339 = true
+ }
+ }
+ }
+
+ if isISO8601 {
+ if v.Kind() != reflect.String {
+ return reflect.ValueOf(time.Now()), ErrInvalidISO8601
+ }
+
+ t, err := time.Parse(iso8601TimeFormat, v.Interface().(string))
+ if err != nil {
+ return reflect.ValueOf(time.Now()), ErrInvalidISO8601
+ }
+
+ if fieldValue.Kind() == reflect.Ptr {
+ return reflect.ValueOf(&t), nil
+ }
+
+ return reflect.ValueOf(t), nil
+ }
+
+ 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
+ }
+
+ var at int64
+
+ if v.Kind() == reflect.Float64 {
+ at = int64(v.Interface().(float64))
+ } else if v.Kind() == reflect.Int {
+ at = v.Int()
+ } else {
+ return reflect.ValueOf(time.Now()), ErrInvalidTime
+ }
+
+ t := time.Unix(at, 0)
+
+ return reflect.ValueOf(t), nil
+}
+
+func handleNumeric(
+ attribute interface{},
+ fieldType reflect.Type,
+ fieldValue reflect.Value) (reflect.Value, error) {
+ v := reflect.ValueOf(attribute)
+ floatValue := v.Interface().(float64)
+
+ var kind reflect.Kind
+ if fieldValue.Kind() == reflect.Ptr {
+ kind = fieldType.Elem().Kind()
+ } else {
+ kind = fieldType.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 := 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) {
+ t := fieldValue.Type()
+ var concreteVal reflect.Value
+
+ switch cVal := attribute.(type) {
+ case string:
+ concreteVal = reflect.ValueOf(&cVal)
+ case bool:
+ concreteVal = reflect.ValueOf(&cVal)
+ case complex64, complex128, uintptr:
+ concreteVal = reflect.ValueOf(&cVal)
+ case map[string]interface{}:
+ var err error
+ concreteVal, err = handleStruct(attribute, fieldValue)
+ if err != nil {
+ return reflect.Value{}, newErrUnsupportedPtrType(
+ reflect.ValueOf(attribute), fieldType, structField)
+ }
+ return concreteVal, err
+ default:
+ return reflect.Value{}, newErrUnsupportedPtrType(
+ reflect.ValueOf(attribute), fieldType, structField)
+ }
+
+ if t != concreteVal.Type() {
+ return reflect.Value{}, newErrUnsupportedPtrType(
+ reflect.ValueOf(attribute), fieldType, structField)
+ }
+
+ return concreteVal, nil
+}
+
+func handleStruct(
+ attribute interface{},
+ fieldValue reflect.Value) (reflect.Value, error) {
+
+ data, err := json.Marshal(attribute)
+ if err != nil {
+ return reflect.Value{}, err
+ }
+
+ node := new(Node)
+ if err := json.Unmarshal(data, &node.Attributes); err != nil {
+ return reflect.Value{}, err
+ }
+
+ var model reflect.Value
+ if fieldValue.Kind() == reflect.Ptr {
+ model = reflect.New(fieldValue.Type().Elem())
+ } else {
+ model = reflect.New(fieldValue.Type())
+ }
+
+ if err := unmarshalNode(node, model, nil); err != nil {
+ return reflect.Value{}, err
+ }
+
+ return model, nil
+}
+
+func handleStructSlice(
+ attribute interface{},
+ fieldValue reflect.Value) (reflect.Value, error) {
+ models := reflect.New(fieldValue.Type()).Elem()
+ dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
+ for _, data := range dataMap {
+ model := reflect.New(fieldValue.Type().Elem()).Elem()
+
+ value, err := handleStruct(data, model)
+
+ if err != nil {
+ continue
+ }
+
+ models = reflect.Append(models, reflect.Indirect(value))
+ }
+
+ return models, nil
+}