Skip to content

Commit 4f0b623

Browse files
Ryan MoranRyan Moran
Ryan Moran
authored and
Ryan Moran
committed
First pass implementation
1 parent 1430cc8 commit 4f0b623

25 files changed

+972
-0
lines changed

constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package jsonapi
2+
3+
const ContentType = "application/vnd.api+json"

constants_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package jsonapi_test
2+
3+
import (
4+
"github.com/ryanmoran/jsonapi"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
var _ = Describe("Constants", func() {
11+
It("defines a content type", func() {
12+
Expect(jsonapi.ContentType).To(Equal("application/vnd.api+json"))
13+
})
14+
})

decodable.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package jsonapi
2+
3+
type Decodable interface {
4+
Type() string
5+
SetPrimary(id string)
6+
}

decode_attributes.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package jsonapi
2+
3+
import (
4+
"encoding/json"
5+
"reflect"
6+
)
7+
8+
type DecodeAttributes struct {
9+
d Decodable
10+
}
11+
12+
func NewDecodeAttributes(d Decodable) DecodeAttributes {
13+
return DecodeAttributes{d}
14+
}
15+
16+
func (da DecodeAttributes) UnmarshalJSON(data []byte) error {
17+
var attributes map[string]interface{}
18+
19+
err := json.Unmarshal(data, &attributes)
20+
if err != nil {
21+
panic(err)
22+
}
23+
24+
dValue := reflect.ValueOf(da.d).Elem()
25+
dType := reflect.TypeOf(da.d).Elem()
26+
27+
fieldMap := map[string]reflect.Value{}
28+
for i := 0; i < dValue.NumField(); i++ {
29+
fieldStruct := dType.Field(i)
30+
fieldValue := dValue.Field(i)
31+
tag, ok := fieldStruct.Tag.Lookup("jsonapi")
32+
if !ok {
33+
continue
34+
}
35+
36+
fieldMap[tag] = fieldValue
37+
}
38+
39+
for k, v := range attributes {
40+
field, ok := fieldMap[k]
41+
if !ok || !field.CanSet() {
42+
continue
43+
}
44+
45+
value := reflect.ValueOf(v)
46+
if value.Kind() != field.Kind() {
47+
panic("kinds don't match")
48+
}
49+
50+
field.Set(value)
51+
}
52+
53+
return nil
54+
}

decode_document.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package jsonapi
2+
3+
import "encoding/json"
4+
5+
type DecodeDocument struct {
6+
d Decodable
7+
}
8+
9+
func NewDecodeDocument(d Decodable) DecodeDocument {
10+
return DecodeDocument{d}
11+
}
12+
13+
func (dd DecodeDocument) UnmarshalJSON(data []byte) error {
14+
document := struct {
15+
Data DecodeResourceObject `json:"data"`
16+
}{
17+
Data: NewDecodeResourceObject(dd.d),
18+
}
19+
20+
err := json.Unmarshal(data, &document)
21+
if err != nil {
22+
panic(err)
23+
}
24+
25+
return nil
26+
}

decode_payload.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package jsonapi
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
type DecodePayload struct {
8+
v interface{}
9+
}
10+
11+
func NewDecodePayload(v interface{}) DecodePayload {
12+
return DecodePayload{v}
13+
}
14+
15+
func (dp DecodePayload) UnmarshalJSON(data []byte) error {
16+
decodable, ok := dp.v.(Decodable)
17+
if !ok {
18+
panic("not decodable")
19+
}
20+
21+
document := NewDecodeDocument(decodable)
22+
err := json.Unmarshal(data, &document)
23+
if err != nil {
24+
panic(err)
25+
}
26+
27+
return nil
28+
}

decode_relationships.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package jsonapi
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
type DecodeRelationships struct {
8+
d Decodable
9+
}
10+
11+
func NewDecodeRelationships(d Decodable) DecodeRelationships {
12+
return DecodeRelationships{d}
13+
}
14+
15+
func (dr DecodeRelationships) UnmarshalJSON(data []byte) error {
16+
assignable, ok := dr.d.(RelationshipsAssignable)
17+
if !ok {
18+
return nil
19+
}
20+
21+
var relationshipsMap map[string]interface{}
22+
23+
err := json.Unmarshal(data, &relationshipsMap)
24+
if err != nil {
25+
panic(err)
26+
}
27+
28+
var relationships []Relationship
29+
for name, resource := range relationshipsMap {
30+
switch r := resource.(type) {
31+
case map[string]interface{}:
32+
rType, _ := r["type"].(string)
33+
rID, _ := r["id"].(string)
34+
35+
relationships = append(relationships, Relationship{
36+
Name: name,
37+
Type: SingularRelationship,
38+
Resource: DecodeResourceLinkage{
39+
ResourceType: rType,
40+
ID: rID,
41+
},
42+
})
43+
case []interface{}:
44+
for _, item := range r {
45+
s, ok := item.(map[string]interface{})
46+
if !ok {
47+
panic("not a map")
48+
}
49+
50+
sType, _ := s["type"].(string)
51+
sID, _ := s["id"].(string)
52+
53+
relationships = append(relationships, Relationship{
54+
Name: name,
55+
Type: MultiRelationship,
56+
Resource: DecodeResourceLinkage{
57+
ResourceType: sType,
58+
ID: sID,
59+
},
60+
})
61+
}
62+
}
63+
}
64+
65+
assignable.AssignRelationships(relationships)
66+
67+
return nil
68+
}

decode_resource_linkage.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package jsonapi
2+
3+
type DecodeResourceLinkage struct {
4+
ResourceType string
5+
ID string
6+
}
7+
8+
func (drl DecodeResourceLinkage) Type() string {
9+
return drl.ResourceType
10+
}
11+
12+
func (drl DecodeResourceLinkage) Primary() string {
13+
return drl.ID
14+
}

decode_resource_object.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package jsonapi
2+
3+
import "encoding/json"
4+
5+
type DecodeResourceObject struct {
6+
d Decodable
7+
}
8+
9+
func NewDecodeResourceObject(d Decodable) DecodeResourceObject {
10+
return DecodeResourceObject{d}
11+
}
12+
13+
func (dro DecodeResourceObject) UnmarshalJSON(data []byte) error {
14+
object := struct {
15+
Type string `json:"type"`
16+
ID string `json:"id"`
17+
Attributes DecodeAttributes `json:"attributes"`
18+
Relationships DecodeRelationships `json:"relationships"`
19+
}{
20+
Attributes: NewDecodeAttributes(dro.d),
21+
Relationships: NewDecodeRelationships(dro.d),
22+
}
23+
24+
err := json.Unmarshal(data, &object)
25+
if err != nil {
26+
panic(err)
27+
}
28+
29+
if dro.d.Type() != object.Type {
30+
panic("types don't match")
31+
}
32+
33+
dro.d.SetPrimary(object.ID)
34+
35+
return nil
36+
}

encodable.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package jsonapi
2+
3+
type Encodable interface {
4+
Type() string
5+
Primary() string
6+
}
7+
8+
func ToEncodable(v interface{}) (Encodable, error) {
9+
m, ok := v.(Encodable)
10+
if !ok {
11+
panic("cannot encode")
12+
}
13+
14+
return m, nil
15+
}

encode_attributes.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package jsonapi
2+
3+
import "reflect"
4+
5+
type EncodeAttributes map[string]interface{}
6+
7+
func NewEncodeAttributes(m interface{}) EncodeAttributes {
8+
mType := reflect.TypeOf(m)
9+
mValue := reflect.ValueOf(m)
10+
11+
attributes := EncodeAttributes{}
12+
for i := 0; i < mType.NumField(); i++ {
13+
if name, ok := mType.Field(i).Tag.Lookup("jsonapi"); ok {
14+
attributes[name] = mValue.Field(i).Interface()
15+
}
16+
}
17+
18+
return attributes
19+
}

encode_document.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package jsonapi
2+
3+
import (
4+
"encoding/json"
5+
"reflect"
6+
)
7+
8+
type EncodeDocument struct {
9+
Data json.Marshaler `json:"data,omitempty"`
10+
Errors Errors `json:"errors,omitempty"`
11+
}
12+
13+
func NewSingularEncodeDocument(m interface{}) (EncodeDocument, error) {
14+
encodable, err := ToEncodable(m)
15+
if err != nil {
16+
panic(err)
17+
}
18+
19+
return EncodeDocument{
20+
Data: NewEncodeResourceObject(encodable),
21+
}, nil
22+
}
23+
24+
func NewMultipleEncodeDocument(m interface{}) (EncodeDocument, error) {
25+
var resourceObjects EncodeResourceObjects
26+
27+
value := reflect.ValueOf(m)
28+
for i := 0; i < value.Len(); i++ {
29+
elem := value.Index(i)
30+
encodable, err := ToEncodable(elem.Interface())
31+
if err != nil {
32+
panic(err)
33+
}
34+
35+
resourceObjects = append(resourceObjects, NewEncodeResourceObject(encodable))
36+
}
37+
38+
return EncodeDocument{
39+
Data: resourceObjects,
40+
}, nil
41+
}
42+
43+
func NewErrorsEncodeDocument(errors Errors) (EncodeDocument, error) {
44+
return EncodeDocument{
45+
Errors: errors,
46+
}, nil
47+
}

encode_links.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package jsonapi
2+
3+
type EncodeLinks map[string]Link
4+
5+
func NewEncodeLinks(m interface{}) EncodeLinks {
6+
linkable, ok := m.(Linkable)
7+
if !ok {
8+
return nil
9+
}
10+
11+
links := EncodeLinks{}
12+
for _, link := range linkable.Links() {
13+
links[link.Name] = link
14+
}
15+
16+
return links
17+
}

0 commit comments

Comments
 (0)