Skip to content

Commit

Permalink
Unit Tests and a gin example (#11)
Browse files Browse the repository at this point in the history
* move gin example to dedicated example folder

* included tests

* add ContentType middleware function

* update comments and func names

* createResourceIdentifier unit tests

* more unit tests
  • Loading branch information
alehechka authored May 12, 2022
1 parent 8686926 commit d8b94ad
Show file tree
Hide file tree
Showing 13 changed files with 624 additions and 74 deletions.
51 changes: 1 addition & 50 deletions documentation-wip.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,56 +22,7 @@ go get github.com/alehechka/go-jsonapi

This library does not create any unnecessary marshalling assumptions, it will simply take in `Response` or `CollectionResponse` objects and transform them into JSON:API structs that keep the same provided object as the `Attributes` value. This allows an overlaying marshalling implementation to take over after transformation.

<details><summary>Example Implementation</summary>

```go
package main

import (
"github.com/gin-gonic/gin"
"github.com/alehechka/go-jsonapi"
)

func main() {
engine := gin.Default()

engine.GET("/record", getRecords)
engine.GET("/record/:id", getRecord)

engine.Run()
}

type Record struct {
RecordID string `json:"-"`
// variables
}

func (record Record) ID() string {
return record.RecordID
}

func (record Record) Type() string {
return "records"
}

func getRecords(ctx *gin.Context) {
records := getRecordsFromDatabase()

ctx.JSON(200, jsonapi.CreateCollectionResponse(ctx.Request)(jsonapi.CollectionResponse{
Nodes: records,
}))
}

func getRecord(ctx *gin.Context) {
record := getRecordFromDatabase(ctx.Param("id"))

ctx.JSON(200, jsonapi.CreateResponse(ctx.Request)(jsonapi.Response{
Node: record,
}))
}
```

</details>
View examples here: [/examples](/examples)

### Interface Methods

Expand Down
68 changes: 68 additions & 0 deletions examples/gin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package examples

import (
"github.com/alehechka/go-jsonapi/jsonapi"
"github.com/gin-gonic/gin"
)

func main() {
engine := gin.Default()

engine.GET("/record", getRecords)
engine.GET("/record/:id", getRecord)

engine.Run()
}

type Record struct {
RecordID string `json:"-"`
FirstName string `json:"firstName"`
Age int `json:"age"`
IsAdmin bool `json:"isAdmin"`
}

func (record Record) ID() string {
return record.RecordID
}

func (record Record) Type() string {
return "records"
}

func getRecords(ctx *gin.Context) {

ctx.JSON(200, jsonapi.CreateCollectionResponse(ctx.Request)(jsonapi.CollectionResponse{
Nodes: records(),
}))
}

func getRecord(ctx *gin.Context) {
records := records()
recordID := ctx.Param("id")

var record Record
for _, r := range records {
if r.RecordID == recordID {
record = r
}
}

ctx.JSON(200, jsonapi.CreateResponse(ctx.Request)(jsonapi.Response{
Node: record,
}))
}

func records() []Record {
return []Record{{
RecordID: "1234",
FirstName: "Joe",
Age: 25,
IsAdmin: true,
},
{
RecordID: "4321",
FirstName: "Sally",
Age: 30,
IsAdmin: true,
}}
}
8 changes: 8 additions & 0 deletions jsonapi/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ const (
// Include query parameter used to request extra resources to include in response
const Include string = "include"

// HTTP Header keys and values
const (
// ContentType is the standard Content-Type header.
ContentType string = "Content-Type"
// MediaType is the standard JSON:API media type for the Content-Type header.
MediaType string = "application/vnd.api+json"
)

// Standard HTTP Headers
const (
// ForwardedPrefix represents the prefix that is dropped when proxied through rest-api
Expand Down
45 changes: 45 additions & 0 deletions jsonapi/included_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package jsonapi

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_transformIncluded_Nil(t *testing.T) {
included := transformIncluded([]Node{}, nil, baseURL)

assert.Nil(t, included)
}

func Test_transformIncluded(t *testing.T) {
included := transformIncluded([]Node{testObject}, []Node{}, baseURL)

assert.NotNil(t, included)
assert.Equal(t, 1, len(included))

include := included[0]

assert.Equal(t, testObject.ID(), include.ID)
assert.Equal(t, testObject.Type(), include.Type)
assert.Equal(t, testObject, include.Attributes)
assert.Equal(t, testObject.Meta(), include.Meta)

assert.NotNil(t, include.Links)
assert.NotNil(t, include.Links[SelfKey])
assert.Equal(t, baseURL+testObject.Links()[SelfKey].Href, include.Links[SelfKey])
}

func Test_transformIncludedNode(t *testing.T) {
include := transformIncludedNode(testObject, baseURL)

assert.NotNil(t, include)
assert.Equal(t, testObject.ID(), include.ID)
assert.Equal(t, testObject.Type(), include.Type)
assert.Equal(t, testObject, include.Attributes)
assert.Equal(t, testObject.Meta(), include.Meta)

assert.NotNil(t, include.Links)
assert.NotNil(t, include.Links[SelfKey])
assert.Equal(t, baseURL+testObject.Links()[SelfKey].Href, include.Links[SelfKey])
}
32 changes: 17 additions & 15 deletions jsonapi/links.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ type Link struct {
Queries Queries `json:"-"`
}

// Links is a map of JsonAPILink objects
// Links is a map of Link objects
type Links map[string]Link

// LinkMap should have values of type JsonAPILink or string
type LinkMap map[string]interface{} // JsonAPILink | string
// LinkMap should have values of type Link or string
type LinkMap map[string]interface{} // Link | string

type Linkable interface {
Links() Links
Expand Down Expand Up @@ -81,7 +81,7 @@ func TransformLink(jsonLink Link, baseURL string) (link interface{}) {
}

func appendBaseURL(link Link, baseURL string) Link {
// only append baseURL if href is a relative URL

if IsRelativeURL(link.Href) {
link.Href = fmt.Sprintf("%s%s", baseURL, link.Href)
}
Expand All @@ -90,21 +90,23 @@ func appendBaseURL(link Link, baseURL string) Link {
}

func substitutePathParams(link Link) Link {
if link.Params != nil && len(link.Params) > 0 {
pathParts := strings.Split(link.Href, "/")

for index, pathPart := range pathParts {
if strings.HasPrefix(pathPart, ":") {
paramString := strings.TrimPrefix(pathPart, ":")
if param, exists := link.Params[paramString]; exists {
pathParts[index] = fmt.Sprintf("%v", param)
}
if link.Params == nil && len(link.Params) == 0 {
return link
}

pathParts := strings.Split(link.Href, "/")

for index, pathPart := range pathParts {
if strings.HasPrefix(pathPart, ":") {
paramString := strings.TrimPrefix(pathPart, ":")
if param, exists := link.Params[paramString]; exists {
pathParts[index] = fmt.Sprintf("%v", param)
}
}

link.Href = strings.Join(pathParts, "/")
}

link.Href = strings.Join(pathParts, "/")

return link
}

Expand Down
Loading

0 comments on commit d8b94ad

Please sign in to comment.