2015-07-06 16:40:43 -04:00
|
|
|
package jsonapi
|
|
|
|
|
|
|
|
import (
|
2015-07-08 12:34:37 -04:00
|
|
|
"encoding/json"
|
2015-07-06 16:40:43 -04:00
|
|
|
"errors"
|
2015-07-07 18:46:20 -04:00
|
|
|
"fmt"
|
2015-07-08 12:34:37 -04:00
|
|
|
"io"
|
2015-07-06 16:40:43 -04:00
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
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,
|
|
|
|
//
|
|
|
|
// 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.WriteHeader(201)
|
|
|
|
// w.Header().Set("Content-Type", "application/vnd.api+json")
|
|
|
|
//
|
|
|
|
// if err := jsonapi.MarshalOnePayload(w, blog); err != nil {
|
|
|
|
// http.Error(w, err.Error(), 500)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// 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.
|
2015-07-10 12:07:12 -04:00
|
|
|
func UnmarshalPayload(in io.Reader, model interface{}) error {
|
|
|
|
payload := new(OnePayload)
|
2015-07-08 12:34:37 -04:00
|
|
|
|
|
|
|
if err := json.NewDecoder(in).Decode(payload); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
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-07-06 16:40:43 -04:00
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) error {
|
2015-07-07 18:46:20 -04:00
|
|
|
modelValue := model.Elem()
|
|
|
|
modelType := model.Type().Elem()
|
2015-07-06 16:40:43 -04:00
|
|
|
|
|
|
|
var er error
|
2015-07-06 16:41:38 -04:00
|
|
|
|
|
|
|
var i = 0
|
2015-07-06 16:40:43 -04:00
|
|
|
modelType.FieldByNameFunc(func(name string) bool {
|
2015-07-07 18:46:20 -04:00
|
|
|
if er != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-07-06 16:40:43 -04:00
|
|
|
fieldValue := modelValue.Field(i)
|
2015-07-07 18:46:20 -04:00
|
|
|
fieldType := modelType.Field(i)
|
2015-07-06 16:40:43 -04:00
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
tag := fieldType.Tag.Get("jsonapi")
|
|
|
|
|
|
|
|
args := strings.Split(tag, ",")
|
|
|
|
|
2015-07-08 14:49:36 -04:00
|
|
|
if len(args) != 2 {
|
|
|
|
er = errors.New(fmt.Sprintf("jsonapi tag, on %s, had two few arguments", fieldType.Name))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-07-06 16:40:43 -04:00
|
|
|
if len(args) >= 1 && args[0] != "" {
|
|
|
|
annotation := args[0]
|
|
|
|
|
|
|
|
if annotation == "primary" {
|
2015-07-07 18:46:20 -04:00
|
|
|
if data.Id == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
if data.Type != args[1] {
|
|
|
|
er = errors.New("Trying to Unmarshal a type that does not match")
|
|
|
|
return false
|
|
|
|
}
|
2015-07-06 16:40:43 -04:00
|
|
|
|
2015-07-10 11:20:49 -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
|
2015-07-06 16:40:43 -04:00
|
|
|
return false
|
|
|
|
}
|
2015-07-10 11:20:49 -04:00
|
|
|
fieldValue.SetInt(int64(id))
|
2015-07-06 16:40:43 -04:00
|
|
|
} else {
|
2015-07-10 11:20:49 -04:00
|
|
|
er = errors.New("Unsuppored data type for primary key, not int or string")
|
2015-07-06 16:40:43 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else if annotation == "attr" {
|
|
|
|
attributes := data.Attributes
|
|
|
|
if attributes == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
val := attributes[args[1]]
|
2015-07-09 15:27:03 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
// next if the attribute was not included in the request
|
|
|
|
if val == nil {
|
|
|
|
return false
|
|
|
|
}
|
2015-07-09 15:27:03 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
v := reflect.ValueOf(val)
|
2015-07-06 16:40:43 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
|
2015-07-09 15:27:03 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
var at int64
|
2015-07-07 18:46:20 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
if v.Kind() == reflect.Float64 {
|
|
|
|
at = int64(v.Interface().(float64))
|
|
|
|
} else if v.Kind() == reflect.Int {
|
|
|
|
at = v.Int()
|
|
|
|
} else {
|
|
|
|
er = errors.New("Only numbers can be parsed as dates, unix timestamps")
|
|
|
|
return false
|
|
|
|
}
|
2015-07-06 16:40:43 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
t := time.Unix(at, 0)
|
2015-07-09 15:27:03 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
fieldValue.Set(reflect.ValueOf(t))
|
2015-07-06 17:35:17 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
return false
|
|
|
|
}
|
2015-07-06 16:40:43 -04:00
|
|
|
|
2015-07-10 11:20:49 -04:00
|
|
|
if fieldValue.Kind() == reflect.Int && v.Kind() == reflect.Float64 {
|
|
|
|
fieldValue.Set(reflect.ValueOf(int(v.Interface().(float64))))
|
2015-07-06 16:40:43 -04:00
|
|
|
} else {
|
2015-07-10 11:20:49 -04:00
|
|
|
fieldValue.Set(reflect.ValueOf(val))
|
2015-07-06 16:40:43 -04:00
|
|
|
}
|
2015-07-07 18:46:20 -04:00
|
|
|
} else if annotation == "relation" {
|
|
|
|
isSlice := fieldValue.Type().Kind() == reflect.Slice
|
|
|
|
|
|
|
|
if data.Relationships == nil || data.Relationships[args[1]] == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
relationship := reflect.ValueOf(data.Relationships[args[1]]).Interface().(map[string]interface{})
|
|
|
|
|
|
|
|
if isSlice {
|
|
|
|
data := relationship["data"].([]interface{})
|
|
|
|
models := reflect.New(fieldValue.Type()).Elem()
|
|
|
|
|
|
|
|
for _, r := range data {
|
|
|
|
m := reflect.New(fieldValue.Type().Elem().Elem())
|
2015-07-10 11:30:59 -04:00
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
if err := unmarshalMap(r, m, included); err != nil {
|
2015-07-07 18:46:20 -04:00
|
|
|
er = err
|
|
|
|
return false
|
|
|
|
}
|
2015-07-10 11:30:59 -04:00
|
|
|
|
2015-07-07 18:46:20 -04:00
|
|
|
models = reflect.Append(models, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldValue.Set(models)
|
|
|
|
} else {
|
|
|
|
m := reflect.New(fieldValue.Type().Elem())
|
2015-07-10 14:41:54 -04:00
|
|
|
if err := unmarshalMap(relationship["data"], m, included); err != nil {
|
2015-07-07 18:46:20 -04:00
|
|
|
er = err
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldValue.Set(m)
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
er = errors.New(fmt.Sprintf("Unsupported jsonapi tag annotation, %s", annotation))
|
2015-07-06 16:40:43 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
if er != nil {
|
|
|
|
return er
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2015-07-07 18:46:20 -04:00
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
func unmarshalMap(nodeData interface{}, model reflect.Value, included *map[string]*Node) error {
|
|
|
|
h := nodeData.(map[string]interface{})
|
|
|
|
node := fetchNode(h, included)
|
|
|
|
|
|
|
|
if err := unmarshalNode(node, model, included); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func fetchNode(m map[string]interface{}, included *map[string]*Node) *Node {
|
|
|
|
var attributes map[string]interface{}
|
|
|
|
var relationships map[string]interface{}
|
|
|
|
|
|
|
|
if included != nil {
|
|
|
|
key := fmt.Sprintf("%s,%s", m["type"].(string), m["id"].(string))
|
|
|
|
node := (*included)[key]
|
|
|
|
|
|
|
|
if node != nil {
|
|
|
|
attributes = node.Attributes
|
|
|
|
relationships = node.Relationships
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapToNode(m, attributes, relationships)
|
|
|
|
}
|
|
|
|
|
|
|
|
func mapToNode(m map[string]interface{}, attributes map[string]interface{}, relationships map[string]interface{}) *Node {
|
2015-07-10 12:07:12 -04:00
|
|
|
node := &Node{Type: m["type"].(string)}
|
2015-07-07 18:46:20 -04:00
|
|
|
|
|
|
|
if m["id"] != nil {
|
|
|
|
node.Id = m["id"].(string)
|
|
|
|
}
|
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
if m["attributes"] == nil {
|
|
|
|
node.Attributes = attributes
|
|
|
|
} else {
|
2015-07-07 18:46:20 -04:00
|
|
|
node.Attributes = m["attributes"].(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
2015-07-10 14:41:54 -04:00
|
|
|
if m["relationships"] == nil {
|
|
|
|
node.Relationships = relationships
|
|
|
|
} else {
|
2015-07-07 18:46:20 -04:00
|
|
|
node.Relationships = m["relationships"].(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
return node
|
|
|
|
}
|