diff --git a/pkg/ivms101/db.go b/pkg/ivms101/db.go index 370fb5c..d027620 100644 --- a/pkg/ivms101/db.go +++ b/pkg/ivms101/db.go @@ -7,12 +7,9 @@ import ( ) func (i *IdentityPayload) Scan(src interface{}) error { - p := IdentityPayload{} - if err := ScanJSON(src, &p); err != nil { + if err := ScanJSON(src, i); err != nil { return err } - - *i = p return nil } @@ -21,12 +18,9 @@ func (i *IdentityPayload) Value() (_ driver.Value, err error) { } func (p *Person) Scan(src interface{}) error { - o := Person{} - if err := ScanJSON(src, &o); err != nil { + if err := ScanJSON(src, p); err != nil { return err } - - *p = o return nil } @@ -35,12 +29,9 @@ func (p *Person) Value() (_ driver.Value, err error) { } func (p *NaturalPerson) Scan(src interface{}) error { - o := NaturalPerson{} - if err := ScanJSON(src, &o); err != nil { + if err := ScanJSON(src, p); err != nil { return err } - - *p = o return nil } @@ -49,12 +40,9 @@ func (p *NaturalPerson) Value() (_ driver.Value, err error) { } func (p *LegalPerson) Scan(src interface{}) error { - o := LegalPerson{} - if err := ScanJSON(src, &o); err != nil { + if err := ScanJSON(src, p); err != nil { return err } - - *p = o return nil } @@ -63,12 +51,9 @@ func (p *LegalPerson) Value() (_ driver.Value, err error) { } func (a *Address) Scan(src interface{}) error { - o := Address{} - if err := ScanJSON(src, &o); err != nil { + if err := ScanJSON(src, a); err != nil { return err } - - *a = o return nil } diff --git a/pkg/ivms101/errors.go b/pkg/ivms101/errors.go index a0c0e44..b690cb5 100644 --- a/pkg/ivms101/errors.go +++ b/pkg/ivms101/errors.go @@ -1,25 +1,23 @@ package ivms101 -import "errors" +import ( + "errors" + "fmt" + "strings" +) // Standard error values for error type checking var ( ErrNoNaturalPersonNameIdentifiers = errors.New("one or more natural person name identifiers is required") ErrInvalidNaturalPersonName = errors.New("natural person name required with max length 100 chars") - ErrInvalidNaturalPersonNameTypeCode = errors.New("invalid natural person name type code") - ErrParseNaturalPersonNameTypeCode = errors.New("could not parse natural person name type code from value") ErrNoLegalPersonNameIdentifiers = errors.New("one or more legal person name identifiers is required") ErrInvalidLegalPersonName = errors.New("legal person name required with max length 100 chars") - ErrInvalidLegalPersonNameTypeCode = errors.New("invalid legal person name type code") - ErrParseLegalPersonNameTypeCode = errors.New("could not parse legal person name type code from value") ErrLegalNamesPresent = errors.New("at least one name identifier must have a LEGL name identifier type") ErrInvalidCustomerNumber = errors.New("customer number can be at most 50 chars") ErrInvalidCustomerIdentification = errors.New("customer identification 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") - ErrInvalidNationalIdentifierTypeCode = errors.New("invalid national identifier type code") - ErrParseNationalIdentifierTypeCode = errors.New("could not parse national identifier type code from value") 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") @@ -27,13 +25,132 @@ var ( 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") - ErrInvalidAddressTypeCode = errors.New("invalid address type code") - ErrParseAddressTypeCode = errors.New("could not parse address type code from value") ErrInvalidAddressLines = errors.New("an address can contain at most 7 address lines") - ErrInvalidTransliterationMethodCode = errors.New("invalid transliteration method code") - ErrParseTransliterationMethodCode = errors.New("could not parse transliteration method code from value") ) +// Parsing and JSON Serialization Errors +var ( + ErrPersonOneOfViolation = errors.New("ivms101: person must be either a legal person or a natural person not both") + ErrInvalidNaturalPersonNameTypeCode = errors.New("ivms101: invalid natural person name type code") + ErrParseNaturalPersonNameTypeCode = errors.New("ivms101: could not parse natural person name type code from value") + ErrInvalidLegalPersonNameTypeCode = errors.New("ivms101: invalid legal person name type code") + ErrParseLegalPersonNameTypeCode = errors.New("ivms101: could not parse legal person name type code from value") + ErrInvalidNationalIdentifierTypeCode = errors.New("ivms101: invalid national identifier type code") + ErrParseNationalIdentifierTypeCode = errors.New("ivms101: could not parse national identifier type code from value") + ErrInvalidAddressTypeCode = errors.New("ivms101: invalid address type code") + ErrParseAddressTypeCode = errors.New("ivms101: could not parse address type code from value") + ErrInvalidTransliterationMethodCode = errors.New("ivms101: invalid transliteration method code") + ErrParseTransliterationMethodCode = errors.New("ivms101: could not parse transliteration method code from value") +) + +//=========================================================================== +// Validation Errors +//=========================================================================== + +func MissingField(field string) *FieldError { + return &FieldError{verb: "missing", field: field, issue: "this field is required"} +} + +func IncorrectField(field, issue string) *FieldError { + return &FieldError{verb: "invalid field", field: field, issue: issue} +} + +func ReadOnlyField(field string) *FieldError { + return &FieldError{verb: "read-only field", field: field, issue: "this field cannot be written by the user"} +} + +func OneOfMissing(fields ...string) *FieldError { + var fieldstr string + switch len(fields) { + case 0: + panic("no fields specified for one of") + case 1: + return MissingField(fields[0]) + default: + fieldstr = fieldList(fields...) + } + + return &FieldError{verb: "missing one of", field: fieldstr, issue: "at most one of these fields is required"} +} + +func OneOfTooMany(fields ...string) *FieldError { + if len(fields) < 2 { + panic("must specify at least two fields for one of too many") + } + return &FieldError{verb: "specify only one of", field: fieldList(fields...), issue: "at most one of these fields may be specified"} +} + +func ValidationError(err error, errs ...*FieldError) error { + var verr ValidationErrors + if err == nil { + verr = make(ValidationErrors, 0, len(errs)) + } else { + var ok bool + if verr, ok = err.(ValidationErrors); !ok { + verr = make(ValidationErrors, 0, len(errs)+1) + verr = append(verr, &FieldError{verb: "invalid", field: "input", issue: err.Error()}) + } + } + + for _, e := range errs { + if e != nil { + verr = append(verr, e) + } + } + + if len(verr) == 0 { + return nil + } + return verr +} + +type ValidationErrors []*FieldError + +func (e ValidationErrors) Error() string { + if len(e) == 1 { + return e[0].Error() + } + + errs := make([]string, 0, len(e)) + for _, err := range e { + errs = append(errs, err.Error()) + } + + return fmt.Sprintf("%d validation errors occurred:\n %s", len(e), strings.Join(errs, "\n ")) +} + +type FieldError struct { + verb string + field string + issue string +} + +func (e *FieldError) Error() string { + return fmt.Sprintf("ivms101: %s %s: %s", e.verb, e.field, e.issue) +} + +func (e *FieldError) Field() string { + return e.field +} + +func fieldList(fields ...string) string { + switch len(fields) { + case 0: + return "" + case 1: + return fields[0] + case 2: + return fmt.Sprintf("%s or %s", fields[0], fields[1]) + default: + last := len(fields) - 1 + return fmt.Sprintf("%s, or %s", strings.Join(fields[0:last], ", "), fields[last]) + } +} + +//=========================================================================== +// Wrapped Error +//=========================================================================== + // Wraps one error with another error for better error type checking. type WrappedError struct { error error diff --git a/pkg/ivms101/identity.go b/pkg/ivms101/identity.go index 00fc88b..ff01c24 100644 --- a/pkg/ivms101/identity.go +++ b/pkg/ivms101/identity.go @@ -2,9 +2,9 @@ package ivms101 import "encoding/json" -// -// IdentityPayload JSON -// +//=========================================================================== +// IdentityPayload Methods +//=========================================================================== type serialIdentityPayload struct { Originator *Originator `json:"originator,omitempty"` @@ -15,6 +15,21 @@ type serialIdentityPayload struct { PayloadMetadata *PayloadMetadata `json:"payloadMetadata,omitempty"` } +var serialIdentityPayloadFields = map[string]string{ + "originator": "originator", + "originators": "originator", + "beneficiary": "beneficiary", + "beneficiaries": "beneficiary", + "originatingVASP": "originatingVASP", + "originating_vasp": "originatingVASP", + "beneficiaryVASP": "beneficiaryVASP", + "beneficiary_vasp": "beneficiaryVASP", + "transferPath": "transferPath", + "transfer_path": "transferPath", + "payloadMetadata": "payloadMetadata", + "payload_metadata": "payloadMetadata", +} + func (i *IdentityPayload) MarshalJSON() ([]byte, error) { middle := &serialIdentityPayload{ Originator: i.Originator, @@ -27,35 +42,53 @@ func (i *IdentityPayload) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (i *IdentityPayload) UnmarshalJSON(data []byte) error { +func (i *IdentityPayload) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialIdentityPayloadFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialIdentityPayload{} if err := json.Unmarshal(data, &middle); err != nil { return err } - p := IdentityPayload{ - Originator: middle.Originator, - Beneficiary: middle.Beneficiary, - OriginatingVasp: middle.OriginatingVASP, - BeneficiaryVasp: middle.BeneficiaryVASP, - TransferPath: middle.TransferPath, - PayloadMetadata: middle.PayloadMetadata, - } + // Populate the identity payload value + i.Originator = middle.Originator + i.Beneficiary = middle.Beneficiary + i.OriginatingVasp = middle.OriginatingVASP + i.BeneficiaryVasp = middle.BeneficiaryVASP + i.TransferPath = middle.TransferPath + i.PayloadMetadata = middle.PayloadMetadata - // TODO warning: assignment copies lock value to *n - *i = p return nil } -// -// Identity Natural Persons JSON -// +//=========================================================================== +// Originator Methods +//=========================================================================== type serialOriginator struct { Originator []*Person `json:"originatorPersons,omitempty"` AccountNumbers []string `json:"accountNumber,omitempty"` } +var serialOriginatorFields = map[string]string{ + "originatorPersons": "originatorPersons", + "originatorPerson": "originatorPersons", + "originator_persons": "originatorPersons", + "originator_person": "originatorPersons", + "originator": "originatorPersons", + "originators": "originatorPersons", + "accountNumber": "accountNumber", + "accountNumbers": "accountNumber", + "account_number": "accountNumber", + "account_numbers": "accountNumber", +} + func (o *Originator) MarshalJSON() ([]byte, error) { middle := serialOriginator{ Originator: o.OriginatorPersons, @@ -64,27 +97,49 @@ func (o *Originator) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (o *Originator) UnmarshalJSON(data []byte) error { +func (o *Originator) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialOriginatorFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialOriginator{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := Originator{ - OriginatorPersons: middle.Originator, - AccountNumbers: middle.AccountNumbers, - } + // Populate originator values + o.OriginatorPersons = middle.Originator + o.AccountNumbers = middle.AccountNumbers - // TODO warning: assignment copies lock value to *n - *o = i return nil } +//=========================================================================== +// Beneficiary Methods +//=========================================================================== + type serialBeneficiary struct { Beneficiary []*Person `json:"beneficiaryPersons,omitempty"` AccountNumbers []string `json:"accountNumber,omitempty"` } +var serialBeneficiaryFields = map[string]string{ + "beneficiaryPersons": "beneficiaryPersons", + "beneficiaryPerson": "beneficiaryPersons", + "beneficiary_persons": "beneficiaryPersons", + "beneficiary_person": "beneficiaryPersons", + "beneficiary": "beneficiaryPersons", + "beneficiaries": "beneficiaryPersons", + "accountNumber": "accountNumber", + "accountNumbers": "accountNumber", + "account_number": "accountNumber", + "account_numbers": "accountNumber", +} + func (b *Beneficiary) MarshalJSON() ([]byte, error) { middle := serialBeneficiary{ Beneficiary: b.BeneficiaryPersons, @@ -93,30 +148,40 @@ func (b *Beneficiary) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (b *Beneficiary) UnmarshalJSON(data []byte) error { +func (b *Beneficiary) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialBeneficiaryFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialBeneficiary{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := Beneficiary{ - BeneficiaryPersons: middle.Beneficiary, - AccountNumbers: middle.AccountNumbers, - } + // Populate originator values + b.BeneficiaryPersons = middle.Beneficiary + b.AccountNumbers = middle.AccountNumbers - // TODO warning: assignment copies lock value to *n - *b = i return nil } -// -// Identity Legal Persons JSON -// +//=========================================================================== +// OriginatorVASP Methods +//=========================================================================== type serialOriginatorVASP struct { Originator *Person `json:"originatingVASP,omitempty"` } +var serialOriginatorVASPFields = map[string]string{ + "originatingVASP": "originatingVASP", + "originating_vasp": "originatingVASP", +} + func (o *OriginatingVasp) MarshalJSON() ([]byte, error) { middle := serialOriginatorVASP{ Originator: o.OriginatingVasp, @@ -124,25 +189,39 @@ func (o *OriginatingVasp) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (o *OriginatingVasp) UnmarshalJSON(data []byte) error { +func (o *OriginatingVasp) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialOriginatorVASPFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialOriginatorVASP{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := OriginatingVasp{ - OriginatingVasp: middle.Originator, - } + // Populate originator vasp values. + o.OriginatingVasp = middle.Originator - // TODO warning: assignment copies lock value to *n - *o = i return nil } +//=========================================================================== +// BeneficiaryVASP Methods +//=========================================================================== + type serialBeneficiaryVASP struct { Beneficiary *Person `json:"beneficiaryVASP,omitempty"` } +var serialBeneficiaryVASPFields = map[string]string{ + "beneficiaryVASP": "beneficiaryVASP", + "beneficiary_vasp": "beneficiaryVASP", +} + func (b *BeneficiaryVasp) MarshalJSON() ([]byte, error) { middle := serialBeneficiaryVASP{ Beneficiary: b.BeneficiaryVasp, @@ -150,30 +229,43 @@ func (b *BeneficiaryVasp) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (b *BeneficiaryVasp) UnmarshalJSON(data []byte) error { +func (b *BeneficiaryVasp) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialBeneficiaryVASPFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialBeneficiaryVASP{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := BeneficiaryVasp{ - BeneficiaryVasp: middle.Beneficiary, - } + // Populate beneficiary vasp values + b.BeneficiaryVasp = middle.Beneficiary - // TODO warning: assignment copies lock value to *n - *b = i return nil } -// -// Transfer Path JSON -// +//=========================================================================== +// IntermediaryVASP Methods +//=========================================================================== type serialIntermediaryVASP struct { Intermediary *Person `json:"intermediaryVASP,omitempty"` Sequence uint64 `json:"sequence,omitempty"` } +var serialIntermediaryVASPFields = map[string]string{ + "intermediaryVASP": "intermediaryVASP", + "intermediaryVASPs": "intermediaryVASP", + "intermediary_vasp": "intermediaryVASP", + "intermediary_vasps": "intermediaryVASP", + "sequence": "sequence", +} + func (v *IntermediaryVasp) MarshalJSON() ([]byte, error) { middle := serialIntermediaryVASP{ Intermediary: v.IntermediaryVasp, @@ -182,26 +274,40 @@ func (v *IntermediaryVasp) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (v *IntermediaryVasp) UnmarshalJSON(data []byte) error { +func (v *IntermediaryVasp) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialIntermediaryVASPFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialIntermediaryVASP{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := IntermediaryVasp{ - IntermediaryVasp: middle.Intermediary, - Sequence: middle.Sequence, - } + // Populate intermediary vasp values + v.IntermediaryVasp = middle.Intermediary + v.Sequence = middle.Sequence - // TODO warning: assignment copies lock value to *n - *v = i return nil } +//=========================================================================== +// TransferPath Methods +//=========================================================================== + type serialTransferPath struct { TransferPath []*IntermediaryVasp `json:"transferPath,omitempty"` } +var serialTransferPathFields = map[string]string{ + "transferPath": "transferPath", + "transfer_path": "transferPath", +} + func (p *TransferPath) MarshalJSON() ([]byte, error) { middle := serialTransferPath{ TransferPath: p.TransferPath, @@ -209,29 +315,42 @@ func (p *TransferPath) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (p *TransferPath) UnmarshalJSON(data []byte) error { +func (p *TransferPath) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialTransferPathFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialTransferPath{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := TransferPath{ - TransferPath: middle.TransferPath, - } + // Populate transfer path values + p.TransferPath = middle.TransferPath - // TODO warning: assignment copies lock value to *n - *p = i return nil } -// -// Payload Metadata JSON -// +//=========================================================================== +// PayloadMetadata Methods +//=========================================================================== type serialPayloadMetadata struct { TransliterationMethod []TransliterationMethodCode `json:"transliterationMethod,omitempty"` } +var serialPayloadMetadataFields = map[string]string{ + "transliterationMethod": "transliterationMethod", + "transliteration_method": "transliterationMethod", + "transliterationMethods": "transliterationMethod", + "transliteration_methods": "transliterationMethod", + "methods": "transliterationMethod", +} + func (p *PayloadMetadata) MarshalJSON() ([]byte, error) { middle := serialPayloadMetadata{ TransliterationMethod: p.TransliterationMethod, @@ -239,17 +358,22 @@ func (p *PayloadMetadata) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (p *PayloadMetadata) UnmarshalJSON(data []byte) error { +func (p *PayloadMetadata) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialPayloadMetadataFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialPayloadMetadata{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := PayloadMetadata{ - TransliterationMethod: middle.TransliterationMethod, - } + // Populate payload metadata values + p.TransliterationMethod = middle.TransliterationMethod - // TODO warning: assignment copies lock value to *n - *p = i return nil } diff --git a/pkg/ivms101/identity_test.go b/pkg/ivms101/identity_test.go index df545be..2d2da8f 100644 --- a/pkg/ivms101/identity_test.go +++ b/pkg/ivms101/identity_test.go @@ -2,11 +2,9 @@ package ivms101_test import ( "encoding/json" - "fmt" "os" "testing" - "github.com/nsf/jsondiff" "github.com/stretchr/testify/require" "github.com/trisacrypto/trisa/pkg/ivms101" "google.golang.org/protobuf/encoding/protojson" @@ -26,10 +24,7 @@ func TestIdentityPayloadSerialization(t *testing.T) { out, err := json.Marshal(identity) require.NoError(t, err, "could not marshal identity payload to JSON") - fmt.Println(string(out)) - diffOpts := jsondiff.DefaultConsoleOptions() - res, _ := jsondiff.Compare(in, out, &diffOpts) - require.Equal(t, res, jsondiff.FullMatch, "marshalled json differs from original") + require.JSONEq(t, string(in), string(out)) } func TestIdentityPayloadSerializationFromPB(t *testing.T) { diff --git a/pkg/ivms101/ivms101.go b/pkg/ivms101/ivms101.go index d7f2a1f..0ba48dc 100644 --- a/pkg/ivms101/ivms101.go +++ b/pkg/ivms101/ivms101.go @@ -2,18 +2,24 @@ package ivms101 import ( "encoding/json" - "errors" ) -// -// Person JSON -// +//=========================================================================== +// Person Methods +//=========================================================================== type serialPerson struct { NaturalPerson *NaturalPerson `json:"naturalPerson,omitempty"` LegalPerson *LegalPerson `json:"legalPerson,omitempty"` } +var serialPersonKeys = map[string]string{ + "naturalPerson": "naturalPerson", + "natural_person": "naturalPerson", + "legalPerson": "legalPerson", + "legal_person": "legalPerson", +} + func (p *Person) MarshalJSON() ([]byte, error) { middle := serialPerson{ NaturalPerson: p.GetNaturalPerson(), @@ -22,35 +28,42 @@ func (p *Person) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (p *Person) UnmarshalJSON(data []byte) error { - middle := serialPerson{} - if err := json.Unmarshal(data, &middle); err != nil { +func (p *Person) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialPersonKeys); err != nil { + return err + } + } + + // Unmarshal middle data structure + middle := &serialPerson{} + if err = json.Unmarshal(data, &middle); err != nil { return err } + // Check oneof constraint if middle.NaturalPerson != nil && middle.LegalPerson != nil { - return errors.New("person object cannot be both a natural and legal person") + return ErrPersonOneOfViolation } - i := Person{} + // Populate the person value switch { case middle.NaturalPerson != nil: - i.Person = &Person_NaturalPerson{ + p.Person = &Person_NaturalPerson{ NaturalPerson: middle.NaturalPerson, } case middle.LegalPerson != nil: - i.Person = &Person_LegalPerson{ + p.Person = &Person_LegalPerson{ LegalPerson: middle.LegalPerson, } } - - *p = i return nil } -// -// NaturalPerson JSON -// +//=========================================================================== +// NaturalPerson Methods +//=========================================================================== type serialNaturalPerson struct { Name *NaturalPersonName `json:"name,omitempty"` @@ -61,6 +74,27 @@ type serialNaturalPerson struct { CountryOfResidence string `json:"countryOfResidence,omitempty"` } +var serialNaturalPersonKeys = map[string]string{ + "name": "name", + "names": "name", + "geographicAddress": "geographicAddress", + "geographicAddresses": "geographicAddress", + "geographic_address": "geographicAddress", + "geographic_addresses": "geographicAddress", + "addresses": "geographicAddress", + "address": "geographicAddress", + "nationalIdentification": "nationalIdentification", + "national_identification": "nationalIdentification", + "customerIdentification": "customerIdentification", + "customer_identification": "customerIdentification", + "dateAndPlaceOfBirth": "dateAndPlaceOfBirth", + "date_and_place_of_birth": "dateAndPlaceOfBirth", + "dob": "dateAndPlaceOfBirth", + "countryOfResidence": "countryOfResidence", + "country_of_residence": "countryOfResidence", + "country": "countryOfResidence", +} + func (n *NaturalPerson) MarshalJSON() ([]byte, error) { middle := serialNaturalPerson{ Name: n.Name, @@ -73,36 +107,56 @@ func (n *NaturalPerson) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (n *NaturalPerson) UnmarshalJSON(data []byte) error { +func (n *NaturalPerson) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialNaturalPersonKeys); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialNaturalPerson{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := NaturalPerson{ - Name: middle.Name, - GeographicAddresses: middle.Address, - NationalIdentification: middle.Identification, - CustomerIdentification: middle.CustomerID, - DateAndPlaceOfBirth: middle.DOB, - CountryOfResidence: middle.CountryOfResidence, - } + // 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 - // TODO warning: assignment copies lock value to *n - *n = i return nil } -// -// NaturalPersonName and NaturalPersonNameIdentifiers JSON -// +//=========================================================================== +// NaturalPersonNameIdentifiers Methods +//=========================================================================== type serialNaturalPersonName struct { - LocalNameIdentifiers []*LocalNaturalPersonNameId `json:"localNameIdentifier,omitempty"` NameIdentifiers []*NaturalPersonNameId `json:"nameIdentifier,omitempty"` + LocalNameIdentifiers []*LocalNaturalPersonNameId `json:"localNameIdentifier,omitempty"` PhoneticNameIdentifiers []*LocalNaturalPersonNameId `json:"phoneticNameIdentifier,omitempty"` } +var serialNaturalPersonNameFields = map[string]string{ + "nameIdentifier": "nameIdentifier", + "nameIdentifiers": "nameIdentifier", + "name_identifier": "nameIdentifier", + "name_identifiers": "nameIdentifier", + "localNameIdentifier": "localNameIdentifier", + "localNameIdentifiers": "localNameIdentifier", + "local_name_identifier": "localNameIdentifier", + "local_name_identifiers": "localNameIdentifier", + "phoneticNameIdentifier": "phoneticNameIdentifier", + "phoneticNameIdentifiers": "phoneticNameIdentifier", + "phonetic_name_identifier": "phoneticNameIdentifier", + "phonetic_name_identifiers": "phoneticNameIdentifier", +} + func (n *NaturalPersonName) MarshalJSON() ([]byte, error) { middle := serialNaturalPersonName{ LocalNameIdentifiers: n.LocalNameIdentifiers, @@ -112,31 +166,56 @@ func (n *NaturalPersonName) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (n *NaturalPersonName) UnmarshalJSON(data []byte) error { +func (n *NaturalPersonName) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialNaturalPersonNameFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialNaturalPersonName{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := NaturalPersonName{ - NameIdentifiers: middle.NameIdentifiers, - LocalNameIdentifiers: middle.LocalNameIdentifiers, - PhoneticNameIdentifiers: middle.PhoneticNameIdentifiers, - } + // Populate the natural person name value + n.NameIdentifiers = middle.NameIdentifiers + n.LocalNameIdentifiers = middle.LocalNameIdentifiers + n.PhoneticNameIdentifiers = middle.PhoneticNameIdentifiers - // TODO warning: assignment copies lock value to *n - *n = i return nil } -type serialNaturalPersonNameId struct { +//=========================================================================== +// NaturalPersonNameID Methods +//=========================================================================== + +type serialNaturalPersonNameID struct { PrimaryIdentifier string `json:"primaryIdentifier,omitempty"` SecondaryIdentifier string `json:"secondaryIdentifier,omitempty"` NameIdentifierType NaturalPersonNameTypeCode `json:"nameIdentifierType,omitempty"` } +var serialNaturalPersonNameIDFields = map[string]string{ + "primaryIdentifier": "primaryIdentifier", + "primary_identifier": "primaryIdentifier", + "last_name": "primaryIdentifier", + "lastName": "primaryIdentifier", + "surname": "primaryIdentifier", + "family_name": "primaryIdentifier", + "familyName": "primaryIdentifier", + "secondaryIdentifier": "secondaryIdentifier", + "secondary_identifier": "secondaryIdentifier", + "first_name": "secondaryIdentifier", + "firstName": "secondaryIdentifier", + "nameIdentifierType": "nameIdentifierType", + "name_identifier_type": "nameIdentifierType", +} + func (p *NaturalPersonNameId) MarshalJSON() ([]byte, error) { - middle := serialNaturalPersonNameId{ + middle := serialNaturalPersonNameID{ PrimaryIdentifier: p.PrimaryIdentifier, SecondaryIdentifier: p.SecondaryIdentifier, NameIdentifierType: p.NameIdentifierType, @@ -144,31 +223,49 @@ func (p *NaturalPersonNameId) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (p *NaturalPersonNameId) UnmarshalJSON(data []byte) error { - middle := serialNaturalPersonNameId{} +func (p *NaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialNaturalPersonNameIDFields); err != nil { + return err + } + } + + // Unmarshal middle data structure + middle := serialNaturalPersonNameID{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := NaturalPersonNameId{ - PrimaryIdentifier: middle.PrimaryIdentifier, - SecondaryIdentifier: middle.SecondaryIdentifier, - NameIdentifierType: middle.NameIdentifierType, - } + // Populate the natural person name id values + p.PrimaryIdentifier = middle.PrimaryIdentifier + p.SecondaryIdentifier = middle.SecondaryIdentifier + p.NameIdentifierType = middle.NameIdentifierType - // TODO warning: assignment copies lock value to *p - *p = i return nil } -type serialLocalNaturalPersonNameId struct { +//=========================================================================== +// LocalNaturalPersonNameID Methods +//=========================================================================== + +type serialLocalNaturalPersonNameID struct { PrimaryIdentifier string `json:"primaryIdentifier,omitempty"` SecondaryIdentifier string `json:"secondaryIdentifier,omitempty"` NameIdentifierType NaturalPersonNameTypeCode `json:"nameIdentifierType,omitempty"` } +var serialLocalNaturalPersonNameIDFields = map[string]string{ + "primaryIdentifier": "primaryIdentifier", + "primary_identifier": "primaryIdentifier", + "secondaryIdentifier": "secondaryIdentifier", + "secondary_identifier": "secondaryIdentifier", + "nameIdentifierType": "nameIdentifierType", + "name_identifier_type": "nameIdentifierType", +} + func (p *LocalNaturalPersonNameId) MarshalJSON() ([]byte, error) { - middle := serialLocalNaturalPersonNameId{ + middle := serialLocalNaturalPersonNameID{ PrimaryIdentifier: p.PrimaryIdentifier, SecondaryIdentifier: p.SecondaryIdentifier, NameIdentifierType: p.NameIdentifierType, @@ -176,26 +273,31 @@ func (p *LocalNaturalPersonNameId) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (p *LocalNaturalPersonNameId) UnmarshalJSON(data []byte) error { - middle := serialLocalNaturalPersonNameId{} +func (p *LocalNaturalPersonNameId) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialLocalNaturalPersonNameIDFields); err != nil { + return err + } + } + + // Unmarshal middle data structure + middle := serialLocalNaturalPersonNameID{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := LocalNaturalPersonNameId{ - PrimaryIdentifier: middle.PrimaryIdentifier, - SecondaryIdentifier: middle.SecondaryIdentifier, - NameIdentifierType: middle.NameIdentifierType, - } + // Populate the natural person name id values + p.PrimaryIdentifier = middle.PrimaryIdentifier + p.SecondaryIdentifier = middle.SecondaryIdentifier + p.NameIdentifierType = middle.NameIdentifierType - // TODO warning: assignment copies lock value to *p - *p = i return nil } -// -// Address JSON -// +//=========================================================================== +// Address Methods +//=========================================================================== type serialAddress struct { AddressType AddressTypeCode `json:"addressType,omitempty"` @@ -216,6 +318,57 @@ type serialAddress struct { Country string `json:"country,omitempty"` } +var serialAddressFields = map[string]string{ + "addressType": "addressType", + "address_type": "addressType", + "department": "department", + "subDepartment": "subDepartment", + "sub_department": "subDepartment", + "subdepartment": "subDepartment", + "streetName": "streetName", + "street_name": "streetName", + "street": "streetName", + "buildingNumber": "buildingNumber", + "building_number": "buildingNumber", + "number": "buildingNumber", + "buildingName": "buildingName", + "building_name": "buildingName", + "building": "buildingName", + "floor": "floor", + "postBox": "postBox", + "post_box": "postBox", + "pob": "postBox", + "room": "room", + "postCode": "postCode", + "post_code": "postCode", + "postalCode": "postCode", + "postal_code": "postCode", + "zipCode": "postCode", + "zip_code": "postCode", + "townName": "townName", + "town_name": "townName", + "town": "townName", + "city": "townName", + "townLocationName": "townLocationName", + "town_location_name": "townLocationName", + "locationName": "townLocationName", + "location_name": "townLocationName", + "districtName": "districtName", + "district_name": "districtName", + "district": "districtName", + "countrySubDivision": "countrySubDivision", + "country_sub_division": "countrySubDivision", + "country_Subdivision": "countrySubDivision", + "country_subdivision": "countrySubDivision", + "state": "countrySubDivision", + "province": "countrySubDivision", + "addressLine": "addressLine", + "addressLines": "addressLine", + "address_line": "addressLine", + "address_lines": "addressLine", + "country": "country", +} + func (a *Address) MarshalJSON() ([]byte, error) { middle := serialAddress{ AddressType: a.AddressType, @@ -238,45 +391,59 @@ func (a *Address) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (a *Address) UnmarshalJSON(data []byte) error { +func (a *Address) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialAddressFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialAddress{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := Address{ - AddressType: middle.AddressType, - Department: middle.Department, - SubDepartment: middle.SubDepartment, - StreetName: middle.StreetName, - BuildingNumber: middle.BuildingNumber, - BuildingName: middle.BuildingName, - Floor: middle.Floor, - PostBox: middle.PostBox, - Room: middle.Room, - PostCode: middle.PostCode, - TownName: middle.TownName, - TownLocationName: middle.TownLocationName, - DistrictName: middle.DistrictName, - CountrySubDivision: middle.CountrySubDivision, - AddressLine: middle.AddressLine, - Country: middle.Country, - } - - // TODO warning: assignment copies lock value to *a - *a = i + // Populate address values + a.AddressType = middle.AddressType + a.Department = middle.Department + a.SubDepartment = middle.SubDepartment + a.StreetName = middle.StreetName + a.BuildingNumber = middle.BuildingNumber + a.BuildingName = middle.BuildingName + a.Floor = middle.Floor + a.PostBox = middle.PostBox + a.Room = middle.Room + a.PostCode = middle.PostCode + a.TownName = middle.TownName + a.TownLocationName = middle.TownLocationName + a.DistrictName = middle.DistrictName + a.CountrySubDivision = middle.CountrySubDivision + a.AddressLine = middle.AddressLine + a.Country = middle.Country + return nil } -// -// DateAndPlaceOfBirth JSON -// +//=========================================================================== +// DateAndPlaceOfBirth Methods +//=========================================================================== type serialDateAndPlaceOfBirth struct { DateOfBirth string `json:"dateOfBirth,omitempty"` PlaceOfBirth string `json:"placeOfBirth,omitempty"` } +var serialDateAndPlaceOfBirthFields = map[string]string{ + "dateOfBirth": "dateOfBirth", + "date_of_birth": "dateOfBirth", + "dob": "dateOfBirth", + "placeOfBirth": "placeOfBirth", + "place_of_birth": "placeOfBirth", + "pob": "placeOfBirth", +} + func (d *DateAndPlaceOfBirth) MarshalJSON() ([]byte, error) { middle := serialDateAndPlaceOfBirth{ DateOfBirth: d.DateOfBirth, @@ -285,25 +452,30 @@ func (d *DateAndPlaceOfBirth) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (d *DateAndPlaceOfBirth) UnmarshalJSON(data []byte) error { +func (d *DateAndPlaceOfBirth) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialDateAndPlaceOfBirthFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialDateAndPlaceOfBirth{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := DateAndPlaceOfBirth{ - DateOfBirth: middle.DateOfBirth, - PlaceOfBirth: middle.PlaceOfBirth, - } + // Populate date and place of birth values + d.DateOfBirth = middle.DateOfBirth + d.PlaceOfBirth = middle.PlaceOfBirth - // TODO warning: assignment copies lock value to *d - *d = i return nil } -// -// NationalIdentification JSON -// +//=========================================================================== +// NationalIdentification Methods +//=========================================================================== type serialNationalIdentification struct { NationalIdentifier string `json:"nationalIdentifier,omitempty"` @@ -312,6 +484,22 @@ type serialNationalIdentification struct { RegistrationAuthority string `json:"registrationAuthority,omitempty"` } +var serialNationalIdentificationFields = map[string]string{ + "nationalIdentifier": "nationalIdentifier", + "national_identifier": "nationalIdentifier", + "number": "nationalIdentifier", + "identifierNumber": "nationalIdentifier", + "identifier_number": "nationalIdentifier", + "nationalIdentifierType": "nationalIdentifierType", + "national_identifier_type": "nationalIdentifierType", + "countryOfIssue": "countryOfIssue", + "country_of_issue": "countryOfIssue", + "country": "countryOfIssue", + "registrationAuthority": "registrationAuthority", + "registration_authority": "registrationAuthority", + "ra": "registrationAuthority", +} + func (n *NationalIdentification) MarshalJSON() ([]byte, error) { middle := serialNationalIdentification{ NationalIdentifier: n.NationalIdentifier, @@ -322,27 +510,32 @@ func (n *NationalIdentification) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (n *NationalIdentification) UnmarshalJSON(data []byte) error { +func (n *NationalIdentification) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialNationalIdentificationFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialNationalIdentification{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := NationalIdentification{ - NationalIdentifier: middle.NationalIdentifier, - NationalIdentifierType: middle.NationalIdentifierType, - CountryOfIssue: middle.CountryOfIssue, - RegistrationAuthority: middle.RegistrationAuthority, - } + // Populate national identification values + n.NationalIdentifier = middle.NationalIdentifier + n.NationalIdentifierType = middle.NationalIdentifierType + n.CountryOfIssue = middle.CountryOfIssue + n.RegistrationAuthority = middle.RegistrationAuthority - // TODO warning: assignment copies lock value to *n - *n = i return nil } -// -// LegalPerson JSON -// +//=========================================================================== +// LegalPerson Methods +//=========================================================================== type serialLegalPerson struct { Name *LegalPersonName `json:"name,omitempty"` @@ -352,6 +545,24 @@ type serialLegalPerson struct { CountryOfRegistration string `json:"countryOfRegistration,omitempty"` } +var serialLegalPersonFields = map[string]string{ + "name": "name", + "names": "name", + "geographicAddress": "geographicAddress", + "geographicAddresses": "geographicAddress", + "geographic_address": "geographicAddress", + "geographic_addresses": "geographicAddress", + "addresses": "geographicAddress", + "address": "geographicAddress", + "customerNumber": "customerNumber", + "customer_number": "customerNumber", + "nationalIdentification": "nationalIdentification", + "national_identification": "nationalIdentification", + "countryOfRegistration": "countryOfRegistration", + "country_of_registration": "countryOfRegistration", + "country": "countryOfRegistration", +} + func (l *LegalPerson) MarshalJSON() ([]byte, error) { middle := serialLegalPerson{ Name: l.Name, @@ -364,34 +575,54 @@ func (l *LegalPerson) MarshalJSON() ([]byte, error) { } func (l *LegalPerson) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialLegalPersonFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialLegalPerson{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := LegalPerson{ - Name: middle.Name, - GeographicAddresses: middle.Address, - CustomerNumber: middle.CustomerNumber, - NationalIdentification: middle.NationalIdentification, - CountryOfRegistration: middle.CountryOfRegistration, - } + // Populate legal person values + l.Name = middle.Name + l.GeographicAddresses = middle.Address + l.CustomerNumber = middle.CustomerNumber + l.NationalIdentification = middle.NationalIdentification + l.CountryOfRegistration = middle.CountryOfRegistration - // TODO warning: assignment copies lock value to *n - *l = i return nil } -// -// LegalPersonName and LegalPersonNameIdentifiers JSON -// +//=========================================================================== +// LegalPersonName Methods +//=========================================================================== type serialLegalPersonName struct { - LocalNameIdentifiers []*LocalLegalPersonNameId `json:"localNameIdentifier,omitempty"` NameIdentifiers []*LegalPersonNameId `json:"nameIdentifier,omitempty"` + LocalNameIdentifiers []*LocalLegalPersonNameId `json:"localNameIdentifier,omitempty"` PhoneticNameIdentifiers []*LocalLegalPersonNameId `json:"phoneticNameIdentifier,omitempty"` } +var serialLegalPersonNameFields = map[string]string{ + "nameIdentifier": "nameIdentifier", + "nameIdentifiers": "nameIdentifier", + "name_identifier": "nameIdentifier", + "name_identifiers": "nameIdentifier", + "localNameIdentifier": "localNameIdentifier", + "localNameIdentifiers": "localNameIdentifier", + "local_name_identifier": "localNameIdentifier", + "local_name_identifiers": "localNameIdentifier", + "phoneticNameIdentifier": "phoneticNameIdentifier", + "phoneticNameIdentifiers": "phoneticNameIdentifier", + "phonetic_name_identifier": "phoneticNameIdentifier", + "phonetic_name_identifiers": "phoneticNameIdentifier", +} + func (l *LegalPersonName) MarshalJSON() ([]byte, error) { middle := serialLegalPersonName{ LocalNameIdentifiers: l.LocalNameIdentifiers, @@ -401,77 +632,116 @@ func (l *LegalPersonName) MarshalJSON() ([]byte, error) { return json.Marshal(middle) } -func (l *LegalPersonName) UnmarshalJSON(data []byte) error { +func (l *LegalPersonName) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialLegalPersonNameFields); err != nil { + return err + } + } + + // Unmarshal middle data structure middle := serialLegalPersonName{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := LegalPersonName{ - NameIdentifiers: middle.NameIdentifiers, - LocalNameIdentifiers: middle.LocalNameIdentifiers, - PhoneticNameIdentifiers: middle.PhoneticNameIdentifiers, - } + // Populate legal person values + l.NameIdentifiers = middle.NameIdentifiers + l.LocalNameIdentifiers = middle.LocalNameIdentifiers + l.PhoneticNameIdentifiers = middle.PhoneticNameIdentifiers - // TODO warning: assignment copies lock value to *l - *l = i return nil } -type serialLegalPersonNameId struct { +//=========================================================================== +// LegalPersonNameID Methods +//=========================================================================== + +type serialLegalPersonNameID struct { LegalPersonName string `json:"legalPersonName,omitempty"` LegalPersonNameIdentifierType LegalPersonNameTypeCode `json:"legalPersonNameIdentifierType,omitempty"` } +var serialLegalPersonNameIDFields = map[string]string{ + "legalPersonName": "legalPersonName", + "legal_person_name": "legalPersonName", + "name": "legalPersonName", + "legalPersonNameIdentifierType": "legalPersonNameIdentifierType", + "legal_person_name_identifier_type": "legalPersonNameIdentifierType", +} + func (p *LegalPersonNameId) MarshalJSON() ([]byte, error) { - middle := serialLegalPersonNameId{ + middle := serialLegalPersonNameID{ LegalPersonName: p.LegalPersonName, LegalPersonNameIdentifierType: p.LegalPersonNameIdentifierType, } return json.Marshal(middle) } -func (p *LegalPersonNameId) UnmarshalJSON(data []byte) error { - middle := serialLegalPersonNameId{} +func (p *LegalPersonNameId) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialLegalPersonNameIDFields); err != nil { + return err + } + } + + // Unmarshal middle data structure + middle := serialLegalPersonNameID{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := LegalPersonNameId{ - LegalPersonName: middle.LegalPersonName, - LegalPersonNameIdentifierType: middle.LegalPersonNameIdentifierType, - } + // Populate legal person values + p.LegalPersonName = middle.LegalPersonName + p.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType - // TODO warning: assignment copies lock value to *p - *p = i return nil } -type serialLocalLegalPersonNameId struct { +//=========================================================================== +// LocalLegalPersonNameID Methods +//=========================================================================== + +type serialLocalLegalPersonNameID struct { LegalPersonName string `json:"legalPersonName,omitempty"` LegalPersonNameIdentifierType LegalPersonNameTypeCode `json:"legalPersonNameIdentifierType,omitempty"` } +var serialLocalLegalPersonNameIDFields = map[string]string{ + "legalPersonName": "legalPersonName", + "legal_person_name": "legalPersonName", + "name": "legalPersonName", + "legalPersonNameIdentifierType": "legalPersonNameIdentifierType", + "legal_person_name_identifier_type": "legalPersonNameIdentifierType", +} + func (p *LocalLegalPersonNameId) MarshalJSON() ([]byte, error) { - middle := serialLocalLegalPersonNameId{ + middle := serialLocalLegalPersonNameID{ LegalPersonName: p.LegalPersonName, LegalPersonNameIdentifierType: p.LegalPersonNameIdentifierType, } return json.Marshal(middle) } -func (p *LocalLegalPersonNameId) UnmarshalJSON(data []byte) error { - middle := serialLocalLegalPersonNameId{} +func (p *LocalLegalPersonNameId) UnmarshalJSON(data []byte) (err error) { + // Perform rekeying operation + if allowRekeying { + if data, err = Rekey(data, serialLocalLegalPersonNameIDFields); err != nil { + return err + } + } + + // Unmarshal middle data structure + middle := serialLocalLegalPersonNameID{} if err := json.Unmarshal(data, &middle); err != nil { return err } - i := LocalLegalPersonNameId{ - LegalPersonName: middle.LegalPersonName, - LegalPersonNameIdentifierType: middle.LegalPersonNameIdentifierType, - } + // Populate legal person values + p.LegalPersonName = middle.LegalPersonName + p.LegalPersonNameIdentifierType = middle.LegalPersonNameIdentifierType - // TODO warning: assignment copies lock value to *p - *p = i return nil } diff --git a/pkg/ivms101/ivms101_test.go b/pkg/ivms101/ivms101_test.go index 168c742..89a7836 100644 --- a/pkg/ivms101/ivms101_test.go +++ b/pkg/ivms101/ivms101_test.go @@ -35,7 +35,7 @@ func TestPersonMarshaling(t *testing.T) { require.NoError(t, err, "could not load person with both persons json fixture") person = &ivms101.Person{} - require.EqualError(t, json.Unmarshal(data, person), "person object cannot be both a natural and legal person") + require.ErrorIs(t, json.Unmarshal(data, person), ivms101.ErrPersonOneOfViolation) } // @@ -85,10 +85,10 @@ func TestMarshalNatName(t *testing.T) { NameIdentifiers: []*ivms101.NaturalPersonNameId{name}, LocalNameIdentifiers: []*ivms101.LocalNaturalPersonNameId{birthname}, } - expected := []byte(`{"localNameIdentifier":[{"primaryIdentifier":"kal","secondaryIdentifier":"el","nameIdentifierType":"BIRT"}],"nameIdentifier":[{"primaryIdentifier":"superman","nameIdentifierType":"LEGL"}]}`) + expected := `{"localNameIdentifier":[{"primaryIdentifier":"kal","secondaryIdentifier":"el","nameIdentifierType":"BIRT"}],"nameIdentifier":[{"primaryIdentifier":"superman","nameIdentifierType":"LEGL"}]}` compat, err := json.Marshal(n) require.Nil(t, err) - require.Equal(t, expected, compat) + require.JSONEq(t, expected, string(compat)) } func TestUnmarshalNatName(t *testing.T) { @@ -293,10 +293,10 @@ func TestMarshalLegName(t *testing.T) { NameIdentifiers: []*ivms101.LegalPersonNameId{name}, LocalNameIdentifiers: []*ivms101.LocalLegalPersonNameId{tradename}, } - expected := []byte(`{"localNameIdentifier":[{"legalPersonName":"animaniacs","legalPersonNameIdentifierType":"TRAD"}],"nameIdentifier":[{"legalPersonName":"acme labs","legalPersonNameIdentifierType":"LEGL"}]}`) + expected := `{"localNameIdentifier":[{"legalPersonName":"animaniacs","legalPersonNameIdentifierType":"TRAD"}],"nameIdentifier":[{"legalPersonName":"acme labs","legalPersonNameIdentifierType":"LEGL"}]}` compat, err := json.Marshal(n) require.Nil(t, err) - require.Equal(t, expected, compat) + require.JSONEq(t, expected, string(compat)) } func TestUnmarshalLegName(t *testing.T) { diff --git a/pkg/ivms101/rekey.go b/pkg/ivms101/rekey.go new file mode 100644 index 0000000..7a66479 --- /dev/null +++ b/pkg/ivms101/rekey.go @@ -0,0 +1,66 @@ +package ivms101 + +import ( + "encoding/json" + "fmt" +) + +var ( + allowRekeying bool + disallowUnknownFields bool +) + +// Rekeying sets the module to perform a rekeying operation that changes snake_case and +// pluralized keys to the OpenVASP compatible keys in a JSON payload. This incurs some +// extra cost when unmarshaling but can make loading IVMS101 JSON data more flexible. +// +// Rekeying is false by default. +func AllowRekeying() { + allowRekeying = true +} + +// Turns of rekeying during JSON unmarshaling. +func DisallowRekeying() { + allowRekeying = false + disallowUnknownFields = false +} + +// DisallowUnknownFields will return an error when an unknown field is part of the JSON +// message; this may make data validation easier. +// +// DisallowUnknownFields is false by default. Rekeying must be allowed to disallow +// unknown fields and rekeying is automatically set to true when this method is called. +func DisallowUnknownFields() { + allowRekeying = true + disallowUnknownFields = true +} + +// Turns of disallow unknown fields checking during JSON unmarshaling. +func AllowUnknownFields() { + disallowUnknownFields = false +} + +func Rekey(data []byte, keyMap map[string]string) (_ []byte, err error) { + if !allowRekeying { + return data, nil + } + + var message map[string]json.RawMessage + if err = json.Unmarshal(data, &message); err != nil { + return nil, err + } + + for key, raw := range message { + rekey, ok := keyMap[key] + if !ok { + if disallowUnknownFields { + return nil, fmt.Errorf("json: unknown field %q", key) + } + continue + } + + message[rekey] = raw + } + + return json.Marshal(message) +} diff --git a/pkg/ivms101/rekey_test.go b/pkg/ivms101/rekey_test.go new file mode 100644 index 0000000..a0dc2d8 --- /dev/null +++ b/pkg/ivms101/rekey_test.go @@ -0,0 +1,126 @@ +package ivms101_test + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/trisacrypto/trisa/pkg/ivms101" +) + +func TestRekeying(t *testing.T) { + ivms101.AllowRekeying() + t.Cleanup(ivms101.DisallowRekeying) + + expected, err := os.ReadFile("testdata/identity_payload.json") + require.NoError(t, err, "could not load testdata/identity_payload.json") + + t.Run("NoModification", func(t *testing.T) { + f, err := os.Open("testdata/identity_payload.json") + require.NoError(t, err, "could not load testdata/identity_payload.json") + defer f.Close() + + payload := &ivms101.IdentityPayload{} + err = json.NewDecoder(f).Decode(payload) + require.NoError(t, err, "could not decode payload with rekeying") + + compat, err := json.Marshal(payload) + require.NoError(t, err, "could not marshal payload") + + require.JSONEq(t, string(expected), string(compat), "json not equal to expected") + }) + + t.Run("Modified", func(t *testing.T) { + f, err := os.Open("testdata/identity_payload_alt.json") + require.NoError(t, err, "could not load testdata/identity_payload_alt.json") + defer f.Close() + + payload := &ivms101.IdentityPayload{} + err = json.NewDecoder(f).Decode(payload) + require.NoError(t, err, "could not decode payload with rekeying") + + compat, err := json.Marshal(payload) + require.NoError(t, err, "could not marshal payload") + + require.JSONEq(t, string(expected), string(compat), "json not equal to expected") + }) + + t.Run("Protobuf", func(t *testing.T) { + f, err := os.Open("testdata/identity_payload.pb.json") + require.NoError(t, err, "could not load testdata/identity_payload.pb.json") + defer f.Close() + + payload := &ivms101.IdentityPayload{} + err = json.NewDecoder(f).Decode(payload) + require.NoError(t, err, "could not decode payload with rekeying") + }) +} + +func BenchmarkRekeying(b *testing.B) { + data, err := os.ReadFile("testdata/identity_payload.json") + require.NoError(b, err) + + b.Run("WithRekeying", func(b *testing.B) { + ivms101.AllowRekeying() + ivms101.DisallowUnknownFields() + b.Cleanup(func() { + ivms101.AllowUnknownFields() + ivms101.DisallowRekeying() + }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + payload := ivms101.IdentityPayload{} + json.Unmarshal(data, &payload) + } + }) + + b.Run("WithoutRekeying", func(b *testing.B) { + for i := 0; i < b.N; i++ { + payload := ivms101.IdentityPayload{} + json.Unmarshal(data, &payload) + } + }) +} + +func BenchmarkRekeyingType(b *testing.B) { + // Rekey1 uses a map[string]interface{} for decoding and rekeying. + rekey1 := func(b *testing.B, data []byte) { + var middle map[string]interface{} + err := json.Unmarshal(data, &middle) + assert.NoError(b, err) + + cmpt, err := json.Marshal(middle) + assert.NoError(b, err) + assert.Len(b, cmpt, 2733) + } + + // Rekey2 uses a map[string]json.RawMessage for decoding and rekeying. + // Rekey2 is the clear winner in terms of performance. + reykey2 := func(b *testing.B, data []byte) { + var middle map[string]json.RawMessage + err := json.Unmarshal(data, &middle) + assert.NoError(b, err) + + cmpt, err := json.Marshal(middle) + assert.NoError(b, err) + assert.Len(b, cmpt, 2733) + } + + data, err := os.ReadFile("testdata/identity_payload.json") + assert.NoError(b, err) + + b.Run("Rekey1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + rekey1(b, data) + } + }) + + b.Run("Rekey2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + reykey2(b, data) + } + }) +} diff --git a/pkg/ivms101/testdata/identity_payload_alt.json b/pkg/ivms101/testdata/identity_payload_alt.json new file mode 100644 index 0000000..41559af --- /dev/null +++ b/pkg/ivms101/testdata/identity_payload_alt.json @@ -0,0 +1,153 @@ +{ + "originator": { + "originatorPersons": [{ + "naturalPerson": { + "name": { + "nameIdentifiers": [{ + "primaryIdentifier": "Howard", + "secondaryIdentifier": "Jane", + "nameIdentifierType": "LEGL" + }, + { + "lastName": "Price", + "firstName": "Jane", + "nameIdentifierType": "MAID" + } + ] + }, + "geographicAddresses": [{ + "addressType": "HOME", + "street": "Greystone Street", + "number": "28", + "postalCode": "38017", + "town": "Collierville", + "state": "TN", + "country": "US" + }], + "nationalIdentification": { + "nationalIdentifier": "112502920", + "nationalIdentifierType": "SOCS", + "countryOfIssue": "US", + "ra": "RA777777" + }, + "customerIdentification": "2642", + "dateAndPlaceOfBirth": { + "dob": "1992-10-04", + "pob": "West Islip, NY" + }, + "country_of_residence": "US" + } + }], + "accountNumber": [ + "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v" + ] + }, + "beneficiary": { + "beneficiaryPersons": [{ + "naturalPerson": { + "name": { + "nameIdentifiers": [{ + "primaryIdentifier": "Clark", + "secondaryIdentifier": "Lawrence", + "nameIdentifierType": "LEGL" + }, + { + "surname": "Clark", + "firstName": "Larry", + "nameIdentifierType": "ALIA" + } + ] + }, + "geographic_addresses": [{ + "addressType": "HOME", + "street_name": "Watling St", + "building_number": "249", + "zip_code": "WD7 7AL", + "city": "Radlett", + "country": "GB" + }], + "national_identification": { + "nationalIdentifier": "319560446", + "nationalIdentifierType": "DRLC", + "country": "GB", + "registrationAuthority": "RA777777" + }, + "customer_identification": "5610", + "date_and_place_of_birth": { + "date_of_birth": "1986-12-13", + "place_of_birth": "Leeds, United Kingdom" + }, + "countryOfResidence": "GB" + } + }], + "accountNumber": [ + "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3" + ] + }, + "originatingVASP": { + "originatingVASP": { + "legalPerson": { + "name": { + "name_identifiers": [{ + "name": "AliceCoin, Inc.", + "legalPersonNameIdentifierType": "LEGL" + }, + { + "legal_person_name": "Alice VASP", + "legal_person_name_identifier_type": "SHRT" + }, + { + "legalPersonName": "AliceCoin", + "legalPersonNameIdentifierType": "TRAD" + } + ] + }, + "addresses": [{ + "address_type": "BIZZ", + "streetName": "Roosevelt Place", + "buildingNumber": "23", + "postCode": "02151", + "townName": "Boston", + "countrySubDivision": "MA", + "country": "US" + }], + "nationalIdentification": { + "national_identifier": "506700T7Z685VUOZL877", + "national_identifier_type": "LEIX" + }, + "countryOfRegistration": "US" + } + } + }, + "beneficiaryVASP": { + "beneficiaryVASP": { + "legalPerson": { + "name": { + "nameIdentifiers": [{ + "legalPersonName": "Bob's Discount VASP, PLC", + "legalPersonNameIdentifierType": "LEGL" + }, + { + "legalPersonName": "Bob VASP", + "legalPersonNameIdentifierType": "SHRT" + } + ] + }, + "geographicAddress": [{ + "addressType": "BIZZ", + "streetName": "Grimsby Road", + "buildingNumber": "762", + "postCode": "OX8 U89", + "townName": "Oxford", + "country": "GB" + }], + "nationalIdentification": { + "nationalIdentifier": "213800AQUAUP6I215N33", + "nationalIdentifierType": "TXID", + "registrationAuthority": "RA777777" + }, + "country": "GB" + } + } + } +} \ No newline at end of file