Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement readonly tag for primary and attr #107

Open
wants to merge 1 commit into
base: feature/embeded-structs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,34 @@ Tag value arguments are comma separated. The first argument must be,
`primary`, and the second must be the name that should appear in the
`type`\* field for all data objects that represent this type of model.

If the optional argument `readonly` is present the id will not be set when
using `Unmarshal` methods.

\* According the [JSON API](http://jsonapi.org) spec, the plural record
types are shown in the examples, but not required.

#### `attr`

```
`jsonapi:"attr,<key name in attributes hash>,<optional: omitempty>"`
`jsonapi:"attr,<key name in attributes hash>,<optional: omitempty>,<optional: readonly>"`
```

These fields' values will end up in the `attributes`hash for a record.
The first argument must be, `attr`, and the second should be the name
for the key to display in the `attributes` hash for that record. The optional
third argument is `omitempty` - if it is present the field will not be present
in the `"attributes"` if the field's value is equivalent to the field types
empty value (ie if the `count` field is of type `int`, `omitempty` will omit the
field when `count` has a value of `0`). Lastly, the spec indicates that
`attributes` key names should be dasherized for multiple word field names.
These fields' values will end up in the `attributes`hash for a record. The
first argument must be, `attr`, and the second should be the name for the key to
display in the `attributes` hash for that record.

If the optional argument `omitempty` is present the field will not be present in
the `"attributes"` if the field's value is equivalent to the field types empty
value (ie if the `count` field is of type `int`, `omitempty` will omit the field
when `count` has a value of `0`).

If the optional argument `readonly` is present the field will not be set when
using `Unmarshal` methods. This is useful for performing PATCH operations when
you unmarshal into an existing model to update the fields but don't want to
allow server set fields to be overwritten such as `created_at`.

Lastly, the spec indicates that `attributes` key names should be dasherized for
multiple word field names.

#### `relation`

Expand Down
1 change: 1 addition & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
annotationAttribute = "attr"
annotationRelation = "relation"
annotationOmitEmpty = "omitempty"
annotationReadOnly = "readonly"
annotationISO8601 = "iso8601"
annotationSeperator = ","
annotationIgnore = "-"
Expand Down
17 changes: 15 additions & 2 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@ func handlePrimaryUnmarshal(data *Node, args []string, fieldType reflect.StructF
)
}

if len(args) > 2 {
for _, arg := range args[2:] {
if arg == annotationReadOnly {
return nil
}
}
}

// Deal with PTRS
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
Expand Down Expand Up @@ -428,16 +436,21 @@ func handleAttributeUnmarshal(data *Node, args []string, fieldType reflect.Struc
return nil
}

var iso8601 bool

var iso8601, readOnly bool
if len(args) > 2 {
for _, arg := range args[2:] {
if arg == annotationISO8601 {
iso8601 = true
} else if arg == annotationReadOnly {
readOnly = true
}
}
}

if readOnly {
return nil
}

val := attributes[args[1]]

// continue if the attribute was not included in the request
Expand Down
31 changes: 31 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,36 @@ func TestEmbededStructs_nilStructPtr(t *testing.T) {
}
}

func TestUnmarshalReadOnly(t *testing.T) {
type PatchBlog struct {
ID int `jsonapi:"primary,blogs,readonly"`
CreatedAt time.Time `jsonapi:"attr,created_at,omitempty,readonly"`
ViewCount int `jsonapi:"attr,view_count,readonly,omitempty"`
Blog
}

now := time.Now()
blog := PatchBlog{
ID: 1,
CreatedAt: now,
ViewCount: 123,
}

if err := UnmarshalPayload(samplePayloadWithID(), &blog); err != nil {
t.Fatal(err)
}

if blog.ID != 1 {
t.Errorf("readonly id was set")
}
if blog.CreatedAt != now {
t.Errorf("readonly attr created_at was set")
}
if blog.ViewCount != 123 {
t.Errorf("readonly attr view_count was set")
}
}

func samplePayloadWithoutIncluded() map[string]interface{} {
return map[string]interface{}{
"data": map[string]interface{}{
Expand Down Expand Up @@ -910,6 +940,7 @@ func samplePayloadWithID() io.Reader {
Attributes: map[string]interface{}{
"title": "New blog",
"view_count": 1000,
"created_at": time.Now().Add(1 * time.Hour).Unix(),
},
},
}
Expand Down