diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b1da35d..dc1b437 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.17', '1.15', '1.14', '1.13' ] + go: [ '1.18', '1.19', '1.20' ] name: Go ${{ matrix.go }} tests steps: - uses: actions/checkout@v2 @@ -24,9 +24,12 @@ jobs: run: | go get -t -d -v ./... go get github.com/onsi/ginkgo/ginkgo + go install github.com/onsi/ginkgo/ginkgo go get -u golang.org/x/lint/golint go get -u github.com/modocache/gover + go install github.com/modocache/gover go get -u github.com/mattn/goveralls + go install github.com/mattn/goveralls - name: Run tests run: | ginkgo -r -cover --randomizeAllSpecs --randomizeSuites --failOnPending --trace --race --progress diff --git a/.gitignore b/.gitignore index 8365624..4942eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.o *.a *.so +*.idea # Folders _obj diff --git a/api.go b/api.go index 3fe9a60..5d2e381 100644 --- a/api.go +++ b/api.go @@ -217,10 +217,9 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source interfac name = resourceType.Elem().Name() } - // check if EntityNamer interface is implemented and use that as name - entityName, ok := prototype.(jsonapi.EntityNamer) - if ok { - name = entityName.GetName() + identifier := prototype.GetID() + if identifier.Name != "" { + name = identifier.Name } else { name = jsonapi.Jsonify(jsonapi.Pluralize(name)) } @@ -711,9 +710,9 @@ func (res *resource) handleCreate(c APIContexter, w http.ResponseWriter, r *http } if len(prefix) > 0 { - w.Header().Set("Location", "/"+prefix+"/"+res.name+"/"+result.GetID()) + w.Header().Set("Location", "/"+prefix+"/"+res.name+"/"+result.GetID().ID) } else { - w.Header().Set("Location", "/"+res.name+"/"+result.GetID()) + w.Header().Set("Location", "/"+res.name+"/"+result.GetID().ID) } // handle 200 status codes @@ -764,7 +763,7 @@ func (res *resource) handleUpdate(c APIContexter, w http.ResponseWriter, r *http } identifiable, ok := updatingObj.Interface().(jsonapi.MarshalIdentifier) - if !ok || identifiable.GetID() != id { + if !ok || identifiable.GetID().ID != id { conflictError := errors.New("id in the resource does not match servers endpoint") return NewHTTPError(conflictError, conflictError.Error(), http.StatusConflict) } @@ -1231,9 +1230,10 @@ func handleError(err error, w http.ResponseWriter, r *http.Request, contentType func processRelationshipsData(data interface{}, linkName string, target interface{}) error { hasOne, ok := data.(map[string]interface{}) if ok { - hasOneID, ok := hasOne["id"].(string) - if !ok { - return fmt.Errorf("data object must have a field id for %s", linkName) + hasOneID, okID := hasOne["id"].(string) + hasOneLID, okLID := hasOne["lid"].(string) + if !okID && !okLID { + return fmt.Errorf("data object must have a field id or lid for %s", linkName) } target, ok := target.(jsonapi.UnmarshalToOneRelations) @@ -1241,7 +1241,7 @@ func processRelationshipsData(data interface{}, linkName string, target interfac return errors.New("target struct must implement interface UnmarshalToOneRelations") } - err := target.SetToOneReferenceID(linkName, hasOneID) + err := target.SetToOneReferenceID(linkName, &jsonapi.Identifier{ID: hasOneID, LID: hasOneLID}) if err != nil { return err } @@ -1252,7 +1252,7 @@ func processRelationshipsData(data interface{}, linkName string, target interfac return errors.New("target struct must implement interface UnmarshalToOneRelations") } - err := target.SetToOneReferenceID(linkName, "") + err := target.SetToOneReferenceID(linkName, nil) if err != nil { return err } @@ -1267,22 +1267,23 @@ func processRelationshipsData(data interface{}, linkName string, target interfac return errors.New("target struct must implement interface UnmarshalToManyRelations") } - hasManyIDs := []string{} + hasManyRelations := make([]jsonapi.Identifier, 0, len(hasMany)) for _, entry := range hasMany { data, ok := entry.(map[string]interface{}) if !ok { return fmt.Errorf("entry in data array must be an object for %s", linkName) } - dataID, ok := data["id"].(string) - if !ok { - return fmt.Errorf("all data objects must have a field id for %s", linkName) + dataID, okID := data["id"].(string) + dataLID, okLID := data["lid"].(string) + if !okID && !okLID { + return fmt.Errorf("all data objects must have a field id or lid for %s", linkName) } - hasManyIDs = append(hasManyIDs, dataID) + hasManyRelations = append(hasManyRelations, jsonapi.Identifier{ID: dataID, LID: dataLID}) } - err := target.SetToManyReferenceIDs(linkName, hasManyIDs) + err := target.SetToManyReferenceIDs(linkName, hasManyRelations) if err != nil { return err } diff --git a/api_entity_name_test.go b/api_entity_name_test.go index 973c62b..266a9c9 100644 --- a/api_entity_name_test.go +++ b/api_entity_name_test.go @@ -1,6 +1,7 @@ package api2go import ( + "github.com/manyminds/api2go/jsonapi" "net/http" "net/http/httptest" "strings" @@ -11,22 +12,20 @@ import ( type BaguetteTaste struct { ID string `json:"-"` + LID string `json:"-"` Taste string `json:"taste"` } -func (s BaguetteTaste) GetID() string { - return s.ID +func (s BaguetteTaste) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: s.ID, LID: s.LID, Name: "baguette-tastes"} } -func (s *BaguetteTaste) SetID(ID string) error { - s.ID = ID +func (s *BaguetteTaste) SetID(ID jsonapi.Identifier) error { + s.ID = ID.ID + s.LID = ID.LID return nil } -func (s BaguetteTaste) GetName() string { - return "baguette-tastes" -} - type BaguetteResource struct{} func (s BaguetteResource) FindOne(ID string, req Request) (Responder, error) { @@ -69,7 +68,7 @@ func (s BaguetteResource) Update(obj interface{}, req Request) (Responder, error }, nil } -var _ = Describe("Test route renaming with EntityNamer interface", func() { +var _ = Describe("Test route renaming with Identifier name", func() { var ( api *API rec *httptest.ResponseRecorder diff --git a/api_interfaces.go b/api_interfaces.go index 758addb..1089093 100644 --- a/api_interfaces.go +++ b/api_interfaces.go @@ -84,9 +84,9 @@ type ObjectInitializer interface { InitializeObject(interface{}) } -//URLResolver allows you to implement a static -//way to return a baseURL for all incoming -//requests for one api2go instance. +// URLResolver allows you to implement a static +// way to return a baseURL for all incoming +// requests for one api2go instance. type URLResolver interface { GetBaseURL() string } diff --git a/api_interfaces_test.go b/api_interfaces_test.go index ceeb8f1..3494268 100644 --- a/api_interfaces_test.go +++ b/api_interfaces_test.go @@ -14,16 +14,18 @@ import ( type SomeData struct { ID string `json:"-"` + LID string `json:"-"` Data string `json:"data"` CustomerID string `json:"customerId"` } -func (s SomeData) GetID() string { - return s.ID +func (s SomeData) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: s.ID, LID: s.LID} } -func (s *SomeData) SetID(ID string) error { - s.ID = ID +func (s *SomeData) SetID(ID jsonapi.Identifier) error { + s.ID = ID.ID + s.LID = ID.LID return nil } diff --git a/api_public.go b/api_public.go index c453d4e..7d31558 100644 --- a/api_public.go +++ b/api_public.go @@ -28,7 +28,7 @@ func (api API) Handler() http.Handler { return api.router.Handler() } -//Router returns the specified router on an api instance +// Router returns the specified router on an api instance func (api API) Router() routing.Routeable { return api.router } diff --git a/api_test.go b/api_test.go index 7f2d583..96a5ef0 100644 --- a/api_test.go +++ b/api_test.go @@ -37,12 +37,13 @@ func (m *requestURLResolver) SetRequest(r http.Request) { type invalid string -func (i invalid) GetID() string { - return "invalid" +func (i invalid) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: "invalid", LID: "invalid"} } type Post struct { ID string `json:"-"` + LID string `json:"-"` Title string `json:"title"` Value null.Float `json:"value"` Author *User `json:"-"` @@ -50,12 +51,13 @@ type Post struct { Bananas []Banana `json:"-"` } -func (p Post) GetID() string { - return p.ID +func (p Post) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: p.ID, LID: p.LID} } -func (p *Post) SetID(ID string) error { - p.ID = ID +func (p *Post) SetID(ID jsonapi.Identifier) error { + p.ID = ID.ID + p.LID = ID.LID return nil } @@ -79,24 +81,24 @@ func (p Post) GetReferences() []jsonapi.Reference { func (p Post) GetReferencedIDs() []jsonapi.ReferenceID { result := []jsonapi.ReferenceID{} if p.Author != nil { - result = append(result, jsonapi.ReferenceID{ID: p.Author.GetID(), Name: "author", Type: "users"}) + result = append(result, jsonapi.ReferenceID{ID: p.Author.GetID().ID, Name: "author", Type: "users"}) } for _, comment := range p.Comments { - result = append(result, jsonapi.ReferenceID{ID: comment.GetID(), Name: "comments", Type: "comments"}) + result = append(result, jsonapi.ReferenceID{ID: comment.GetID().ID, Name: "comments", Type: "comments"}) } for _, banana := range p.Bananas { - result = append(result, jsonapi.ReferenceID{ID: banana.GetID(), Name: "bananas", Type: "bananas"}) + result = append(result, jsonapi.ReferenceID{ID: banana.GetID().ID, Name: "bananas", Type: "bananas"}) } return result } -func (p *Post) SetToOneReferenceID(name, ID string) error { +func (p *Post) SetToOneReferenceID(name string, ID *jsonapi.Identifier) error { if name == "author" { - if ID == "" { + if ID == nil { p.Author = nil } else { - p.Author = &User{ID: ID} + p.Author = &User{ID: ID.ID, LID: ID.LID} } return nil @@ -105,11 +107,11 @@ func (p *Post) SetToOneReferenceID(name, ID string) error { return errors.New("There is no to-one relationship with the name " + name) } -func (p *Post) SetToManyReferenceIDs(name string, IDs []string) error { +func (p *Post) SetToManyReferenceIDs(name string, IDs []jsonapi.Identifier) error { if name == "comments" { comments := []Comment{} for _, ID := range IDs { - comments = append(comments, Comment{ID: ID}) + comments = append(comments, Comment{ID: ID.ID, LID: ID.LID}) } p.Comments = comments @@ -119,7 +121,7 @@ func (p *Post) SetToManyReferenceIDs(name string, IDs []string) error { if name == "bananas" { bananas := []Banana{} for _, ID := range IDs { - bananas = append(bananas, Banana{ID: ID}) + bananas = append(bananas, Banana{ID: ID.ID, LID: ID.LID}) } p.Bananas = bananas @@ -150,7 +152,7 @@ func (p *Post) DeleteToManyIDs(name string, IDs []string) error { for _, ID := range IDs { // find and delete the comment with ID for pos, comment := range p.Comments { - if comment.GetID() == ID { + if comment.GetID().ID == ID { p.Comments = append(p.Comments[:pos], p.Comments[pos+1:]...) } } @@ -161,7 +163,7 @@ func (p *Post) DeleteToManyIDs(name string, IDs []string) error { for _, ID := range IDs { // find and delete the comment with ID for pos, banana := range p.Bananas { - if banana.GetID() == ID { + if banana.GetID().ID == ID { p.Bananas = append(p.Bananas[:pos], p.Bananas[pos+1:]...) } } @@ -187,30 +189,33 @@ func (p Post) GetReferencedStructs() []jsonapi.MarshalIdentifier { type Comment struct { ID string `json:"-"` + LID string `json:"-"` Value string `json:"value"` } -func (c Comment) GetID() string { - return c.ID +func (c Comment) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: c.ID, LID: c.LID} } type Banana struct { ID string `jnson:"-"` + LID string `jnson:"-"` Name string } -func (b Banana) GetID() string { - return b.ID +func (b Banana) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: b.ID, LID: b.LID} } type User struct { ID string `json:"-"` + LID string `json:"-"` Name string `json:"name"` Info string `json:"info"` } -func (u User) GetID() string { - return u.ID +func (u User) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: u.ID, LID: u.LID} } type fixtureSource struct { @@ -934,7 +939,7 @@ var _ = Describe("RestHandler", func() { } } `, "/v1/posts/1", "PATCH") - Expect(target.Author.GetID()).To(Equal("2")) + Expect(target.Author.GetID().ID).To(Equal("2")) }) It("Patch can delete to-one relationships", func() { @@ -975,7 +980,7 @@ var _ = Describe("RestHandler", func() { } } `, "/v1/posts/1", "PATCH") - Expect(target.Comments[0].GetID()).To(Equal("2")) + Expect(target.Comments[0].GetID().ID).To(Equal("2")) }) It("Patch can delete to-many relationships", func() { @@ -1004,7 +1009,7 @@ var _ = Describe("RestHandler", func() { } }`, "/v1/posts/1/relationships/author", "PATCH") target := source.posts["1"] - Expect(target.Author.GetID()).To(Equal("2")) + Expect(target.Author.GetID().ID).To(Equal("2")) }) It("Relationship PATCH route updates to-many", func() { @@ -1016,7 +1021,7 @@ var _ = Describe("RestHandler", func() { }`, "/v1/posts/1/relationships/comments", "PATCH") target := source.posts["1"] Expect(target.Comments).To(HaveLen(1)) - Expect(target.Comments[0].GetID()).To(Equal("2")) + Expect(target.Comments[0].GetID().ID).To(Equal("2")) }) It("Relationship POST route adds to-many elements", func() { diff --git a/examples/crud_example.go b/examples/crud_example.go index 15f6bbc..a0d28af 100644 --- a/examples/crud_example.go +++ b/examples/crud_example.go @@ -5,38 +5,51 @@ To play with this example server you can run some of the following curl requests In order to demonstrate dynamic baseurl handling for requests, apply the --header="REQUEST_URI:https://www.your.domain.example.com" parameter to any of the commands. Create a new user: + curl -X POST http://localhost:31415/v0/users -d '{"data" : {"type" : "users" , "attributes": {"user-name" : "marvin"}}}' List users: + curl -X GET http://localhost:31415/v0/users List paginated users: + curl -X GET 'http://localhost:31415/v0/users?page\[offset\]=0&page\[limit\]=2' + OR + curl -X GET 'http://localhost:31415/v0/users?page\[number\]=1&page\[size\]=2' Update: + curl -vX PATCH http://localhost:31415/v0/users/1 -d '{ "data" : {"type" : "users", "id": "1", "attributes": {"user-name" : "better marvin"}}}' Delete: + curl -vX DELETE http://localhost:31415/v0/users/2 Create a chocolate with the name sweet + curl -X POST http://localhost:31415/v0/chocolates -d '{"data" : {"type" : "chocolates" , "attributes": {"name" : "Ritter Sport", "taste": "Very Good"}}}' Create a user with a sweet + curl -X POST http://localhost:31415/v0/users -d '{"data" : {"type" : "users" , "attributes": {"user-name" : "marvin"}, "relationships": {"sweets": {"data": [{"type": "chocolates", "id": "1"}]}}}}' List a users sweets + curl -X GET http://localhost:31415/v0/users/1/sweets Replace a users sweets + curl -X PATCH http://localhost:31415/v0/users/1/relationships/sweets -d '{"data" : [{"type": "chocolates", "id": "2"}]}' Add a sweet + curl -X POST http://localhost:31415/v0/users/1/relationships/sweets -d '{"data" : [{"type": "chocolates", "id": "2"}]}' Remove a sweet + curl -X DELETE http://localhost:31415/v0/users/1/relationships/sweets -d '{"data" : [{"type": "chocolates", "id": "2"}]}' */ package main diff --git a/examples/model/model_chocolate.go b/examples/model/model_chocolate.go index 39e74c3..4532b34 100644 --- a/examples/model/model_chocolate.go +++ b/examples/model/model_chocolate.go @@ -1,19 +1,23 @@ package model +import "github.com/manyminds/api2go/jsonapi" + // Chocolate is the chocolate that a user consumes in order to get fat and happy type Chocolate struct { ID string `json:"-"` + LID string `json:"-"` Name string `json:"name"` Taste string `json:"taste"` } // GetID to satisfy jsonapi.MarshalIdentifier interface -func (c Chocolate) GetID() string { - return c.ID +func (c Chocolate) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: c.ID, LID: c.LID} } // SetID to satisfy jsonapi.UnmarshalIdentifier interface -func (c *Chocolate) SetID(id string) error { - c.ID = id +func (c *Chocolate) SetID(ID jsonapi.Identifier) error { + c.ID = ID.ID + c.LID = ID.LID return nil } diff --git a/examples/model/model_user.go b/examples/model/model_user.go index 46f2702..bd393ee 100644 --- a/examples/model/model_user.go +++ b/examples/model/model_user.go @@ -8,7 +8,8 @@ import ( // User is a generic database user type User struct { - ID string `json:"-"` + ID string `json:"-"` + LID string `json:"-"` //rename the username field to user-name. Username string `json:"user-name"` PasswordHash string `json:"-"` @@ -18,13 +19,14 @@ type User struct { } // GetID to satisfy jsonapi.MarshalIdentifier interface -func (u User) GetID() string { - return u.ID +func (u User) GetID() jsonapi.Identifier { + return jsonapi.Identifier{ID: u.ID, LID: u.LID} } // SetID to satisfy jsonapi.UnmarshalIdentifier interface -func (u *User) SetID(id string) error { - u.ID = id +func (u *User) SetID(ID jsonapi.Identifier) error { + u.ID = ID.ID + u.LID = ID.LID return nil } @@ -63,9 +65,12 @@ func (u User) GetReferencedStructs() []jsonapi.MarshalIdentifier { } // SetToManyReferenceIDs sets the sweets reference IDs and satisfies the jsonapi.UnmarshalToManyRelations interface -func (u *User) SetToManyReferenceIDs(name string, IDs []string) error { +func (u *User) SetToManyReferenceIDs(name string, IDs []jsonapi.Identifier) error { if name == "sweets" { - u.ChocolatesIDs = IDs + u.ChocolatesIDs = make([]string, 0, len(IDs)) + for _, id := range IDs { + u.ChocolatesIDs = append(u.ChocolatesIDs, id.ID) + } return nil } diff --git a/examples/resolver/resolver.go b/examples/resolver/resolver.go index 942b049..4ade39c 100644 --- a/examples/resolver/resolver.go +++ b/examples/resolver/resolver.go @@ -5,20 +5,20 @@ import ( "net/http" ) -//RequestURL simply returns -//the request url from REQUEST_URI header -//this should not be done in production applications +// RequestURL simply returns +// the request url from REQUEST_URI header +// this should not be done in production applications type RequestURL struct { r http.Request Port int } -//SetRequest to implement `RequestAwareResolverInterface` +// SetRequest to implement `RequestAwareResolverInterface` func (m *RequestURL) SetRequest(r http.Request) { m.r = r } -//GetBaseURL implements `URLResolver` interface +// GetBaseURL implements `URLResolver` interface func (m RequestURL) GetBaseURL() string { if uri := m.r.Header.Get("REQUEST_URI"); uri != "" { return uri diff --git a/examples/resource/resource_user.go b/examples/resource/resource_user.go index 22994d3..795a74b 100644 --- a/examples/resource/resource_user.go +++ b/examples/resource/resource_user.go @@ -152,7 +152,7 @@ func (s UserResource) Delete(id string, r api2go.Request) (api2go.Responder, err return &Response{Code: http.StatusNoContent}, err } -//Update stores all changes on the user +// Update stores all changes on the user func (s UserResource) Update(obj interface{}, r api2go.Request) (api2go.Responder, error) { user, ok := obj.(model.User) if !ok { diff --git a/examples/storage/storage_chocolate.go b/examples/storage/storage_chocolate.go index 499f487..31bbdb4 100644 --- a/examples/storage/storage_chocolate.go +++ b/examples/storage/storage_chocolate.go @@ -19,7 +19,7 @@ func (c byID) Swap(i, j int) { } func (c byID) Less(i, j int) bool { - return c[i].GetID() < c[j].GetID() + return c[i].GetID().ID < c[j].GetID().ID } // NewChocolateStorage initializes the storage diff --git a/go.mod b/go.mod index a0b26a2..98bcce4 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,45 @@ -module github.com/manyminds/api2go +module github.com/retailnext/api2go -go 1.14 +go 1.18 require ( github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 - github.com/gin-gonic/gin v1.6.2 + github.com/gin-gonic/gin v1.7.7 github.com/gorilla/mux v1.7.4 github.com/julienschmidt/httprouter v1.3.0 github.com/labstack/echo v3.3.10+incompatible - github.com/labstack/gommon v0.3.0 // indirect - github.com/mattn/goveralls v0.0.11 // indirect - github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect + github.com/manyminds/api2go v0.0.0-20220325145637-95b4fb838cf6 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.10.1 - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.5.1 // indirect - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/tools v0.1.7 // indirect gopkg.in/guregu/null.v3 v3.4.0 ) + +require ( + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/golang/protobuf v1.4.2 // indirect + github.com/json-iterator/go v1.1.9 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/ugorji/go/codec v1.1.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.23.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) + +replace github.com/manyminds/api2go v0.0.0-20220325145637-95b4fb838cf6 => ./ diff --git a/go.sum b/go.sum index 912cc74..c13daca 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -7,20 +8,18 @@ github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSG github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM= -github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -31,11 +30,11 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -43,118 +42,84 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM= -github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk= -golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -169,7 +134,6 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/guregu/null.v3 v3.4.0 h1:AOpMtZ85uElRhQjEDsFx21BkXqFPwA7uoJukd4KErIs= gopkg.in/guregu/null.v3 v3.4.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= @@ -177,7 +141,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jsonapi/data_structs.go b/jsonapi/data_structs.go index 279b6cf..a75c115 100644 --- a/jsonapi/data_structs.go +++ b/jsonapi/data_structs.go @@ -117,6 +117,7 @@ type Meta map[string]interface{} type Data struct { Type string `json:"type"` ID string `json:"id"` + LID string `json:"lid,omitempty"` Attributes json.RawMessage `json:"attributes"` Relationships map[string]Relationship `json:"relationships,omitempty"` Links Links `json:"links,omitempty"` @@ -133,8 +134,8 @@ type Relationship struct { // A RelationshipDataContainer is used to marshal and unmarshal single relationship // objects and arrays of relationship objects. type RelationshipDataContainer struct { - DataObject *RelationshipData - DataArray []RelationshipData + DataObject *Identifier + DataArray []Identifier } // UnmarshalJSON unmarshals the JSON-encoded data to the DataObject field if the @@ -161,9 +162,3 @@ func (c *RelationshipDataContainer) MarshalJSON() ([]byte, error) { } return json.Marshal(c.DataObject) } - -// RelationshipData represents one specific reference ID. -type RelationshipData struct { - Type string `json:"type"` - ID string `json:"id"` -} diff --git a/jsonapi/data_structs_test.go b/jsonapi/data_structs_test.go index 4ce2337..072c3a0 100644 --- a/jsonapi/data_structs_test.go +++ b/jsonapi/data_structs_test.go @@ -14,6 +14,7 @@ var _ = Describe("JSONAPI Struct tests", func() { "data": { "type": "test", "id": "1", + "lid": "2", "attributes": {"foo": "bar"}, "relationships": { "author": { @@ -26,12 +27,13 @@ var _ = Describe("JSONAPI Struct tests", func() { expectedData := &Data{ Type: "test", ID: "1", + LID: "2", Attributes: json.RawMessage([]byte(`{"foo": "bar"}`)), Relationships: map[string]Relationship{ "author": { Data: &RelationshipDataContainer{ - DataObject: &RelationshipData{ - Type: "author", + DataObject: &Identifier{ + Name: "author", ID: "1", }, }, @@ -56,8 +58,8 @@ var _ = Describe("JSONAPI Struct tests", func() { "relationships": { "comments": { "data": [ - {"type": "comments", "id": "1"}, - {"type": "comments", "id": "2"} + {"type": "comments", "id": "1", "lid": "3"}, + {"type": "comments", "id": "2", "lid": "4"} ] } } @@ -72,14 +74,16 @@ var _ = Describe("JSONAPI Struct tests", func() { Relationships: map[string]Relationship{ "comments": { Data: &RelationshipDataContainer{ - DataArray: []RelationshipData{ + DataArray: []Identifier{ { - Type: "comments", + Name: "comments", ID: "1", + LID: "3", }, { - Type: "comments", + Name: "comments", ID: "2", + LID: "4", }, }, }, @@ -145,7 +149,7 @@ var _ = Describe("JSONAPI Struct tests", func() { Relationships: map[string]Relationship{ "comments": { Data: &RelationshipDataContainer{ - DataArray: []RelationshipData{}, + DataArray: []Identifier{}, }, }, "author": { diff --git a/jsonapi/entity_namer.go b/jsonapi/entity_namer.go deleted file mode 100644 index ca56120..0000000 --- a/jsonapi/entity_namer.go +++ /dev/null @@ -1,9 +0,0 @@ -package jsonapi - -// The EntityNamer interface can be optionally implemented to directly return the -// name of resource used for the "type" field. -// -// Note: By default the name is guessed from the struct name. -type EntityNamer interface { - GetName() string -} diff --git a/jsonapi/fixtures_test.go b/jsonapi/fixtures_test.go index 52f3008..a5e1cbe 100644 --- a/jsonapi/fixtures_test.go +++ b/jsonapi/fixtures_test.go @@ -15,8 +15,8 @@ type Magic struct { ID MagicID `json:"-"` } -func (m Magic) GetID() string { - return m.ID.String() +func (m Magic) GetID() Identifier { + return Identifier{ID: m.ID.String()} } type MagicID string @@ -27,22 +27,35 @@ func (m MagicID) String() string { type Comment struct { ID int `json:"-"` + LID int `json:"-"` Text string `json:"text"` SubComments []Comment `json:"-"` SubCommentsEmpty bool `json:"-"` } -func (c Comment) GetID() string { - return fmt.Sprintf("%d", c.ID) +func (c Comment) GetID() Identifier { + id := Identifier{ID: fmt.Sprintf("%d", c.ID)} + if c.LID != 0 { + id.LID = fmt.Sprintf("%d", c.LID) + } + return id } -func (c *Comment) SetID(stringID string) error { - id, err := strconv.Atoi(stringID) - if err != nil { - return err +func (c *Comment) SetID(ID Identifier) error { + if ID.ID != "" { + id, err := strconv.Atoi(ID.ID) + if err != nil { + return err + } + c.ID = id + } + if ID.LID != "" { + lid, err := strconv.Atoi(ID.LID) + if err != nil { + return err + } + c.LID = lid } - - c.ID = id return nil } @@ -61,7 +74,8 @@ func (c Comment) GetReferencedIDs() []ReferenceID { result := []ReferenceID{} for _, comment := range c.SubComments { - commentID := ReferenceID{Type: "comments", Name: "comments", ID: comment.GetID()} + id := comment.GetID() + commentID := ReferenceID{Type: "comments", Name: "comments", ID: id.ID, LID: id.LID} result = append(result, commentID) } @@ -80,27 +94,37 @@ func (c Comment) GetReferencedStructs() []MarshalIdentifier { type User struct { ID int `json:"-"` + LID int `json:"-"` Name string `json:"name"` Password string `json:"-"` } -func (u User) GetID() string { - return fmt.Sprintf("%d", u.ID) +func (u User) GetID() Identifier { + id := Identifier{ID: fmt.Sprintf("%d", u.ID)} + if u.LID != 0 { + id.LID = fmt.Sprintf("%d", u.LID) + } + return id } -func (u *User) SetID(stringID string) error { - id, err := strconv.Atoi(stringID) +func (u *User) SetID(ID Identifier) error { + id, err := strconv.Atoi(ID.ID) if err != nil { return err } - u.ID = id + lid, err := strconv.Atoi(ID.LID) + if err != nil { + return err + } + u.LID = lid return nil } type SimplePost struct { ID string `json:"-"` + LID string `json:"-"` Title string `json:"title"` Text string `json:"text"` Internal string `json:"-"` @@ -110,12 +134,18 @@ type SimplePost struct { topSecret string } -func (s SimplePost) GetID() string { - return s.ID +func (s SimplePost) GetID() Identifier { + return Identifier{ID: s.ID, LID: s.LID} +} + +func (s *SimplePost) SetID(ID Identifier) error { + s.ID = ID.ID + s.LID = ID.LID + return nil } -func (s *SimplePost) SetID(ID string) error { - s.ID = ID +func (s *SimplePost) SetLID(ID string) error { + s.LID = ID return nil } @@ -124,37 +154,65 @@ type ErrorIDPost struct { Error error } -func (s ErrorIDPost) GetID() string { - return "" +func (s ErrorIDPost) GetID() Identifier { + return Identifier{ID: "", LID: ""} } -func (s *ErrorIDPost) SetID(ID string) error { +func (s *ErrorIDPost) SetID(ID Identifier) error { return s.Error } type Post struct { ID int `json:"-"` + LID int `json:"-"` Title string `json:"title"` Comments []Comment `json:"-"` CommentsIDs []int `json:"-"` + CommentsLIDs []int `json:"-"` CommentsEmpty bool `json:"-"` Author *User `json:"-"` AuthorID sql.NullInt64 `json:"-"` + AuthorLID sql.NullInt64 `json:"-"` AuthorEmpty bool `json:"-"` } -func (c Post) GetID() string { - return fmt.Sprintf("%d", c.ID) +func (c Post) GetID() Identifier { + id := Identifier{ID: fmt.Sprintf("%d", c.ID)} + if c.LID != 0 { + id.LID = fmt.Sprintf("%d", c.LID) + } + return id +} + +func (c *Post) SetID(ID Identifier) error { + if ID.ID != "" { + id, err := strconv.Atoi(ID.ID) + if err != nil { + return err + } + c.ID = id + } + if ID.LID != "" { + lid, err := strconv.Atoi(ID.LID) + if err != nil { + return err + } + c.LID = lid + } + + return nil } -func (c *Post) SetID(stringID string) error { - id, err := strconv.Atoi(stringID) +func (c *Post) SetLID(stringID string) error { + if stringID == "" { + return nil + } + var err error + c.LID, err = strconv.Atoi(stringID) if err != nil { return err } - c.ID = id - return nil } @@ -173,15 +231,24 @@ func (c Post) GetReferences() []Reference { } } -func (c *Post) SetToOneReferenceID(name, ID string) error { +func (c *Post) SetToOneReferenceID(name string, ID *Identifier) error { if name == "author" { // Ignore empty author relationships - if ID != "" { - intID, err := strconv.ParseInt(ID, 10, 64) - if err != nil { - return err + if ID != nil { + if ID.ID != "" { + intID, err := strconv.ParseInt(ID.ID, 10, 64) + if err != nil { + return err + } + c.AuthorID = sql.NullInt64{Valid: true, Int64: intID} + } + if ID.LID != "" { + intLID, err := strconv.ParseInt(ID.LID, 10, 64) + if err != nil { + return err + } + c.AuthorLID = sql.NullInt64{Valid: true, Int64: intLID} } - c.AuthorID = sql.NullInt64{Valid: true, Int64: intID} } return nil @@ -190,20 +257,32 @@ func (c *Post) SetToOneReferenceID(name, ID string) error { return errors.New("There is no to-one relationship named " + name) } -func (c *Post) SetToManyReferenceIDs(name string, IDs []string) error { +func (c *Post) SetToManyReferenceIDs(name string, IDs []Identifier) error { if name == "comments" { - commentsIDs := []int{} + var commentsIDs []int + var commentsLIDs []int for _, ID := range IDs { - intID, err := strconv.ParseInt(ID, 10, 64) - if err != nil { - return err + if ID.ID != "" { + intID, err := strconv.ParseInt(ID.ID, 10, 64) + if err != nil { + return err + } + + commentsIDs = append(commentsIDs, int(intID)) } + if ID.LID != "" { + intLID, err := strconv.ParseInt(ID.LID, 10, 64) + if err != nil { + return err + } - commentsIDs = append(commentsIDs, int(intID)) + commentsLIDs = append(commentsLIDs, int(intLID)) + } } c.CommentsIDs = commentsIDs + c.CommentsLIDs = commentsLIDs return nil } @@ -233,7 +312,8 @@ func (c Post) GetReferencedIDs() []ReferenceID { result := []ReferenceID{} if c.Author != nil { - authorID := ReferenceID{Type: "users", Name: "author", ID: c.Author.GetID()} + id := c.Author.GetID() + authorID := ReferenceID{Type: "users", Name: "author", ID: id.ID, LID: id.LID} result = append(result, authorID) } else if c.AuthorID.Valid { authorID := ReferenceID{Type: "users", Name: "author", ID: fmt.Sprintf("%d", c.AuthorID.Int64)} @@ -242,7 +322,8 @@ func (c Post) GetReferencedIDs() []ReferenceID { if len(c.Comments) > 0 { for _, comment := range c.Comments { - result = append(result, ReferenceID{Type: "comments", Name: "comments", ID: comment.GetID()}) + id := comment.GetID() + result = append(result, ReferenceID{Type: "comments", Name: "comments", ID: id.ID, LID: id.LID}) } } else if len(c.CommentsIDs) > 0 { for _, commentID := range c.CommentsIDs { @@ -273,12 +354,17 @@ func (c *Post) SetReferencedStructs(references []UnmarshalIdentifier) error { type AnotherPost struct { ID int `json:"-"` + LID int `json:"-"` AuthorID int `json:"-"` Author *User `json:"-"` } -func (p AnotherPost) GetID() string { - return fmt.Sprintf("%d", p.ID) +func (p AnotherPost) GetID() Identifier { + id := Identifier{ID: fmt.Sprintf("%d", p.ID)} + if p.LID != 0 { + id.LID = fmt.Sprintf("%d", p.LID) + } + return id } func (p AnotherPost) GetReferences() []Reference { @@ -302,22 +388,24 @@ func (p AnotherPost) GetReferencedIDs() []ReferenceID { type ZeroPost struct { ID string `json:"-"` + LID string `json:"-"` Title string `json:"title"` Value zero.Float `json:"value"` } -func (z ZeroPost) GetID() string { - return z.ID +func (z ZeroPost) GetID() Identifier { + return Identifier{ID: z.ID, LID: z.LID} } type ZeroPostPointer struct { ID string `json:"-"` + LID string `json:"-"` Title string `json:"title"` Value *zero.Float `json:"value"` } -func (z ZeroPostPointer) GetID() string { - return z.ID +func (z ZeroPostPointer) GetID() Identifier { + return Identifier{ID: z.ID, LID: z.LID} } type Question struct { @@ -327,8 +415,8 @@ type Question struct { InspiringQuestion *Question `json:"-"` } -func (q Question) GetID() string { - return q.ID +func (q Question) GetID() Identifier { + return Identifier{ID: q.ID, LID: ""} } func (q Question) GetReferences() []Reference { @@ -362,17 +450,35 @@ func (q Question) GetReferencedStructs() []MarshalIdentifier { type Identity struct { ID int64 `json:"-"` + LID int64 `json:"-"` Scopes []string `json:"scopes"` } -func (i Identity) GetID() string { - return fmt.Sprintf("%d", i.ID) +func (i Identity) GetID() Identifier { + id := Identifier{ID: fmt.Sprintf("%d", i.ID)} + if i.LID != 0 { + id.LID = fmt.Sprintf("%d", i.LID) + } + return id } -func (i *Identity) SetID(ID string) error { - var err error - i.ID, err = strconv.ParseInt(ID, 10, 64) - return err +func (i *Identity) SetID(ID Identifier) error { + if ID.ID != "" { + id, err := strconv.Atoi(ID.ID) + if err != nil { + return err + } + i.ID = int64(id) + } + if ID.LID != "" { + lid, err := strconv.Atoi(ID.LID) + if err != nil { + return err + } + i.LID = int64(lid) + } + + return nil } type Unicorn struct { @@ -380,24 +486,31 @@ type Unicorn struct { Scopes []string `json:"scopes"` } -func (u Unicorn) GetID() string { - return "magicalUnicorn" +func (u Unicorn) GetID() Identifier { + return Identifier{ID: "magicalUnicorn", LID: ""} } type NumberPost struct { ID string `json:"-"` + LID string `json:"-"` Title string Number int64 UnsignedNumber uint64 } -func (n *NumberPost) SetID(ID string) error { - n.ID = ID +func (n NumberPost) GetID() Identifier { + return Identifier{ID: n.ID, LID: n.LID} +} + +func (n *NumberPost) SetID(ID Identifier) error { + n.ID = ID.ID + n.LID = ID.LID return nil } type SQLNullPost struct { ID string `json:"-"` + LID string `json:"-"` Title zero.String `json:"title"` Likes zero.Int `json:"likes"` Rating zero.Float `json:"rating"` @@ -405,43 +518,42 @@ type SQLNullPost struct { Today zero.Time `json:"today"` } -func (s SQLNullPost) GetID() string { - return s.ID +func (s SQLNullPost) GetID() Identifier { + return Identifier{ID: s.ID, LID: s.LID, Name: "sqlNullPosts"} } -func (s *SQLNullPost) SetID(ID string) error { - s.ID = ID +func (s *SQLNullPost) SetID(ID Identifier) error { + s.ID = ID.ID + s.LID = ID.LID return nil } type RenamedPostWithEmbedding struct { Embedded SQLNullPost ID string `json:"-"` + LID string `json:"-"` Another string `json:"another"` Field string `json:"foo"` Other string `json:"bar-bar"` Ignored string `json:"-"` } -func (p *RenamedPostWithEmbedding) SetID(ID string) error { - p.ID = ID - return nil +func (p RenamedPostWithEmbedding) GetID() Identifier { + return Identifier{ID: p.ID, LID: p.LID} } -func (s SQLNullPost) GetName() string { - return "sqlNullPosts" +func (p *RenamedPostWithEmbedding) SetID(ID Identifier) error { + p.ID = ID.ID + p.LID = ID.LID + return nil } type RenamedComment struct { Data string } -func (r RenamedComment) GetID() string { - return "666" -} - -func (r RenamedComment) GetName() string { - return "renamed-comments" +func (r RenamedComment) GetID() Identifier { + return Identifier{ID: "666", LID: "", Name: "renamed-comments"} } type CompleteServerInformation struct{} @@ -479,18 +591,14 @@ func (i PrefixServerInformation) GetPrefix() string { type CustomLinksPost struct{} -func (n CustomLinksPost) GetID() string { - return "someID" +func (n CustomLinksPost) GetID() Identifier { + return Identifier{ID: "someID", LID: "", Name: "posts"} } -func (n *CustomLinksPost) SetID(ID string) error { +func (n *CustomLinksPost) SetID(ID Identifier) error { return nil } -func (n CustomLinksPost) GetName() string { - return "posts" -} - func (n CustomLinksPost) GetCustomLinks(base string) Links { return Links{ "nothingInHere": Link{}, @@ -506,36 +614,28 @@ func (n CustomLinksPost) GetCustomLinks(base string) Links { type CustomResourceMetaPost struct{} -func (n CustomResourceMetaPost) GetID() string { - return "someID" +func (n CustomResourceMetaPost) GetID() Identifier { + return Identifier{ID: "someID", LID: "", Name: "posts"} } -func (n *CustomResourceMetaPost) SetID(ID string) error { +func (n *CustomResourceMetaPost) SetID(ID Identifier) error { return nil } -func (n CustomResourceMetaPost) GetName() string { - return "posts" -} - func (n CustomResourceMetaPost) Meta() Meta { return Meta{"access_count": 15} } type CustomMetaPost struct{} -func (n CustomMetaPost) GetID() string { - return "someID" +func (n CustomMetaPost) GetID() Identifier { + return Identifier{ID: "someID", LID: "", Name: "posts"} } -func (n *CustomMetaPost) SetID(ID string) error { +func (n *CustomMetaPost) SetID(ID Identifier) error { return nil } -func (n CustomMetaPost) GetName() string { - return "posts" -} - func (n CustomMetaPost) GetReferences() []Reference { return []Reference{ { @@ -562,51 +662,45 @@ func (n CustomMetaPost) GetCustomMeta(linkURL string) map[string]Meta { type NoRelationshipPosts struct{} -func (n NoRelationshipPosts) GetID() string { - return "someID" +func (n NoRelationshipPosts) GetID() Identifier { + return Identifier{ID: "someID", LID: "", Name: "posts"} } -func (n *NoRelationshipPosts) SetID(ID string) error { +func (n *NoRelationshipPosts) SetID(ID Identifier) error { return nil } -func (n NoRelationshipPosts) GetName() string { - return "posts" -} - type ErrorRelationshipPosts struct{} -func (e ErrorRelationshipPosts) GetID() string { - return "errorID" +func (e ErrorRelationshipPosts) GetID() Identifier { + return Identifier{ID: "errorID", LID: "", Name: "posts"} } -func (e *ErrorRelationshipPosts) SetID(ID string) error { +func (e *ErrorRelationshipPosts) SetID(ID Identifier) error { return nil } -func (e ErrorRelationshipPosts) GetName() string { - return "posts" -} - -func (e ErrorRelationshipPosts) SetToOneReferenceID(name, ID string) error { +func (e ErrorRelationshipPosts) SetToOneReferenceID(name string, ID *Identifier) error { return errors.New("this never works") } -func (e ErrorRelationshipPosts) SetToManyReferenceIDs(name string, IDs []string) error { +func (e ErrorRelationshipPosts) SetToManyReferenceIDs(name string, IDs []Identifier) error { return errors.New("this also never works") } type Image struct { ID string `json:"-"` + LID string `json:"-"` Ports []ImagePort `json:"image-ports"` } -func (i Image) GetID() string { - return i.ID +func (i Image) GetID() Identifier { + return Identifier{ID: i.ID, LID: i.LID} } -func (i *Image) SetID(ID string) error { - i.ID = ID +func (i *Image) SetID(ID Identifier) error { + i.ID = ID.ID + i.LID = ID.LID return nil } @@ -622,8 +716,8 @@ type Article struct { Relationship RelationshipType `json:"-"` } -func (a Article) GetID() string { - return "id" +func (a Article) GetID() Identifier { + return Identifier{ID: "id", LID: ""} } func (a Article) GetReferences() []Reference { @@ -645,12 +739,8 @@ type DeepDedendencies struct { Relationships []DeepDedendencies `json:"-"` } -func (d DeepDedendencies) GetID() string { - return d.ID -} - -func (DeepDedendencies) GetName() string { - return "deep" +func (d DeepDedendencies) GetID() Identifier { + return Identifier{ID: d.ID, LID: "", Name: "deep"} } func (d DeepDedendencies) GetReferences() []Reference { diff --git a/jsonapi/integration_test.go b/jsonapi/integration_test.go index 5fafa64..d84475a 100644 --- a/jsonapi/integration_test.go +++ b/jsonapi/integration_test.go @@ -8,20 +8,22 @@ import ( ) type Book struct { - ID string `json:"-"` - Author *StupidUser `json:"-"` - AuthorID string `json:"-"` - Pages []Page `json:"-"` - PagesIDs []string `json:"-"` + ID string `json:"-"` + LID string `json:"-"` + Author *StupidUser `json:"-"` + AuthorID string `json:"-"` + AuthorLID string `json:"-"` + Pages []Page `json:"-"` + PagesIDs []string `json:"-"` } -func (b Book) GetID() string { - return b.ID +func (b Book) GetID() Identifier { + return Identifier{ID: b.ID, LID: b.LID} } -func (b *Book) SetID(ID string) error { - b.ID = ID - +func (b *Book) SetID(ID Identifier) error { + b.ID = ID.ID + b.LID = ID.LID return nil } @@ -42,16 +44,20 @@ func (b Book) GetReferencedIDs() []ReferenceID { result := []ReferenceID{} if b.Author != nil { + id := b.Author.GetID() result = append(result, ReferenceID{ - ID: b.Author.GetID(), + ID: id.ID, + LID: id.LID, Name: "author", Type: "stupidUsers", }) } for _, page := range b.Pages { + id := page.GetID() result = append(result, ReferenceID{ - ID: page.GetID(), + ID: id.ID, + LID: id.LID, Name: "pages", Type: "pages", }) @@ -60,9 +66,10 @@ func (b Book) GetReferencedIDs() []ReferenceID { return result } -func (b *Book) SetToOneReferenceID(name, ID string) error { +func (b *Book) SetToOneReferenceID(name string, ID *Identifier) error { if name == "author" { - b.AuthorID = ID + b.AuthorID = ID.ID + b.AuthorLID = ID.LID return nil } @@ -70,9 +77,12 @@ func (b *Book) SetToOneReferenceID(name, ID string) error { return errors.New("There is no to-one relationship with name " + name) } -func (b *Book) SetToManyReferenceIDs(name string, IDs []string) error { +func (b *Book) SetToManyReferenceIDs(name string, IDs []Identifier) error { if name == "pages" { - b.PagesIDs = IDs + b.PagesIDs = make([]string, 0, len(IDs)) + for _, id := range IDs { + b.PagesIDs = append(b.PagesIDs, id.ID) + } return nil } @@ -99,8 +109,8 @@ type StupidUser struct { Name string `json:"name"` } -func (s StupidUser) GetID() string { - return s.ID +func (s StupidUser) GetID() Identifier { + return Identifier{ID: s.ID, LID: ""} } type Page struct { @@ -108,8 +118,8 @@ type Page struct { Content string `json:"content"` } -func (p Page) GetID() string { - return p.ID +func (p Page) GetID() Identifier { + return Identifier{ID: p.ID, LID: ""} } var _ = Describe("Test for the public api of this package", func() { diff --git a/jsonapi/marshal.go b/jsonapi/marshal.go index 6e36ce1..077ad1e 100644 --- a/jsonapi/marshal.go +++ b/jsonapi/marshal.go @@ -25,13 +25,20 @@ const ( // // Note: The implementation of this interface is mandatory. type MarshalIdentifier interface { - GetID() string + GetID() Identifier +} + +type Identifier struct { + ID string `json:"id"` + LID string `json:"lid,omitempty"` + Name string `json:"type"` } // ReferenceID contains all necessary information in order to reference another // struct in JSON API. type ReferenceID struct { ID string + LID string Type string Name string Relationship RelationshipType @@ -207,17 +214,18 @@ func marshalSlice(data interface{}, information ServerInformation) (*Document, e } func filterDuplicates(input []MarshalIdentifier, information ServerInformation) ([]Data, error) { - alreadyIncluded := map[string]map[string]bool{} + alreadyIncluded := map[string]map[Identifier]bool{} includedElements := []Data{} for _, referencedStruct := range input { structType := getStructType(referencedStruct) + id := referencedStruct.GetID() if alreadyIncluded[structType] == nil { - alreadyIncluded[structType] = make(map[string]bool) + alreadyIncluded[structType] = make(map[Identifier]bool) } - if !alreadyIncluded[structType][referencedStruct.GetID()] { + if !alreadyIncluded[structType][id] { var data Data err := marshalData(referencedStruct, &data, information) if err != nil { @@ -225,7 +233,7 @@ func filterDuplicates(input []MarshalIdentifier, information ServerInformation) } includedElements = append(includedElements, data) - alreadyIncluded[structType][referencedStruct.GetID()] = true + alreadyIncluded[structType][id] = true } } @@ -244,7 +252,9 @@ func marshalData(element MarshalIdentifier, data *Data, information ServerInform } data.Attributes = attributes - data.ID = element.GetID() + identifier := element.GetID() + data.ID = identifier.ID + data.LID = identifier.LID data.Type = getStructType(element) if information != nil { @@ -322,17 +332,19 @@ func getStructRelationships(relationer MarshalLinkedRelations, information Serve if isToMany(referenceIDs[0].Relationship, referenceIDs[0].Name) { // multiple elements in links - container.DataArray = []RelationshipData{} + container.DataArray = []Identifier{} for _, referenceID := range referenceIDs { - container.DataArray = append(container.DataArray, RelationshipData{ - Type: referenceID.Type, + container.DataArray = append(container.DataArray, Identifier{ + Name: referenceID.Type, ID: referenceID.ID, + LID: referenceID.LID, }) } } else { - container.DataObject = &RelationshipData{ - Type: referenceIDs[0].Type, + container.DataObject = &Identifier{ + Name: referenceIDs[0].Type, ID: referenceIDs[0].ID, + LID: referenceIDs[0].LID, } } @@ -363,7 +375,7 @@ func getStructRelationships(relationer MarshalLinkedRelations, information Serve // Plural empty relationships need an empty array and empty to-one need a null in the json if !reference.IsNotLoaded && isToMany(reference.Relationship, reference.Name) { - container.DataArray = []RelationshipData{} + container.DataArray = []Identifier{} } links := getLinksForServerInformation(relationer, name, information) @@ -393,13 +405,12 @@ func getStructRelationships(relationer MarshalLinkedRelations, information Serve func getLinkBaseURL(element MarshalIdentifier, information ServerInformation) string { prefix := strings.Trim(information.GetBaseURL(), "/") namespace := strings.Trim(information.GetPrefix(), "/") - structType := getStructType(element) if namespace != "" { prefix += "/" + namespace } - return fmt.Sprintf("%s/%s/%s", prefix, structType, element.GetID()) + return fmt.Sprintf("%s/%s/%s", prefix, getStructType(element), element.GetID().ID) } func getLinksForServerInformation(relationer MarshalLinkedRelations, name string, information ServerInformation) Links { @@ -446,9 +457,9 @@ func marshalStruct(data MarshalIdentifier, information ServerInformation) (*Docu } func getStructType(data interface{}) string { - entityName, ok := data.(EntityNamer) - if ok { - return entityName.GetName() + identifier, ok := data.(MarshalIdentifier) + if ok && identifier.GetID().Name != "" { + return identifier.GetID().Name } reflectType := reflect.TypeOf(data) diff --git a/jsonapi/marshal_composition_test.go b/jsonapi/marshal_composition_test.go index 01a10ff..0ac72b1 100644 --- a/jsonapi/marshal_composition_test.go +++ b/jsonapi/marshal_composition_test.go @@ -12,6 +12,10 @@ type TaggedPost struct { Tag string `json:"tag"` } +func (t TaggedPost) GetID() Identifier { + return Identifier{ID: t.ID, LID: t.LID} +} + var _ = Describe("Embedded struct types", func() { created, _ := time.Parse(time.RFC3339, "2014-11-10T16:30:48.823Z") post := TaggedPost{ diff --git a/jsonapi/marshal_different_to_many_test.go b/jsonapi/marshal_different_to_many_test.go index ae1ecbf..4f68351 100644 --- a/jsonapi/marshal_different_to_many_test.go +++ b/jsonapi/marshal_different_to_many_test.go @@ -7,11 +7,12 @@ import ( type ManyParent struct { ID string `json:"-"` + LID string `json:"-"` Content string `json:"content"` } -func (m ManyParent) GetID() string { - return m.ID +func (m ManyParent) GetID() Identifier { + return Identifier{ID: m.ID, LID: m.LID} } func (m ManyParent) GetReferences() []Reference { diff --git a/jsonapi/marshal_enum_test.go b/jsonapi/marshal_enum_test.go index 3ad3ad4..ed7543b 100644 --- a/jsonapi/marshal_enum_test.go +++ b/jsonapi/marshal_enum_test.go @@ -54,17 +54,18 @@ func (s *PublishStatus) UnmarshalJSON(data []byte) error { type EnumPost struct { ID string `json:"-"` + LID string `json:"-"` Title string `json:"title"` Status PublishStatus `json:"status"` } -func (e EnumPost) GetID() string { - return e.ID +func (e EnumPost) GetID() Identifier { + return Identifier{ID: e.ID, LID: e.LID} } -func (e *EnumPost) SetID(ID string) error { - e.ID = ID - +func (e *EnumPost) SetID(ID Identifier) error { + e.ID = ID.ID + e.LID = ID.LID return nil } diff --git a/jsonapi/marshal_same_type_test.go b/jsonapi/marshal_same_type_test.go index c121cf9..c912d1b 100644 --- a/jsonapi/marshal_same_type_test.go +++ b/jsonapi/marshal_same_type_test.go @@ -7,14 +7,15 @@ import ( type Node struct { ID string `json:"-"` + LID string `json:"-"` Content string `json:"content"` MotherID string `json:"-"` ChildIDs []string `json:"-"` AbandonedChildIDs []string `json:"-"` } -func (n *Node) GetID() string { - return n.ID +func (n Node) GetID() Identifier { + return Identifier{ID: n.ID, LID: n.LID} } func (n *Node) GetReferences() []Reference { diff --git a/jsonapi/marshal_test.go b/jsonapi/marshal_test.go index 6a55b1b..e73bfb8 100644 --- a/jsonapi/marshal_test.go +++ b/jsonapi/marshal_test.go @@ -24,7 +24,7 @@ var _ = Describe("Marshalling", func() { }) It("marshals single object without relationships", func() { - user := User{ID: 100, Name: "Nino", Password: "babymaus"} + user := User{ID: 100, LID: 0, Name: "Nino", Password: "babymaus"} i, err := Marshal(user) Expect(err).To(BeNil()) Expect(i).To(MatchJSON(`{ @@ -221,6 +221,86 @@ var _ = Describe("Marshalling", func() { }) }) + Context("When marshaling simple objects with local id", func() { + var ( + firstPost, secondPost SimplePost + created time.Time + ) + + BeforeEach(func() { + created, _ = time.Parse(time.RFC3339, "2014-11-10T16:30:48.823Z") + firstPost = SimplePost{ID: "first", Title: "First Post", Text: "Lipsum", Created: created, LID: "same_as_id"} + secondPost = SimplePost{ID: "second", Title: "Second Post", Text: "Getting more advanced!", Created: created, Updated: created, LID: ""} + }) + + It("marshals single object without relationships", func() { + user := User{ID: 100, Name: "Nino", Password: "babymaus", LID: 100} + i, err := Marshal(user) + Expect(err).To(BeNil()) + Expect(i).To(MatchJSON(`{ + "data": { + "type": "users", + "id": "100", + "lid": "100", + "attributes": { + "name": "Nino" + } + } + }`)) + }) + + It("marshals single object", func() { + i, err := Marshal(firstPost) + Expect(err).To(BeNil()) + Expect(i).To(MatchJSON(`{ + "data": { + "type": "simplePosts", + "id": "first", + "lid": "same_as_id", + "attributes": { + "title": "First Post", + "text": "Lipsum", + "created-date": "2014-11-10T16:30:48.823Z", + "updated-date": "0001-01-01T00:00:00Z", + "size": 0 + } + } + }`)) + }) + + It("marshals collections object", func() { + i, err := Marshal([]SimplePost{firstPost, secondPost}) + Expect(err).To(BeNil()) + Expect(i).To(MatchJSON(`{ + "data": [ + { + "type": "simplePosts", + "id": "first", + "lid": "same_as_id", + "attributes": { + "title": "First Post", + "text": "Lipsum", + "size": 0, + "created-date": "2014-11-10T16:30:48.823Z", + "updated-date": "0001-01-01T00:00:00Z" + } + }, + { + "type": "simplePosts", + "id": "second", + "attributes": { + "title": "Second Post", + "text": "Getting more advanced!", + "size": 0, + "created-date": "2014-11-10T16:30:48.823Z", + "updated-date": "2014-11-10T16:30:48.823Z" + } + } + ] + }`)) + }) + }) + Context("When marshaling objects with custom links", func() { It("contains the custom links in the marshaled data", func() { post := CustomLinksPost{} @@ -590,8 +670,8 @@ var _ = Describe("Marshalling", func() { comment1SubComment1 := Comment{ID: 3, Text: "No you are wrong!", SubCommentsEmpty: true} comment1SubComment2 := Comment{ID: 4, Text: "Nah, he's right!", SubCommentsEmpty: true} comment1 := Comment{ID: 1, Text: "First!", SubComments: []Comment{comment1SubComment1, comment1SubComment2}} - comment2 := Comment{ID: 2, Text: "Second!", SubCommentsEmpty: true} - author := User{ID: 1, Name: "Test Author"} + comment2 := Comment{ID: 2, LID: 2, Text: "Second!", SubCommentsEmpty: true} + author := User{ID: 1, LID: 1, Name: "Test Author"} post1 := Post{ID: 1, Title: "Foobar", Comments: []Comment{comment1, comment2}, Author: &author} i, err := MarshalWithURLs(post1, CompleteServerInformation{}) @@ -612,7 +692,8 @@ var _ = Describe("Marshalling", func() { }, "data": { "type": "users", - "id": "1" + "id": "1", + "lid": "1" } }, "comments": { @@ -627,7 +708,8 @@ var _ = Describe("Marshalling", func() { }, { "type": "comments", - "id": "2" + "id": "2", + "lid": "2" } ] } @@ -637,6 +719,7 @@ var _ = Describe("Marshalling", func() { { "type": "users", "id": "1", + "lid": "1", "attributes": { "name": "Test Author" } @@ -669,6 +752,7 @@ var _ = Describe("Marshalling", func() { { "type": "comments", "id": "2", + "lid": "2", "attributes": { "text": "Second!" }, @@ -1009,6 +1093,65 @@ var _ = Describe("Marshalling", func() { }`)) }) + It("Correctly marshalls the same dependencies with different local id", func() { + comment := Comment{ID: 1, Text: "comment", SubComments: []Comment{{LID: 1, Text: "sub comment one"}, {LID: 2, Text: "sub comment two"}}} + marshalled, err := Marshal(comment) + Expect(err).To(BeNil()) + Expect(marshalled).To(MatchJSON(`{ + "data": { + "type": "comments", + "id": "1", + "attributes": { + "text": "comment" + }, + "relationships": { + "comments": { + "data": [ + { + "id": "0", + "lid": "1", + "type": "comments" + }, + { + "id": "0", + "lid": "2", + "type": "comments" + } + ] + } + } + }, + "included": [ + { + "type": "comments", + "id": "0", + "lid": "1", + "attributes": { + "text": "sub comment one" + }, + "relationships": { + "comments": { + "data": [] + } + } + }, + { + "type": "comments", + "id": "0", + "lid": "2", + "attributes": { + "text": "sub comment two" + }, + "relationships": { + "comments": { + "data": [] + } + } + } + ] + }`)) + }) + It("Does not marshall same dependencies multiple times for slice", func() { marshalled, err := Marshal([]Question{question3, question2}) Expect(err).To(BeNil()) @@ -1145,13 +1288,30 @@ var _ = Describe("Marshalling", func() { }) Context("Slice fields", func() { - It("Marshalls the slice field correctly", func() { - marshalled, err := Marshal(Identity{1234, []string{"user_global"}}) + It("Marshalls the slice field correctly without lid", func() { + marshalled, err := Marshal(Identity{1234, 0, []string{"user_global"}}) + Expect(err).To(BeNil()) + Expect(marshalled).To(MatchJSON(`{ + "data": { + "type": "identities", + "id": "1234", + "attributes": { + "scopes": [ + "user_global" + ] + } + } + }`)) + }) + + It("Marshalls the slice field correctly with lid", func() { + marshalled, err := Marshal(Identity{1234, 1, []string{"user_global"}}) Expect(err).To(BeNil()) Expect(marshalled).To(MatchJSON(`{ "data": { "type": "identities", "id": "1234", + "lid": "1", "attributes": { "scopes": [ "user_global" @@ -1181,17 +1341,17 @@ var _ = Describe("Marshalling", func() { Context("Test getStructTypes method", func() { comment := Comment{ID: 100, Text: "some text"} - It("should work with normal value", func() { + It("should work with normal value with name in id", func() { result := getStructType(comment) Expect(result).To(Equal("comments")) }) - It("should work with pointer to value", func() { + It("should work with pointer to value with name in id", func() { result := getStructType(&comment) Expect(result).To(Equal("comments")) }) - It("checks for EntityNamer interface", func() { + It("checks for MarshalIdentifier interface", func() { result := getStructType(RenamedComment{"something"}) Expect(result).To(Equal("renamed-comments")) }) @@ -1214,9 +1374,9 @@ var _ = Describe("Marshalling", func() { links := getStructRelationships(post, nil) Expect(links["author"]).To(Equal(Relationship{ Data: &RelationshipDataContainer{ - DataObject: &RelationshipData{ + DataObject: &Identifier{ ID: "1", - Type: "users", + Name: "users", }, }, })) @@ -1226,10 +1386,11 @@ var _ = Describe("Marshalling", func() { links := getStructRelationships(post, nil) Expect(links["comments"]).To(Equal(Relationship{ Data: &RelationshipDataContainer{ - DataArray: []RelationshipData{ + DataArray: []Identifier{ { ID: "1", - Type: "comments", + Name: "comments", + LID: "", }, }, }, @@ -1240,9 +1401,9 @@ var _ = Describe("Marshalling", func() { links := getStructRelationships(post, CompleteServerInformation{}) Expect(links["author"]).To(Equal(Relationship{ Data: &RelationshipDataContainer{ - DataObject: &RelationshipData{ + DataObject: &Identifier{ ID: "1", - Type: "users", + Name: "users", }, }, Links: Links{ @@ -1256,9 +1417,9 @@ var _ = Describe("Marshalling", func() { links := getStructRelationships(post, BaseURLServerInformation{}) Expect(links["author"]).To(Equal(Relationship{ Data: &RelationshipDataContainer{ - DataObject: &RelationshipData{ + DataObject: &Identifier{ ID: "1", - Type: "users", + Name: "users", }, }, Links: Links{ @@ -1272,9 +1433,9 @@ var _ = Describe("Marshalling", func() { links := getStructRelationships(post, PrefixServerInformation{}) Expect(links["author"]).To(Equal(Relationship{ Data: &RelationshipDataContainer{ - DataObject: &RelationshipData{ + DataObject: &Identifier{ ID: "1", - Type: "users", + Name: "users", }, }, Links: Links{ diff --git a/jsonapi/unmarshal.go b/jsonapi/unmarshal.go index 9394cb6..0d7f30d 100644 --- a/jsonapi/unmarshal.go +++ b/jsonapi/unmarshal.go @@ -10,19 +10,19 @@ import ( // The UnmarshalIdentifier interface must be implemented to set the ID during // unmarshalling. type UnmarshalIdentifier interface { - SetID(string) error + SetID(Identifier) error } // The UnmarshalToOneRelations interface must be implemented to unmarshal // to-one relations. type UnmarshalToOneRelations interface { - SetToOneReferenceID(name, ID string) error + SetToOneReferenceID(name string, ID *Identifier) error } // The UnmarshalToManyRelations interface must be implemented to unmarshal // to-many relations. type UnmarshalToManyRelations interface { - SetToManyReferenceIDs(name string, IDs []string) error + SetToManyReferenceIDs(name string, IDs []Identifier) error } // The UnmarshalResourceMeta interface must be implemented to unmarshal meta fields inside of data containers @@ -126,7 +126,8 @@ func Unmarshal(data []byte, target interface{}) error { if !ok { return errors.New("existing structs must implement interface MarshalIdentifier") } - if record.ID == marshalCasted.GetID() { + identifier := marshalCasted.GetID() + if record.ID == identifier.ID || (record.LID != "" && record.LID == identifier.LID) { targetRecord = targetValue.Index(i).Addr() break } @@ -175,7 +176,7 @@ func setDataIntoTarget(data *Data, target interface{}) error { } } - if err := castedTarget.SetID(data.ID); err != nil { + if err := castedTarget.SetID(Identifier{ID: data.ID, LID: data.LID}); err != nil { return err } @@ -202,7 +203,7 @@ func setRelationshipIDs(relationships map[string]Relationship, target UnmarshalI return fmt.Errorf("struct %s does not implement UnmarshalToOneRelations", reflect.TypeOf(target)) } - err := castedToOne.SetToOneReferenceID(name, "") + err := castedToOne.SetToOneReferenceID(name, nil) if err != nil { return err } @@ -215,7 +216,7 @@ func setRelationshipIDs(relationships map[string]Relationship, target UnmarshalI if !ok { return fmt.Errorf("struct %s does not implement UnmarshalToOneRelations", reflect.TypeOf(target)) } - err := castedToOne.SetToOneReferenceID(name, rel.Data.DataObject.ID) + err := castedToOne.SetToOneReferenceID(name, rel.Data.DataObject) if err != nil { return err } @@ -227,11 +228,7 @@ func setRelationshipIDs(relationships map[string]Relationship, target UnmarshalI if !ok { return fmt.Errorf("struct %s does not implement UnmarshalToManyRelations", reflect.TypeOf(target)) } - IDs := make([]string, len(rel.Data.DataArray)) - for index, relData := range rel.Data.DataArray { - IDs[index] = relData.ID - } - err := castedToMany.SetToManyReferenceIDs(name, IDs) + err := castedToMany.SetToManyReferenceIDs(name, rel.Data.DataArray) if err != nil { return err } diff --git a/jsonapi/unmarshal_test.go b/jsonapi/unmarshal_test.go index ed0f058..2e73b5b 100644 --- a/jsonapi/unmarshal_test.go +++ b/jsonapi/unmarshal_test.go @@ -17,6 +17,7 @@ var _ = Describe("Unmarshal", func() { t, _ := time.Parse(time.RFC3339, "2014-11-10T16:30:48.823Z") firstPost := SimplePost{ID: "1", Title: "First Post", Text: "Lipsum", Created: t} secondPost := SimplePost{ID: "2", Title: "Second Post", Text: "Foobar!", Created: t, Updated: t} + secondPostWithLID := SimplePost{ID: "2", LID: "2", Title: "Second Post", Text: "Foobar!", Created: t, Updated: t} singlePostJSON := []byte(`{ "data": { @@ -54,6 +55,31 @@ var _ = Describe("Unmarshal", func() { ] }`) + multiplePostJSONWithLID := []byte(`{ + "data": [ + { + "id": "1", + "type": "simplePosts", + "attributes": { + "title": "First Post", + "text": "Lipsum", + "created-date": "2014-11-10T16:30:48.823Z" + } + }, + { + "id": "2", + "lid": "2", + "type": "simplePosts", + "attributes": { + "title": "Second Post", + "text": "Foobar!", + "created-date": "2014-11-10T16:30:48.823Z", + "updated-date": "2014-11-10T16:30:48.823Z" + } + } + ] + }`) + It("unmarshals single object into a struct", func() { var post SimplePost err := Unmarshal(singlePostJSON, &post) @@ -68,6 +94,13 @@ var _ = Describe("Unmarshal", func() { Expect(posts).To(Equal([]SimplePost{firstPost, secondPost})) }) + It("unmarshals multiple objects into a slice with LID", func() { + var posts []SimplePost + err := Unmarshal(multiplePostJSONWithLID, &posts) + Expect(err).To(BeNil()) + Expect(posts).To(Equal([]SimplePost{firstPost, secondPostWithLID})) + }) + It("unmarshals array attributes into array structs", func() { expected := Image{ ID: "one", @@ -101,7 +134,7 @@ var _ = Describe("Unmarshal", func() { It("errors on existing record that does not implement MarshalIdentifier", func() { type invalid struct{} invalids := []interface{}{invalid{}} - err := Unmarshal(multiplePostJSON, &invalids) + err := Unmarshal(multiplePostJSONWithLID, &invalids) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("existing structs must implement interface MarshalIdentifier")) }) @@ -317,6 +350,64 @@ var _ = Describe("Unmarshal", func() { }) }) + Context("When unmarshaling simple objects wtih local id", func() { + t, _ := time.Parse(time.RFC3339, "2014-11-10T16:30:48.823Z") + firstPost := SimplePost{ID: "1", Title: "First Post", Text: "Lipsum", Created: t, LID: "1"} + secondPost := SimplePost{ID: "2", Title: "Second Post", Text: "Foobar!", Created: t, Updated: t, LID: ""} + + singlePostJSON := []byte(`{ + "data": { + "id": "1", + "lid": "1", + "type": "simplePosts", + "attributes": { + "title": "First Post", + "text": "Lipsum", + "created-date": "2014-11-10T16:30:48.823Z" + } + } + }`) + + multiplePostJSON := []byte(`{ + "data": [ + { + "id": "1", + "lid": "1", + "type": "simplePosts", + "attributes": { + "title": "First Post", + "text": "Lipsum", + "created-date": "2014-11-10T16:30:48.823Z" + } + }, + { + "id": "2", + "type": "simplePosts", + "attributes": { + "title": "Second Post", + "text": "Foobar!", + "created-date": "2014-11-10T16:30:48.823Z", + "updated-date": "2014-11-10T16:30:48.823Z" + } + } + ] + }`) + + It("unmarshals single object into a struct", func() { + var post SimplePost + err := Unmarshal(singlePostJSON, &post) + Expect(err).ToNot(HaveOccurred()) + Expect(post).To(Equal(firstPost)) + }) + + It("unmarshals multiple objects into a slice", func() { + var posts []SimplePost + err := Unmarshal(multiplePostJSON, &posts) + Expect(err).To(BeNil()) + Expect(posts).To(Equal([]SimplePost{firstPost, secondPost})) + }) + }) + Context("when unmarshaling objects with relationships", func() { It("unmarshals to-many relationship IDs", func() { expectedPost := Post{ID: 1, CommentsIDs: []int{1}} @@ -403,7 +494,7 @@ var _ = Describe("Unmarshal", func() { }) It("unmarshals empty relationships", func() { - expectedPost := Post{ID: 3, Title: "Test", AuthorID: sql.NullInt64{Valid: false, Int64: 0}, Author: nil, CommentsIDs: []int{}} + expectedPost := Post{ID: 3, Title: "Test", AuthorID: sql.NullInt64{Valid: false, Int64: 0}, Author: nil, CommentsIDs: nil} postJSON := []byte(`{ "data": { "id": "3", @@ -539,6 +630,89 @@ var _ = Describe("Unmarshal", func() { }) }) + Context("when unmarshaling objects with relationships with local ids", func() { + It("unmarshals to-one and to-many relationship IDs", func() { + expectedPost := Post{ID: 1, LID: 1, AuthorID: sql.NullInt64{Valid: false, Int64: 0}, AuthorLID: sql.NullInt64{Valid: true, Int64: 1}, CommentsIDs: []int{1}, CommentsLIDs: []int{2}} + postJSON := []byte(`{ + "data": { + "id": "1", + "lid": "1", + "type": "posts", + "attributes": {}, + "relationships": { + "author": { + "data": { + "lid": "1", + "type": "users" + } + }, + "comments": { + "data": [ + { + "id": "1", + "type": "links" + }, + { + "lid": "2", + "type": "links" + }] + } + } + } + }`) + var post Post + err := Unmarshal(postJSON, &post) + Expect(err).To(BeNil()) + Expect(expectedPost).To(Equal(post)) + }) + + It("unmarshals to-one and to-many relationship IDs when included", func() { + expectedPost := Post{ID: 1, AuthorID: sql.NullInt64{Valid: false, Int64: 0}, AuthorLID: sql.NullInt64{Valid: true, Int64: 1}, CommentsLIDs: []int{1}} + postJSON := []byte(`{ + "data": { + "id": "1", + "type": "posts", + "attributes": {}, + "relationships": { + "author": { + "data": { + "lid": "1", + "type": "users" + } + }, + "comments": { + "data": [ + { + "lid": "1", + "type": "comments" + }] + } + } + }, + "included": [ + { + "type": "users", + "lid": "1", + "attributes": { + "name": "new user" + } + }, + { + "type": "comments", + "lid": "1", + "attributes": { + "text": "Does this test work?" + } + } + ] + }`) + var post Post + err := Unmarshal(postJSON, &post) + Expect(err).To(BeNil()) + Expect(expectedPost).To(Equal(post)) + }) + }) + It("check if type field matches target struct", func() { postJSON := []byte(`{ "data": { diff --git a/routing/gingonic.go b/routing/gingonic.go index 9d6c12d..d57c581 100644 --- a/routing/gingonic.go +++ b/routing/gingonic.go @@ -30,7 +30,7 @@ func (g ginRouter) Handle(protocol, route string, handler HandlerFunc) { g.router.Handle(protocol, route, wrappedCallback) } -//Gin creates a new api2go router to use with the gin framework +// Gin creates a new api2go router to use with the gin framework func Gin(g *gin.Engine) Routeable { return &ginRouter{router: g} } diff --git a/routing/gorillamux.go b/routing/gorillamux.go index 996412f..51d0b05 100644 --- a/routing/gorillamux.go +++ b/routing/gorillamux.go @@ -39,7 +39,7 @@ func (gm gorillamuxRouter) Handle(protocol, route string, handler HandlerFunc) { gm.router.HandleFunc(modroute, wrappedHandler).Methods(protocol) } -//Gorilla creates a new api2go router to use with the Gorilla mux framework +// Gorilla creates a new api2go router to use with the Gorilla mux framework func Gorilla(gm *mux.Router) Routeable { return &gorillamuxRouter{router: gm} }