Skip to content

Commit b4470cb

Browse files
authored
Merge pull request #348 from dmtrinh/master
Fix for ID code & Identifier validation. Updated alphanumeric regex
2 parents e910ce2 + c1bcb31 commit b4470cb

6 files changed

+74
-36
lines changed

beneficiary.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,28 @@ func (ben *Beneficiary) Format(options FormatOptions) string {
131131
// The first error encountered is returned and stops that parsing.
132132
// If ID Code is present, Identifier is mandatory and vice versa.
133133
func (ben *Beneficiary) Validate() error {
134-
if err := ben.fieldInclusion(); err != nil {
135-
return err
136-
}
137134
if ben.tag != TagBeneficiary {
138135
return fieldError("tag", ErrValidTagForType, ben.tag)
139136
}
140-
// Can be any Identification Code
141-
if err := ben.isIdentificationCode(ben.Personal.IdentificationCode); err != nil {
142-
return fieldError("IdentificationCode", err, ben.Personal.IdentificationCode)
137+
138+
if err := ben.fieldInclusion(); err != nil {
139+
return err
143140
}
144-
if err := ben.isAlphanumeric(ben.Personal.Identifier); err != nil {
145-
return fieldError("Identifier", err, ben.Personal.Identifier)
141+
142+
// Per FAIM 3.0.6, Beneficiary ID code is optional.
143+
// fieldInclusion() above already checks for the mandatory combination of IDCode & Identifier
144+
// Here we are checking for allowed values (in IDCode) and text characters (in Identifier)
145+
if ben.Personal.IdentificationCode != "" {
146+
// If it is present, confirm it is a valid code
147+
if err := ben.isIdentificationCode(ben.Personal.IdentificationCode); err != nil {
148+
return fieldError("IdentificationCode", err, ben.Personal.IdentificationCode)
149+
}
150+
// Identifier text must only contain allowed characters
151+
if err := ben.isAlphanumeric(ben.Personal.Identifier); err != nil {
152+
return fieldError("Identifier", err, ben.Personal.Identifier)
153+
}
146154
}
155+
147156
if err := ben.isAlphanumeric(ben.Personal.Name); err != nil {
148157
return fieldError("Name", err, ben.Personal.Name)
149158
}

beneficiary_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,24 +87,28 @@ func TestBeneficiaryAddressLineThreeAlphaNumeric(t *testing.T) {
8787
require.EqualError(t, err, fieldError("AddressLineThree", ErrNonAlphanumeric, ben.Personal.Address.AddressLineThree).Error())
8888
}
8989

90-
// TestBeneficiaryIdentificationCodeRequired validates Beneficiary IdentificationCode is required
91-
func TestBeneficiaryIdentificationCodeRequired(t *testing.T) {
90+
// TestBeneficiaryIdentificationCodeWithNoIdentifier validates Beneficiary Identifier is required
91+
// when IdentificationCode is present
92+
func TestBeneficiaryIdentificationCodeWithNoIdentifier(t *testing.T) {
9293
ben := mockBeneficiary()
93-
ben.Personal.IdentificationCode = ""
94+
ben.Personal.IdentificationCode = "D"
95+
ben.Personal.Identifier = ""
9496

9597
err := ben.Validate()
9698

97-
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
99+
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
98100
}
99101

100-
// TestBeneficiaryIdentifierRequired validates Beneficiary Identifier is required
101-
func TestBeneficiaryIdentifierRequired(t *testing.T) {
102+
// TestBeneficiaryIdentifierWithNoIdentificationCode validates Beneficiary IdentificationCode
103+
// is required when Identifier is present
104+
func TestBeneficiaryIdentifierWithNoIdentificationCode(t *testing.T) {
102105
ben := mockBeneficiary()
103-
ben.Personal.Identifier = ""
106+
ben.Personal.IdentificationCode = ""
107+
ben.Personal.Identifier = "1234567890ABC"
104108

105109
err := ben.Validate()
106110

107-
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
111+
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
108112
}
109113

110114
// TestParseBeneficiaryWrongLength parses a wrong Beneficiary record length

originator.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,28 @@ func (o *Originator) Format(options FormatOptions) string {
130130
// Validate performs WIRE format rule checks on Originator and returns an error if not Validated
131131
// The first error encountered is returned and stops that parsing.
132132
func (o *Originator) Validate() error {
133-
if err := o.fieldInclusion(); err != nil {
134-
return err
135-
}
136133
if o.tag != TagOriginator {
137134
return fieldError("tag", ErrValidTagForType, o.tag)
138135
}
139-
// Can be any Identification Code
140-
if err := o.isIdentificationCode(o.Personal.IdentificationCode); err != nil {
141-
return fieldError("IdentificationCode", err, o.Personal.IdentificationCode)
136+
137+
if err := o.fieldInclusion(); err != nil {
138+
return err
142139
}
143-
if err := o.isAlphanumeric(o.Personal.Identifier); err != nil {
144-
return fieldError("Identifier", err, o.Personal.Identifier)
140+
141+
// Per FAIM 3.0.6, Originator ID code is optional
142+
// fieldInclusion() above already checks for the mandatory combination of IDCode & Identifier
143+
// Here we are checking for allowed values (in IDCode) and text characters (in Identifier)
144+
if o.Personal.IdentificationCode != "" {
145+
// If it is present, confirm it is a valid code
146+
if err := o.isIdentificationCode(o.Personal.IdentificationCode); err != nil {
147+
return fieldError("IdentificationCode", err, o.Personal.IdentificationCode)
148+
}
149+
// Identifier text must only contain allowed characters
150+
if err := o.isAlphanumeric(o.Personal.Identifier); err != nil {
151+
return fieldError("Identifier", err, o.Personal.Identifier)
152+
}
145153
}
154+
146155
if err := o.isAlphanumeric(o.Personal.Name); err != nil {
147156
return fieldError("Name", err, o.Personal.Name)
148157
}

originator_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,28 @@ func TestOriginatorAddressLineThreeAlphaNumeric(t *testing.T) {
9090
require.EqualError(t, err, fieldError("AddressLineThree", ErrNonAlphanumeric, o.Personal.Address.AddressLineThree).Error())
9191
}
9292

93-
// TestOriginatorIdentificationCodeRequired validates Originator IdentificationCode is required
94-
func TestOriginatorIdentificationCodeRequired(t *testing.T) {
93+
// TestOriginatorIdentificationCodeWithNoIdentifier validates Originator Identifier is required
94+
// when IdentificationCode is present
95+
func TestOriginatorIdentificationCodeWithNoIdentifier(t *testing.T) {
9596
o := mockOriginator()
96-
o.Personal.IdentificationCode = ""
97+
o.Personal.IdentificationCode = "D"
98+
o.Personal.Identifier = ""
9799

98100
err := o.Validate()
99101

100-
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
102+
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
101103
}
102104

103-
// TestOriginatorIdentifierRequired validates Originator Identifier is required
104-
func TestOriginatorIdentifierRequired(t *testing.T) {
105+
// TestOriginatorIdentifierWithNoIdentificationCode validates Originator IdentificationCode
106+
// is required when Identifier is present
107+
func TestOriginatorIdentifierWithNoIdentificationCode(t *testing.T) {
105108
o := mockOriginator()
106-
o.Personal.Identifier = ""
109+
o.Personal.IdentificationCode = ""
110+
o.Personal.Identifier = "1234567890ABC"
107111

108112
err := o.Validate()
109113

110-
require.EqualError(t, err, fieldError("Identifier", ErrFieldRequired).Error())
114+
require.EqualError(t, err, fieldError("IdentificationCode", ErrFieldRequired).Error())
111115
}
112116

113117
// TestParseOriginatorWrongLength parses a wrong Originator record length

validators.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ import (
1414

1515
var (
1616
// upperAlphanumericRegex = regexp.MustCompile(`[^ A-Z0-9!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`)
17-
alphanumericRegex = regexp.MustCompile(`[^ \w!"#$%&'()*+,-.\\/:;<>=?@\[\]^_{}|~]+`)
18-
numericRegex = regexp.MustCompile(`[^0-9]`)
19-
amountRegex = regexp.MustCompile("[^0-9,.]")
17+
18+
// Alpha-Numeric including spaces and special characters as defined by FAIM 3.0.6:
19+
// . ? ! , ; : _ @ & / \ ' " ` ~ ( ) < > $ # % + - =
20+
// NOTE: This applies to all Fedwire tags except {8200} Unstructured Addenda Info
21+
alphanumericRegex = regexp.MustCompile(`[^ \w.?!,;:_@&/\\'"\x60~()<>$#%+-=]+`)
22+
23+
numericRegex = regexp.MustCompile(`[^0-9]`)
24+
amountRegex = regexp.MustCompile("[^0-9,.]")
2025
)
2126

2227
// validator is common validation and formatting of golang types to WIRE type strings
@@ -25,7 +30,6 @@ type validator struct{}
2530
// isAlphanumeric checks if a string only contains ASCII alphanumeric characters
2631
func (v *validator) isAlphanumeric(s string) error {
2732
if alphanumericRegex.MatchString(s) {
28-
// ^[ A-Za-z0-9_@./#&+-]*$/
2933
return ErrNonAlphanumeric
3034
}
3135
return nil

validators_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ func TestValidators__validateOptionFName(t *testing.T) {
1919
require.Error(t, v.validateOptionFName(""))
2020
require.Error(t, v.validateOptionFName(" /"))
2121
}
22+
23+
func TestValidators__isAlphanumeric(t *testing.T) {
24+
v := &validator{}
25+
26+
require.NoError(t, v.isAlphanumeric("Telepathic Bank (U.K.) / Acct #12345-ABC"))
27+
require.Error(t, v.isAlphanumeric("{1100}"))
28+
require.Error(t, v.isAlphanumeric("*"))
29+
}

0 commit comments

Comments
 (0)