From cb6ce3e6c544779dc7807ada6a439763389fe749 Mon Sep 17 00:00:00 2001 From: Benjamin Bengfort Date: Mon, 9 Sep 2024 07:02:56 -0500 Subject: [PATCH] Fix Panic During Person Unmarshal (#177) --- pkg/ivms101/db_test.go | 170 ++++++++++++++++++++++++++++++ pkg/ivms101/errors.go | 18 ---- pkg/ivms101/identity.go | 46 +++++++- pkg/ivms101/ivms101.go | 203 +++++++++++++++++++++++------------- pkg/ivms101/ivms101_test.go | 6 ++ pkg/ivms101/rekey.go | 1 + 6 files changed, 353 insertions(+), 91 deletions(-) diff --git a/pkg/ivms101/db_test.go b/pkg/ivms101/db_test.go index 9d55951..0291c9c 100644 --- a/pkg/ivms101/db_test.go +++ b/pkg/ivms101/db_test.go @@ -1,8 +1,12 @@ package ivms101_test import ( + "database/sql" "encoding/json" + "errors" + "fmt" "os" + reflect "reflect" "testing" "github.com/stretchr/testify/require" @@ -84,6 +88,172 @@ func TestIVMS101Database(t *testing.T) { }) } +func TestIVMS101DatabaseScan(t *testing.T) { + type Object struct { + Identity *ivms101.IdentityPayload `json:"identity"` + Person *ivms101.Person `json:"person"` + NaturalPerson *ivms101.NaturalPerson `json:"naturalPerson"` + LegalPerson *ivms101.LegalPerson `json:"legalPerson"` + Address *ivms101.Address `json:"address"` + } + + model := &Object{} + mock := &MockRow{} + err := mock.Open("testdata/identity_payload.json", "testdata/person_legal_person.json", "testdata/natural_person.json", "testdata/legal_person.json", "testdata/address.json") + require.NoError(t, err, "could not load mock fixtures") + require.Len(t, mock.raw, 5) + + err = mock.Scan(&model.Identity, &model.Person, &model.NaturalPerson, &model.LegalPerson, &model.Address) + require.NoError(t, err) + + require.NotEmpty(t, model.Identity) + require.NotEmpty(t, model.Person) + require.NotEmpty(t, model.NaturalPerson) + require.NotEmpty(t, model.LegalPerson) + require.NotEmpty(t, model.Address) +} + +func TestIVMS101DatabaseScanNil(t *testing.T) { + type Object struct { + Identity *ivms101.IdentityPayload `json:"identity"` + Person *ivms101.Person `json:"person"` + NaturalPerson *ivms101.NaturalPerson `json:"naturalPerson"` + LegalPerson *ivms101.LegalPerson `json:"legalPerson"` + Address *ivms101.Address `json:"address"` + } + + model := &Object{} + mock := &MockRow{} + err := mock.Open("", "", "", "", "") + require.NoError(t, err, "could not load mock fixtures") + require.Len(t, mock.raw, 5) + + err = mock.Scan(&model.Identity, &model.Person, &model.NaturalPerson, &model.LegalPerson, &model.Address) + require.NoError(t, err) + + require.Empty(t, model.Identity) + require.Empty(t, model.Person) + require.Empty(t, model.NaturalPerson) + require.Empty(t, model.LegalPerson) + require.Empty(t, model.Address) +} + +func TestIVMS101DatabaseScanEmptyBytes(t *testing.T) { + type Object struct { + Identity *ivms101.IdentityPayload `json:"identity"` + Person *ivms101.Person `json:"person"` + NaturalPerson *ivms101.NaturalPerson `json:"naturalPerson"` + LegalPerson *ivms101.LegalPerson `json:"legalPerson"` + Address *ivms101.Address `json:"address"` + } + + model := &Object{} + mock := &MockRow{ + raw: [][]byte{{}, {}, {}, {}, {}}, + } + + err := mock.Scan(&model.Identity, &model.Person, &model.NaturalPerson, &model.LegalPerson, &model.Address) + require.EqualError(t, err, "unexpected end of JSON input") +} + +func TestIVMS101DatabaseScanNullJSON(t *testing.T) { + type Object struct { + Identity *ivms101.IdentityPayload `json:"identity"` + Person *ivms101.Person `json:"person"` + NaturalPerson *ivms101.NaturalPerson `json:"naturalPerson"` + LegalPerson *ivms101.LegalPerson `json:"legalPerson"` + Address *ivms101.Address `json:"address"` + } + + model := &Object{} + mock := &MockRow{ + raw: [][]byte{{110, 117, 108, 108}, {110, 117, 108, 108}, {110, 117, 108, 108}, {110, 117, 108, 108}, {110, 117, 108, 108}}, + } + + err := mock.Scan(&model.Identity, &model.Person, &model.NaturalPerson, &model.LegalPerson, &model.Address) + require.NoError(t, err) + require.Empty(t, model.Identity) + require.Empty(t, model.Person) + require.Empty(t, model.NaturalPerson) + require.Empty(t, model.LegalPerson) + require.Empty(t, model.Address) +} + +type MockRow struct { + raw [][]byte +} + +var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error + +func (r *MockRow) Open(paths ...string) error { + r.raw = make([][]byte, 0, len(paths)) + for _, path := range paths { + if err := r.open(path); err != nil { + return err + } + } + return nil +} + +func (r *MockRow) open(path string) (err error) { + if path == "" { + r.raw = append(r.raw, nil) + return nil + } + + var data []byte + if data, err = os.ReadFile(path); err != nil { + return err + } + r.raw = append(r.raw, data) + return nil +} + +func (r *MockRow) Scan(dest ...any) error { + if len(dest) != len(r.raw) { + return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(r.raw), len(dest)) + } + + for i, raw := range r.raw { + dst := dest[i] + + if raw == nil { + dst = nil + continue + } + + if scanner, ok := dst.(sql.Scanner); ok { + if err := scanner.Scan(raw); err != nil { + return err + } + } + + dpv := reflect.ValueOf(dst) + if dpv.Kind() != reflect.Pointer { + return errors.New("destination not a pointer") + } + if dpv.IsNil() { + return errNilPtr + } + + dv := reflect.Indirect(dpv) + switch dv.Kind() { + case reflect.Pointer: + if dst == nil { + dv.SetZero() + } + dv.Set(reflect.New(dv.Type().Elem())) + dvi := dv.Interface() + if scanner, ok := dvi.(sql.Scanner); ok { + if err := scanner.Scan(raw); err != nil { + return err + } + } + } + } + return nil +} + func loadFixture(path string, obj interface{}) (err error) { var f *os.File if f, err = os.Open(path); err != nil { diff --git a/pkg/ivms101/errors.go b/pkg/ivms101/errors.go index 1e4c986..003a33f 100644 --- a/pkg/ivms101/errors.go +++ b/pkg/ivms101/errors.go @@ -6,24 +6,6 @@ import ( "strings" ) -// Standard error values for error type checking -var ( - ErrNoLegalPersonNameIdentifiers = errors.New("one or more legal person name identifiers is required") - ErrInvalidLegalPersonName = errors.New("legal person name required with max length 100 chars") - ErrInvalidCustomerNumber = errors.New("customer number can be at most 50 chars") - ErrInvalidCountryCode = errors.New("invalid ISO-3166-1 alpha-2 country code") - ErrValidNationalIdentifierLegalPerson = errors.New("a legal person must have a national identifier of type RAID, MISC, LEIX, or TXID") - ErrInvalidLEI = errors.New("national identifier required with max length 35") - ErrCompleteNationalIdentifierCountry = errors.New("a legal person must not have a value for country if identifier type is not LEIX") - ErrCompleteNationalIdentifierAuthorityEmpty = errors.New("a legal person must have a value for registration authority if identifier type is not LEIX") - ErrCompleteNationalIdentifierAuthority = errors.New("a legal person must not have a value for registration authority if identifier type is LEIX") - ErrInvalidDateOfBirth = errors.New("date of birth must be a valid date in YYYY-MM-DD format") - ErrInvalidPlaceOfBirth = errors.New("place of birth required with at most 70 characters") - ErrDateInPast = errors.New("date of birth must be a historic date, prior to current date") - ErrValidAddress = errors.New("address must have at least one address line or street name + building name or number") - ErrInvalidAddressLines = errors.New("an address can contain at most 7 address lines") -) - // Parsing and JSON Serialization Errors var ( ErrPersonOneOfViolation = errors.New("ivms101: person must be either a legal person or a natural person not both") diff --git a/pkg/ivms101/identity.go b/pkg/ivms101/identity.go index ff01c24..744a795 100644 --- a/pkg/ivms101/identity.go +++ b/pkg/ivms101/identity.go @@ -1,6 +1,9 @@ package ivms101 -import "encoding/json" +import ( + "bytes" + "encoding/json" +) //=========================================================================== // IdentityPayload Methods @@ -43,6 +46,11 @@ func (i *IdentityPayload) MarshalJSON() ([]byte, error) { } func (i *IdentityPayload) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + i = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialIdentityPayloadFields); err != nil { @@ -98,6 +106,11 @@ func (o *Originator) MarshalJSON() ([]byte, error) { } func (o *Originator) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + o = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialOriginatorFields); err != nil { @@ -149,6 +162,11 @@ func (b *Beneficiary) MarshalJSON() ([]byte, error) { } func (b *Beneficiary) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + b = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialBeneficiaryFields); err != nil { @@ -190,6 +208,11 @@ func (o *OriginatingVasp) MarshalJSON() ([]byte, error) { } func (o *OriginatingVasp) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + o = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialOriginatorVASPFields); err != nil { @@ -230,6 +253,11 @@ func (b *BeneficiaryVasp) MarshalJSON() ([]byte, error) { } func (b *BeneficiaryVasp) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + b = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialBeneficiaryVASPFields); err != nil { @@ -275,6 +303,11 @@ func (v *IntermediaryVasp) MarshalJSON() ([]byte, error) { } func (v *IntermediaryVasp) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + v = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialIntermediaryVASPFields); err != nil { @@ -316,6 +349,11 @@ func (p *TransferPath) MarshalJSON() ([]byte, error) { } func (p *TransferPath) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + p = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialTransferPathFields); err != nil { @@ -359,6 +397,11 @@ func (p *PayloadMetadata) MarshalJSON() ([]byte, error) { } func (p *PayloadMetadata) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + p = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialPayloadMetadataFields); err != nil { @@ -374,6 +417,5 @@ func (p *PayloadMetadata) UnmarshalJSON(data []byte) (err error) { // Populate payload metadata values p.TransliterationMethod = middle.TransliterationMethod - return nil } diff --git a/pkg/ivms101/ivms101.go b/pkg/ivms101/ivms101.go index b334294..a8a3308 100644 --- a/pkg/ivms101/ivms101.go +++ b/pkg/ivms101/ivms101.go @@ -1,6 +1,7 @@ package ivms101 import ( + "bytes" "encoding/json" ) @@ -29,6 +30,11 @@ func (p *Person) MarshalJSON() ([]byte, error) { } func (p *Person) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + p = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialPersonKeys); err != nil { @@ -104,19 +110,24 @@ var serialNaturalPersonKeys = map[string]string{ "country": "countryOfResidence", } -func (n *NaturalPerson) MarshalJSON() ([]byte, error) { +func (p *NaturalPerson) MarshalJSON() ([]byte, error) { middle := serialNaturalPerson{ - Name: n.Name, - Address: n.GeographicAddresses, - Identification: n.NationalIdentification, - CustomerID: n.CustomerIdentification, - DOB: n.DateAndPlaceOfBirth, - CountryOfResidence: n.CountryOfResidence, + Name: p.Name, + Address: p.GeographicAddresses, + Identification: p.NationalIdentification, + CustomerID: p.CustomerIdentification, + DOB: p.DateAndPlaceOfBirth, + CountryOfResidence: p.CountryOfResidence, } return json.Marshal(middle) } -func (n *NaturalPerson) UnmarshalJSON(data []byte) (err error) { +func (p *NaturalPerson) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + p = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialNaturalPersonKeys); err != nil { @@ -131,12 +142,12 @@ func (n *NaturalPerson) UnmarshalJSON(data []byte) (err error) { } // Populate the natural person value - n.Name = middle.Name - n.GeographicAddresses = middle.Address - n.NationalIdentification = middle.Identification - n.CustomerIdentification = middle.CustomerID - n.DateAndPlaceOfBirth = middle.DOB - n.CountryOfResidence = middle.CountryOfResidence + p.Name = middle.Name + p.GeographicAddresses = middle.Address + p.NationalIdentification = middle.Identification + p.CustomerIdentification = middle.CustomerID + p.DateAndPlaceOfBirth = middle.DOB + p.CountryOfResidence = middle.CountryOfResidence return nil } @@ -176,6 +187,11 @@ func (n *NaturalPersonName) MarshalJSON() ([]byte, error) { } func (n *NaturalPersonName) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialNaturalPersonNameFields); err != nil { @@ -223,16 +239,21 @@ var serialNaturalPersonNameIDFields = map[string]string{ "name_identifier_type": "nameIdentifierType", } -func (p *NaturalPersonNameId) MarshalJSON() ([]byte, error) { +func (n *NaturalPersonNameId) MarshalJSON() ([]byte, error) { middle := serialNaturalPersonNameID{ - PrimaryIdentifier: p.PrimaryIdentifier, - SecondaryIdentifier: p.SecondaryIdentifier, - NameIdentifierType: p.NameIdentifierType, + PrimaryIdentifier: n.PrimaryIdentifier, + SecondaryIdentifier: n.SecondaryIdentifier, + NameIdentifierType: n.NameIdentifierType, } return json.Marshal(middle) } -func (p *NaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { +func (n *NaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialNaturalPersonNameIDFields); err != nil { @@ -247,9 +268,9 @@ func (p *NaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { } // Populate the natural person name id values - p.PrimaryIdentifier = middle.PrimaryIdentifier - p.SecondaryIdentifier = middle.SecondaryIdentifier - p.NameIdentifierType = middle.NameIdentifierType + n.PrimaryIdentifier = middle.PrimaryIdentifier + n.SecondaryIdentifier = middle.SecondaryIdentifier + n.NameIdentifierType = middle.NameIdentifierType return nil } @@ -273,16 +294,21 @@ var serialLocalNaturalPersonNameIDFields = map[string]string{ "name_identifier_type": "nameIdentifierType", } -func (p *LocalNaturalPersonNameId) MarshalJSON() ([]byte, error) { +func (n *LocalNaturalPersonNameId) MarshalJSON() ([]byte, error) { middle := serialLocalNaturalPersonNameID{ - PrimaryIdentifier: p.PrimaryIdentifier, - SecondaryIdentifier: p.SecondaryIdentifier, - NameIdentifierType: p.NameIdentifierType, + PrimaryIdentifier: n.PrimaryIdentifier, + SecondaryIdentifier: n.SecondaryIdentifier, + NameIdentifierType: n.NameIdentifierType, } return json.Marshal(middle) } -func (p *LocalNaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { +func (n *LocalNaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialLocalNaturalPersonNameIDFields); err != nil { @@ -297,9 +323,9 @@ func (p *LocalNaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { } // Populate the natural person name id values - p.PrimaryIdentifier = middle.PrimaryIdentifier - p.SecondaryIdentifier = middle.SecondaryIdentifier - p.NameIdentifierType = middle.NameIdentifierType + n.PrimaryIdentifier = middle.PrimaryIdentifier + n.SecondaryIdentifier = middle.SecondaryIdentifier + n.NameIdentifierType = middle.NameIdentifierType return nil } @@ -401,6 +427,11 @@ func (a *Address) MarshalJSON() ([]byte, error) { } func (a *Address) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + a = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialAddressFields); err != nil { @@ -462,6 +493,11 @@ func (d *DateAndPlaceOfBirth) MarshalJSON() ([]byte, error) { } func (d *DateAndPlaceOfBirth) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + d = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialDateAndPlaceOfBirthFields); err != nil { @@ -520,6 +556,11 @@ func (n *NationalIdentification) MarshalJSON() ([]byte, error) { } func (n *NationalIdentification) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialNationalIdentificationFields); err != nil { @@ -546,6 +587,15 @@ func (n *NationalIdentification) UnmarshalJSON(data []byte) (err error) { // LegalPerson Methods //=========================================================================== +// Person converts a LegalPerson into a Person protobuf message type. +func (p *LegalPerson) Person() *Person { + return &Person{ + Person: &Person_LegalPerson{ + LegalPerson: p, + }, + } +} + type serialLegalPerson struct { Name *LegalPersonName `json:"name,omitempty"` Address []*Address `json:"geographicAddress,omitempty"` @@ -572,18 +622,23 @@ var serialLegalPersonFields = map[string]string{ "country": "countryOfRegistration", } -func (l *LegalPerson) MarshalJSON() ([]byte, error) { +func (p *LegalPerson) MarshalJSON() ([]byte, error) { middle := serialLegalPerson{ - Name: l.Name, - Address: l.GeographicAddresses, - CustomerNumber: l.CustomerNumber, - NationalIdentification: l.NationalIdentification, - CountryOfRegistration: l.CountryOfRegistration, + Name: p.Name, + Address: p.GeographicAddresses, + CustomerNumber: p.CustomerNumber, + NationalIdentification: p.NationalIdentification, + CountryOfRegistration: p.CountryOfRegistration, } return json.Marshal(middle) } -func (l *LegalPerson) UnmarshalJSON(data []byte) (err error) { +func (p *LegalPerson) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + p = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialLegalPersonFields); err != nil { @@ -598,11 +653,11 @@ func (l *LegalPerson) UnmarshalJSON(data []byte) (err error) { } // Populate legal person values - l.Name = middle.Name - l.GeographicAddresses = middle.Address - l.CustomerNumber = middle.CustomerNumber - l.NationalIdentification = middle.NationalIdentification - l.CountryOfRegistration = middle.CountryOfRegistration + p.Name = middle.Name + p.GeographicAddresses = middle.Address + p.CustomerNumber = middle.CustomerNumber + p.NationalIdentification = middle.NationalIdentification + p.CountryOfRegistration = middle.CountryOfRegistration return nil } @@ -611,15 +666,6 @@ func (l *LegalPerson) UnmarshalJSON(data []byte) (err error) { // LegalPersonName Methods //=========================================================================== -// Person converts a LegalPerson into a Person protobuf message type. -func (p *LegalPerson) Person() *Person { - return &Person{ - Person: &Person_LegalPerson{ - LegalPerson: p, - }, - } -} - type serialLegalPersonName struct { NameIdentifiers []*LegalPersonNameId `json:"nameIdentifier,omitempty"` LocalNameIdentifiers []*LocalLegalPersonNameId `json:"localNameIdentifier,omitempty"` @@ -641,16 +687,21 @@ var serialLegalPersonNameFields = map[string]string{ "phonetic_name_identifiers": "phoneticNameIdentifier", } -func (l *LegalPersonName) MarshalJSON() ([]byte, error) { +func (n *LegalPersonName) MarshalJSON() ([]byte, error) { middle := serialLegalPersonName{ - LocalNameIdentifiers: l.LocalNameIdentifiers, - NameIdentifiers: l.NameIdentifiers, - PhoneticNameIdentifiers: l.PhoneticNameIdentifiers, + LocalNameIdentifiers: n.LocalNameIdentifiers, + NameIdentifiers: n.NameIdentifiers, + PhoneticNameIdentifiers: n.PhoneticNameIdentifiers, } return json.Marshal(middle) } -func (l *LegalPersonName) UnmarshalJSON(data []byte) (err error) { +func (n *LegalPersonName) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialLegalPersonNameFields); err != nil { @@ -665,9 +716,9 @@ func (l *LegalPersonName) UnmarshalJSON(data []byte) (err error) { } // Populate legal person values - l.NameIdentifiers = middle.NameIdentifiers - l.LocalNameIdentifiers = middle.LocalNameIdentifiers - l.PhoneticNameIdentifiers = middle.PhoneticNameIdentifiers + n.NameIdentifiers = middle.NameIdentifiers + n.LocalNameIdentifiers = middle.LocalNameIdentifiers + n.PhoneticNameIdentifiers = middle.PhoneticNameIdentifiers return nil } @@ -689,15 +740,20 @@ var serialLegalPersonNameIDFields = map[string]string{ "legal_person_name_identifier_type": "legalPersonNameIdentifierType", } -func (p *LegalPersonNameId) MarshalJSON() ([]byte, error) { +func (n *LegalPersonNameId) MarshalJSON() ([]byte, error) { middle := serialLegalPersonNameID{ - LegalPersonName: p.LegalPersonName, - LegalPersonNameIdentifierType: p.LegalPersonNameIdentifierType, + LegalPersonName: n.LegalPersonName, + LegalPersonNameIdentifierType: n.LegalPersonNameIdentifierType, } return json.Marshal(middle) } -func (p *LegalPersonNameId) UnmarshalJSON(data []byte) (err error) { +func (n *LegalPersonNameId) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialLegalPersonNameIDFields); err != nil { @@ -712,8 +768,8 @@ func (p *LegalPersonNameId) UnmarshalJSON(data []byte) (err error) { } // Populate legal person values - p.LegalPersonName = middle.LegalPersonName - p.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType + n.LegalPersonName = middle.LegalPersonName + n.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType return nil } @@ -735,15 +791,20 @@ var serialLocalLegalPersonNameIDFields = map[string]string{ "legal_person_name_identifier_type": "legalPersonNameIdentifierType", } -func (p *LocalLegalPersonNameId) MarshalJSON() ([]byte, error) { +func (n *LocalLegalPersonNameId) MarshalJSON() ([]byte, error) { middle := serialLocalLegalPersonNameID{ - LegalPersonName: p.LegalPersonName, - LegalPersonNameIdentifierType: p.LegalPersonNameIdentifierType, + LegalPersonName: n.LegalPersonName, + LegalPersonNameIdentifierType: n.LegalPersonNameIdentifierType, } return json.Marshal(middle) } -func (p *LocalLegalPersonNameId) UnmarshalJSON(data []byte) (err error) { +func (n *LocalLegalPersonNameId) UnmarshalJSON(data []byte) (err error) { + if bytes.Equal(data, nullJSON) { + n = nil + return nil + } + // Perform rekeying operation if allowRekeying { if data, err = Rekey(data, serialLocalLegalPersonNameIDFields); err != nil { @@ -758,8 +819,8 @@ func (p *LocalLegalPersonNameId) UnmarshalJSON(data []byte) (err error) { } // Populate legal person values - p.LegalPersonName = middle.LegalPersonName - p.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType + n.LegalPersonName = middle.LegalPersonName + n.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType return nil } diff --git a/pkg/ivms101/ivms101_test.go b/pkg/ivms101/ivms101_test.go index 7c73e66..650cfd4 100644 --- a/pkg/ivms101/ivms101_test.go +++ b/pkg/ivms101/ivms101_test.go @@ -13,6 +13,12 @@ import ( // Person JSON // +func TestJSONNil(t *testing.T) { + person := &ivms101.Person{} + err := json.Unmarshal([]byte(`{}`), person) + require.NoError(t, err) +} + func TestPersonMarshaling(t *testing.T) { data, err := os.ReadFile("testdata/person_natural_person.json") require.NoError(t, err, "could not load person with natural person json fixture") diff --git a/pkg/ivms101/rekey.go b/pkg/ivms101/rekey.go index 7a66479..1c753c1 100644 --- a/pkg/ivms101/rekey.go +++ b/pkg/ivms101/rekey.go @@ -8,6 +8,7 @@ import ( var ( allowRekeying bool disallowUnknownFields bool + nullJSON = []byte{110, 117, 108, 108} ) // Rekeying sets the module to perform a rekeying operation that changes snake_case and