diff --git a/marshal.go b/marshal.go index 48525f9..996c90e 100644 --- a/marshal.go +++ b/marshal.go @@ -21,6 +21,7 @@ func init() { type Marshaler struct { meta any includeJSONAPI bool + checkUniqueness bool jsonAPImeta any included []any link *Link @@ -50,6 +51,13 @@ func MarshalJSONAPI(meta any) MarshalOption { } } +// MarshallCheckUniqueness enables checking for unique resources during marshaling. +func MarshallCheckUniqueness() MarshalOption { + return func(m *Marshaler) { + m.checkUniqueness = true + } +} + // MarshalInclude includes the json:api encoding of v within Document.Included creating a compound document as defined by https://jsonapi.org/format/#document-compound-documents. func MarshalInclude(v ...any) MarshalOption { return func(m *Marshaler) { @@ -204,9 +212,10 @@ func makeDocument(v any, m *Marshaler, isRelationship bool) (*document, error) { } d.Included = append(d.Included, ro) } - - if ok := d.verifyResourceUniqueness(); !ok { - return nil, ErrNonuniqueResource + if m.checkUniqueness { + if ok := d.verifyResourceUniqueness(); !ok { + return nil, ErrNonuniqueResource + } } // if we got any included data, verify full-linkage of this compound document. if err := d.verifyFullLinkage(false); err != nil { diff --git a/marshal_test.go b/marshal_test.go index ff1d248..50f461b 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -20,6 +20,7 @@ func TestMarshal(t *testing.T) { given any expect string expectError error + opts []MarshalOption }{ { description: "nil", @@ -86,6 +87,7 @@ func TestMarshal(t *testing.T) { given: articlesAA, expect: "", expectError: ErrNonuniqueResource, + opts: []MarshalOption{MarshallCheckUniqueness()}, }, { description: "[]*Article", given: articlesABPtr, @@ -285,11 +287,11 @@ func TestMarshal(t *testing.T) { for i, tc := range tests { tc := tc - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("%02d - %s", i, tc.description), func(t *testing.T) { t.Parallel() t.Log(tc.description) - actual, err := Marshal(tc.given) + actual, err := Marshal(tc.given, tc.opts...) if tc.expectError != nil { is.EqualError(t, tc.expectError, err) is.Nil(t, actual) @@ -592,7 +594,7 @@ func TestMarshalRelationships(t *testing.T) { }, { description: "with related nonunique comments", given: &articleRelatedNonuniqueComments, - marshalOptions: nil, + marshalOptions: []MarshalOption{MarshallCheckUniqueness()}, expect: "", expectError: ErrNonuniqueResource, }, { @@ -641,7 +643,7 @@ func TestMarshalRelationships(t *testing.T) { for i, tc := range tests { tc := tc - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("%d - %s", i, tc.description), func(t *testing.T) { t.Parallel() t.Log(tc.description) diff --git a/unmarshal.go b/unmarshal.go index 722b09a..cb7798b 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -11,6 +11,7 @@ import ( type Unmarshaler struct { unmarshalMeta bool unmarshalLinks bool + checkUniqueness bool meta any links *Link memberNameValidationMode MemberNameValidationMode @@ -35,6 +36,13 @@ func UnmarshalLinks(link *Link) UnmarshalOption { } } +// UnmarshalCheckUniqueness enables checking for unique resources during unmarshaling. +func UnmarshalCheckUniqueness() UnmarshalOption { + return func(m *Unmarshaler) { + m.checkUniqueness = true + } +} + // UnmarshalSetNameValidation enables a given level of document member name validation. func UnmarshalSetNameValidation(mode MemberNameValidationMode) UnmarshalOption { return func(m *Unmarshaler) { @@ -88,8 +96,10 @@ func Unmarshal(data []byte, v any, opts ...UnmarshalOption) (err error) { } func (d *document) unmarshal(v any, m *Unmarshaler) (err error) { - if ok := d.verifyResourceUniqueness(); !ok { - return ErrNonuniqueResource + if m.checkUniqueness { + if ok := d.verifyResourceUniqueness(); !ok { + return ErrNonuniqueResource + } } // verify full-linkage in-case this is a compound document if err = d.verifyFullLinkage(true); err != nil { diff --git a/unmarshal_test.go b/unmarshal_test.go index 6c29eaf..ff93845 100644 --- a/unmarshal_test.go +++ b/unmarshal_test.go @@ -74,7 +74,7 @@ func TestUnmarshal(t *testing.T) { given: articlesABNonuniqueData, do: func(body []byte) (any, error) { var a []Article - err := Unmarshal(body, &a) + err := Unmarshal(body, &a, UnmarshalCheckUniqueness()) return a, err }, expect: ([]Article)(nil), @@ -430,7 +430,7 @@ func TestUnmarshal(t *testing.T) { given: articleRelatedNonuniqueLinkage, do: func(body []byte) (any, error) { var a ArticleRelated - err := Unmarshal(body, &a) + err := Unmarshal(body, &a, UnmarshalCheckUniqueness()) return a, err }, expect: ArticleRelated{},