Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix for ID code & Identifier validation. Updated alphanumeric regex #348

Merged
merged 3 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions beneficiary.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,28 @@ func (ben *Beneficiary) Format(options FormatOptions) string {
// The first error encountered is returned and stops that parsing.
// If ID Code is present, Identifier is mandatory and vice versa.
func (ben *Beneficiary) Validate() error {
if err := ben.fieldInclusion(); err != nil {
return err
}
if ben.tag != TagBeneficiary {
return fieldError("tag", ErrValidTagForType, ben.tag)
}
// Can be any Identification Code
if err := ben.isIdentificationCode(ben.Personal.IdentificationCode); err != nil {
return fieldError("IdentificationCode", err, ben.Personal.IdentificationCode)

if err := ben.fieldInclusion(); err != nil {
return err
}
if err := ben.isAlphanumeric(ben.Personal.Identifier); err != nil {
return fieldError("Identifier", err, ben.Personal.Identifier)

// Per FAIM 3.0.6, Beneficiary ID code is optional.
// fieldInclusion() above already checks for the mandatory combination of IDCode & Identifier
// Here we are checking for allowed values (in IDCode) and text characters (in Identifier)
if ben.Personal.IdentificationCode != "" {
// If it is present, confirm it is a valid code
if err := ben.isIdentificationCode(ben.Personal.IdentificationCode); err != nil {
return fieldError("IdentificationCode", err, ben.Personal.IdentificationCode)
}
// Identifier text must only contain allowed characters
if err := ben.isAlphanumeric(ben.Personal.Identifier); err != nil {
return fieldError("Identifier", err, ben.Personal.Identifier)
}
}

if err := ben.isAlphanumeric(ben.Personal.Name); err != nil {
return fieldError("Name", err, ben.Personal.Name)
}
Expand Down
20 changes: 12 additions & 8 deletions beneficiary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,24 +87,28 @@ func TestBeneficiaryAddressLineThreeAlphaNumeric(t *testing.T) {
require.EqualError(t, err, fieldError("AddressLineThree", ErrNonAlphanumeric, ben.Personal.Address.AddressLineThree).Error())
}

// TestBeneficiaryIdentificationCodeRequired validates Beneficiary IdentificationCode is required
func TestBeneficiaryIdentificationCodeRequired(t *testing.T) {
// TestBeneficiaryIdentificationCodeWithNoIdentifier validates Beneficiary Identifier is required
// when IdentificationCode is present
func TestBeneficiaryIdentificationCodeWithNoIdentifier(t *testing.T) {
ben := mockBeneficiary()
ben.Personal.IdentificationCode = ""
ben.Personal.IdentificationCode = "D"
ben.Personal.Identifier = ""

err := ben.Validate()

require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
}

// TestBeneficiaryIdentifierRequired validates Beneficiary Identifier is required
func TestBeneficiaryIdentifierRequired(t *testing.T) {
// TestBeneficiaryIdentifierWithNoIdentificationCode validates Beneficiary IdentificationCode
// is required when Identifier is present
func TestBeneficiaryIdentifierWithNoIdentificationCode(t *testing.T) {
ben := mockBeneficiary()
ben.Personal.Identifier = ""
ben.Personal.IdentificationCode = ""
ben.Personal.Identifier = "1234567890ABC"

err := ben.Validate()

require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
}

// TestParseBeneficiaryWrongLength parses a wrong Beneficiary record length
Expand Down
25 changes: 17 additions & 8 deletions originator.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,28 @@ func (o *Originator) Format(options FormatOptions) string {
// Validate performs WIRE format rule checks on Originator and returns an error if not Validated
// The first error encountered is returned and stops that parsing.
func (o *Originator) Validate() error {
if err := o.fieldInclusion(); err != nil {
return err
}
if o.tag != TagOriginator {
return fieldError("tag", ErrValidTagForType, o.tag)
}
// Can be any Identification Code
if err := o.isIdentificationCode(o.Personal.IdentificationCode); err != nil {
return fieldError("IdentificationCode", err, o.Personal.IdentificationCode)

if err := o.fieldInclusion(); err != nil {
return err
}
if err := o.isAlphanumeric(o.Personal.Identifier); err != nil {
return fieldError("Identifier", err, o.Personal.Identifier)

// Per FAIM 3.0.6, Originator ID code is optional
// fieldInclusion() above already checks for the mandatory combination of IDCode & Identifier
// Here we are checking for allowed values (in IDCode) and text characters (in Identifier)
if o.Personal.IdentificationCode != "" {
// If it is present, confirm it is a valid code
if err := o.isIdentificationCode(o.Personal.IdentificationCode); err != nil {
return fieldError("IdentificationCode", err, o.Personal.IdentificationCode)
}
// Identifier text must only contain allowed characters
if err := o.isAlphanumeric(o.Personal.Identifier); err != nil {
return fieldError("Identifier", err, o.Personal.Identifier)
}
}

if err := o.isAlphanumeric(o.Personal.Name); err != nil {
return fieldError("Name", err, o.Personal.Name)
}
Expand Down
20 changes: 12 additions & 8 deletions originator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,28 @@ func TestOriginatorAddressLineThreeAlphaNumeric(t *testing.T) {
require.EqualError(t, err, fieldError("AddressLineThree", ErrNonAlphanumeric, o.Personal.Address.AddressLineThree).Error())
}

// TestOriginatorIdentificationCodeRequired validates Originator IdentificationCode is required
func TestOriginatorIdentificationCodeRequired(t *testing.T) {
// TestOriginatorIdentificationCodeWithNoIdentifier validates Originator Identifier is required
// when IdentificationCode is present
func TestOriginatorIdentificationCodeWithNoIdentifier(t *testing.T) {
o := mockOriginator()
o.Personal.IdentificationCode = ""
o.Personal.IdentificationCode = "D"
o.Personal.Identifier = ""

err := o.Validate()

require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
}

// TestOriginatorIdentifierRequired validates Originator Identifier is required
func TestOriginatorIdentifierRequired(t *testing.T) {
// TestOriginatorIdentifierWithNoIdentificationCode validates Originator IdentificationCode
// is required when Identifier is present
func TestOriginatorIdentifierWithNoIdentificationCode(t *testing.T) {
o := mockOriginator()
o.Personal.Identifier = ""
o.Personal.IdentificationCode = ""
o.Personal.Identifier = "1234567890ABC"

err := o.Validate()

require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
}

// TestParseOriginatorWrongLength parses a wrong Originator record length
Expand Down
12 changes: 8 additions & 4 deletions validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ import (

var (
// upperAlphanumericRegex = regexp.MustCompile(`[^ A-Z0-9!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`)
alphanumericRegex = regexp.MustCompile(`[^ \w!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`)
numericRegex = regexp.MustCompile(`[^0-9]`)
amountRegex = regexp.MustCompile("[^0-9,.]")

// Alpha-Numeric including spaces and special characters as defined by FAIM 3.0.6:
// . ? ! , ; : _ @ & / \ ' " ` ~ ( ) < > $ # % + - =
// NOTE: This applies to all Fedwire tags except {8200} Unstructured Addenda Info
alphanumericRegex = regexp.MustCompile(`[^ \w.?!,;:_@&/\\'"\x60~()<>$#%+-=]+`)

numericRegex = regexp.MustCompile(`[^0-9]`)
amountRegex = regexp.MustCompile("[^0-9,.]")
)

// validator is common validation and formatting of golang types to WIRE type strings
Expand All @@ -25,7 +30,6 @@ type validator struct{}
// isAlphanumeric checks if a string only contains ASCII alphanumeric characters
func (v *validator) isAlphanumeric(s string) error {
if alphanumericRegex.MatchString(s) {
// ^[ A-Za-z0-9_@./#&+-]*$/
return ErrNonAlphanumeric
}
return nil
Expand Down
8 changes: 8 additions & 0 deletions validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ func TestValidators__validateOptionFName(t *testing.T) {
require.Error(t, v.validateOptionFName(""))
require.Error(t, v.validateOptionFName(" /"))
}

func TestValidators__isAlphanumeric(t *testing.T) {
v := &validator{}

require.NoError(t, v.isAlphanumeric("Telepathic Bank (U.K.) / Acct #12345-ABC"))
require.Error(t, v.isAlphanumeric("{1100}"))
require.Error(t, v.isAlphanumeric("*"))
}