Skip to content

Commit a3ed5bc

Browse files
authored
Add default name validation options and fix README (#37)
* Normalizes all the name validation options into one each for Marshal and Unmarshal, making the name validation mode constants public. * Add missing links for name validation options * Minor fix to the examples used for Marshal and Unmarshal in the README
1 parent b3c2184 commit a3ed5bc

File tree

7 files changed

+42
-69
lines changed

7 files changed

+42
-69
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ if err != nil {
4141

4242
fmt.Println("%s", string(b))
4343
// {
44-
// "data": [
44+
// "data": {
4545
// "id": "1",
4646
// "type": "articles",
4747
// "attributes": {
4848
// "title": "Hello World"
4949
// }
50-
// ]
50+
// }
5151
// }
5252
```
5353

@@ -56,7 +56,7 @@ fmt.Println("%s", string(b))
5656
[jsonapi.Unmarshal](https://pkg.go.dev/github.com/DataDog/jsonapi#Marshal)
5757

5858
```go
59-
body := `{"data":[{"id":"1","type":"articles","attributes":{"title":"Hello World"}}]}`
59+
body := `{"data":{"id":"1","type":"articles","attributes":{"title":"Hello World"}}}`
6060

6161
type Article struct {
6262
ID string `jsonapi:"primary,articles"`
@@ -93,8 +93,8 @@ Both [jsonapi.Marshal](https://pkg.go.dev/github.com/DataDog/jsonapi#Marshal) an
9393

9494
| Option | Supports |
9595
| --- | --- |
96-
| [jsonapi.MarshalOption](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalOption) | [meta](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalMeta), [json:api](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalJSONAPI), [includes](https://pkg.go.dev/github.com/DataDog/github.com/jsonapi#MarshalInclude), [document links](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalLinks), [sparse fieldsets](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalFields) |
97-
| [jsonapi.UnmarshalOption](https://pkg.go.dev/github.com/DataDog/jsonapi#UnmarshalOption) | [meta](https://pkg.go.dev/github.com/DataDog/jsonapi#UnmarshalMeta) |
96+
| [jsonapi.MarshalOption](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalOption) | [meta](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalMeta), [json:api](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalJSONAPI), [includes](https://pkg.go.dev/github.com/DataDog/github.com/jsonapi#MarshalInclude), [document links](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalLinks), [sparse fieldsets](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalFields), [name validation](https://pkg.go.dev/github.com/DataDog/jsonapi#MarshalSetNameValidation) |
97+
| [jsonapi.UnmarshalOption](https://pkg.go.dev/github.com/DataDog/jsonapi#UnmarshalOption) | [meta](https://pkg.go.dev/github.com/DataDog/jsonapi#UnmarshalMeta), [name validation](https://pkg.go.dev/github.com/DataDog/jsonapi#UnmarshalSetNameValidation) |
9898

9999
## Non-String Identifiers
100100

marshal.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Marshaler struct {
2525
included []any
2626
link *Link
2727
clientMode bool
28-
memberNameValidationMode memberNameValidationMode
28+
memberNameValidationMode MemberNameValidationMode
2929

3030
// fields support sparse fieldsets https://jsonapi.org/format/#fetching-sparse-fieldsets
3131
fields map[string][]string
@@ -86,24 +86,10 @@ func MarshalClientMode() MarshalOption {
8686
}
8787
}
8888

89-
// MarshalStrictNameValidation enables member name validation that is more strict than default.
90-
//
91-
// In addition to the basic naming rules from https://jsonapi.org/format/#document-member-names,
92-
// this option follows guidelines from https://jsonapi.org/recommendations/#naming.
93-
func MarshalStrictNameValidation() MarshalOption {
89+
// MarshalSetNameValidation enables a given level of document member name validation.
90+
func MarshalSetNameValidation(mode MemberNameValidationMode) MarshalOption {
9491
return func(m *Marshaler) {
95-
m.memberNameValidationMode = strictValidation
96-
}
97-
}
98-
99-
// MarshalDisableNameValidation turns off member name validation, which may be useful for
100-
// compatibility or performance reasons.
101-
//
102-
// Note that this option allows you to use member names which do not conform to the JSON:API spec.
103-
// See https://jsonapi.org/format/#document-member-names.
104-
func MarshalDisableNameValidation() MarshalOption {
105-
return func(m *Marshaler) {
106-
m.memberNameValidationMode = disableValidation
92+
m.memberNameValidationMode = mode
10793
}
10894
}
10995

marshal_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ func TestMarshalMemberNameValidation(t *testing.T) {
764764
_, err := Marshal(tc.given, opts...)
765765
is.EqualError(t, tc.expectError, err)
766766

767-
opts = append(opts, MarshalDisableNameValidation())
767+
opts = append(opts, MarshalSetNameValidation(DisableValidation))
768768
_, err = Marshal(tc.given, opts...)
769769
is.MustNoError(t, err)
770770
})
@@ -798,7 +798,7 @@ func BenchmarkMarshal(b *testing.B) {
798798
given: articleRelatedComments,
799799
opts: []MarshalOption{
800800
MarshalInclude(&commentAWithAuthor, &authorA),
801-
MarshalDisableNameValidation(),
801+
MarshalSetNameValidation(DisableValidation),
802802
},
803803
}, {
804804
name: "ArticlesComplex",
@@ -808,7 +808,7 @@ func BenchmarkMarshal(b *testing.B) {
808808
name: "ArticlesComplexDisableNameValidation",
809809
given: articlesRelatedComplex,
810810
opts: append(
811-
[]MarshalOption{MarshalDisableNameValidation()},
811+
[]MarshalOption{MarshalSetNameValidation(DisableValidation)},
812812
articlesRelatedComplexMarshalOptions...,
813813
),
814814
},

member_names.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,42 @@ func init() {
2525
strictNameRegex = regexp.MustCompile(`^([a-z]|[a-z]+((\d)|([A-Z\d][a-z\d]+))*([A-Z\d][a-z\d]*[a-z]))$`)
2626
}
2727

28-
type memberNameValidationMode int
28+
// MemberNameValidationMode controls how document member names are checked for correctness.
29+
type MemberNameValidationMode int
2930

3031
const (
31-
// defaultValidation verifies that member names are valid according to the spec in
32+
// DefaultValidation verifies that member names are valid according to the spec in
3233
// https://jsonapi.org/format/#document-member-names.
3334
//
3435
// Note that this validation mode allows for non-URL-safe member names.
35-
defaultValidation memberNameValidationMode = iota
36+
DefaultValidation MemberNameValidationMode = iota
3637

37-
// disableValidation turns off member name validation for convenience and performance-saving
38+
// DisableValidation turns off member name validation for convenience or performance-saving
3839
// reasons.
3940
//
40-
// Note that this validation mode allows for member names to not conform to the JSON:API spec.
41-
disableValidation
41+
// Note that this validation mode allows member names that do NOT conform to the JSON:API spec.
42+
DisableValidation
4243

43-
// strictValidation verifies that member names are both valid according to the spec in
44+
// StrictValidation verifies that member names are valid according to the spec in
4445
// https://jsonapi.org/format/#document-member-names, and follow recommendations from
4546
// https://jsonapi.org/recommendations/#naming.
4647
//
4748
// Note that these names are always URL-safe.
48-
strictValidation
49+
StrictValidation
4950
)
5051

51-
func isValidMemberName(name string, mode memberNameValidationMode) bool {
52+
func isValidMemberName(name string, mode MemberNameValidationMode) bool {
5253
switch mode {
53-
case disableValidation:
54+
case DisableValidation:
5455
return true
55-
case strictValidation:
56+
case StrictValidation:
5657
return strictNameRegex.MatchString(name)
5758
default:
5859
return defaultNameRegex.MatchString(name)
5960
}
6061
}
6162

62-
func validateMapMemberNames(m map[string]any, mode memberNameValidationMode) error {
63+
func validateMapMemberNames(m map[string]any, mode MemberNameValidationMode) error {
6364
for member, val := range m {
6465
if !isValidMemberName(member, mode) {
6566
return &MemberNameValidationError{member}
@@ -84,7 +85,7 @@ func validateMapMemberNames(m map[string]any, mode memberNameValidationMode) err
8485
return nil
8586
}
8687

87-
func validateJSONMemberNames(b []byte, mode memberNameValidationMode) error {
88+
func validateJSONMemberNames(b []byte, mode MemberNameValidationMode) error {
8889
var m map[string]any
8990
if err := json.Unmarshal(b, &m); err != nil {
9091
return fmt.Errorf("unexpected unmarshal failure: %w", err)

member_names_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ func TestIsValidMemberName(t *testing.T) {
1010
t.Parallel()
1111

1212
// associate member name strings with the strictest validation mode they should pass
13-
testValidations := map[memberNameValidationMode][]string{
14-
strictValidation: {
13+
testValidations := map[MemberNameValidationMode][]string{
14+
StrictValidation: {
1515
"a",
1616
"lowercase1with2numerals",
1717
"camelCase",
1818
"camel12Case9WithNumera1s",
1919
},
20-
defaultValidation: {
20+
DefaultValidation: {
2121
"A",
2222
"9camelCaseWithNumeralPrefix",
2323
"camelCaseWithNumeralSuffix10",
@@ -31,7 +31,7 @@ func TestIsValidMemberName(t *testing.T) {
3131
"12",
3232
"ƒunky unicode",
3333
},
34-
disableValidation: {
34+
DisableValidation: {
3535
"bad%character",
3636
},
3737
}
@@ -43,17 +43,17 @@ func TestIsValidMemberName(t *testing.T) {
4343
t.Run(name, func(t *testing.T) {
4444
t.Parallel()
4545

46-
passesStrict := isValidMemberName(name, strictValidation)
47-
passesDefault := isValidMemberName(name, defaultValidation)
46+
passesStrict := isValidMemberName(name, StrictValidation)
47+
passesDefault := isValidMemberName(name, DefaultValidation)
4848

4949
switch mode {
50-
case strictValidation:
50+
case StrictValidation:
5151
is.Equal(t, true, passesStrict)
5252
is.Equal(t, true, passesDefault)
53-
case defaultValidation:
53+
case DefaultValidation:
5454
is.Equal(t, false, passesStrict)
5555
is.Equal(t, true, passesDefault)
56-
case disableValidation:
56+
case DisableValidation:
5757
is.Equal(t, false, passesStrict)
5858
is.Equal(t, false, passesDefault)
5959
}

unmarshal.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type Unmarshaler struct {
1212
unmarshalMeta bool
1313
meta any
14-
memberNameValidationMode memberNameValidationMode
14+
memberNameValidationMode MemberNameValidationMode
1515
}
1616

1717
// UnmarshalOption allows for configuration of Unmarshaling.
@@ -25,24 +25,10 @@ func UnmarshalMeta(meta any) UnmarshalOption {
2525
}
2626
}
2727

28-
// UnmarshalStrictNameValidation enables member name validation that is more strict than default.
29-
//
30-
// In addition to the basic naming rules from https://jsonapi.org/format/#document-member-names,
31-
// this option follows guidelines from https://jsonapi.org/recommendations/#naming.
32-
func UnmarshalStrictNameValidation() UnmarshalOption {
28+
// UnmarshalSetNameValidation enables a given level of document member name validation.
29+
func UnmarshalSetNameValidation(mode MemberNameValidationMode) UnmarshalOption {
3330
return func(m *Unmarshaler) {
34-
m.memberNameValidationMode = strictValidation
35-
}
36-
}
37-
38-
// UnmarshalDisableNameValidation turns off member name validation, which may be useful for
39-
// compatibility or performance reasons.
40-
//
41-
// Note that this option allows you to use member names which do not conform to the JSON:API spec.
42-
// See https://jsonapi.org/format/#document-member-names.
43-
func UnmarshalDisableNameValidation() UnmarshalOption {
44-
return func(m *Unmarshaler) {
45-
m.memberNameValidationMode = disableValidation
31+
m.memberNameValidationMode = mode
4632
}
4733
}
4834

unmarshal_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ func TestUnmarshalMemberNameValidation(t *testing.T) {
699699
err := tc.do([]byte(tc.given))
700700
is.EqualError(t, tc.expectError, err)
701701

702-
err = tc.do([]byte(tc.given), UnmarshalDisableNameValidation())
702+
err = tc.do([]byte(tc.given), UnmarshalSetNameValidation(DisableValidation))
703703
is.MustNoError(t, err)
704704
})
705705
}
@@ -731,7 +731,7 @@ func BenchmarkUnmarshal(b *testing.B) {
731731
name: "ArticleComplexDisableNameValidation",
732732
data: articleRelatedCommentsNestedWithIncludeBody,
733733
target: ArticleRelated{},
734-
opts: []UnmarshalOption{UnmarshalDisableNameValidation()},
734+
opts: []UnmarshalOption{UnmarshalSetNameValidation(DisableValidation)},
735735
}, {
736736
name: "ArticlesComplex",
737737
data: articlesRelatedComplexBody,
@@ -741,7 +741,7 @@ func BenchmarkUnmarshal(b *testing.B) {
741741
name: "ArticlesComplexDisableNameValidation",
742742
data: articlesRelatedComplexBody,
743743
target: []*ArticleRelated{},
744-
opts: []UnmarshalOption{UnmarshalDisableNameValidation()},
744+
opts: []UnmarshalOption{UnmarshalSetNameValidation(DisableValidation)},
745745
},
746746
}
747747

0 commit comments

Comments
 (0)