Skip to content

Commit

Permalink
fix: allow full range of ASCII and EBCDIC characters accepted by Nach…
Browse files Browse the repository at this point in the history
…a rules

The 2022 Nacha rules state that Valid Characters are:

> 0-9, A-Z, a-z, space, EBCDIC values greater than hexadecimal "3F",
> ASCII values greater than hexadecimal "1F".
  • Loading branch information
adamdecaf committed Jul 18, 2023
1 parent 13495b4 commit 85b7e3c
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 13 deletions.
38 changes: 29 additions & 9 deletions validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ import (
)

var (
upperAlphanumericRegex = regexp.MustCompile(fmt.Sprintf(
`[^ A-Z0-9!"#$%%&'()*+,-.\\/:;<>=?@\[\]^_{}|~%s]+`, "`"))
alphanumericRegex = regexp.MustCompile(fmt.Sprintf(
`[^ \w!"#$%%&'()*+,-.\\/:;<>=?@\[\]^_{}|~%s]+`, "`"))
lowerAlphaCharacters = "abcdefghijklmnopqrstuvwxyz"
numericCharacters = "0123456789"
asciiCharacters = ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`"
ebcdicExtraCharacters = `¢¬¦±`

validAlphaNumericCharacters = lowerAlphaCharacters + strings.ToUpper(lowerAlphaCharacters) + numericCharacters + asciiCharacters + ebcdicExtraCharacters
validUppercaseAlphaNumericCharacters = strings.ToUpper(lowerAlphaCharacters) + numericCharacters + asciiCharacters + ebcdicExtraCharacters
)

// validator is common validation and formatting of golang types to ach type strings
Expand Down Expand Up @@ -422,19 +425,36 @@ func (v *validator) isTransactionTypeCode(s string) error {
return ErrTransactionTypeCode
}

func (v *validator) includesValidCharacters(input string, charset string) error {
for _, i := range input {
var found bool
for _, c := range charset {
if i == c {
found = true
break
}
}
if !found {
return fmt.Errorf("invalid character: %v", i)
}
}
return nil
}

// isUpperAlphanumeric checks if string only contains ASCII alphanumeric upper case characters
func (v *validator) isUpperAlphanumeric(s string) error {
if upperAlphanumericRegex.MatchString(s) {
return ErrUpperAlpha
err := v.includesValidCharacters(s, validUppercaseAlphaNumericCharacters)
if err != nil {
return fmt.Errorf("%w: %v", ErrUpperAlpha, err)
}
return nil
}

// 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
err := v.includesValidCharacters(s, validAlphaNumericCharacters)
if err != nil {
return fmt.Errorf("%w: %v", ErrNonAlphanumeric, err)
}
return nil
}
Expand Down
13 changes: 9 additions & 4 deletions validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,15 @@ func TestValidators__isAlphanumeric(t *testing.T) {
name: "is alphanumeric",
checkFunc: v.isAlphanumeric,
shouldErr: func(i int) bool {
return i < 0x20 || i > 0x7E
return i <= 0x1F || i > 0x7E
},
},
// Ensure that ASCII characters from 0x20 to 0x60 and 0x7B to 0x7E are considered upper case alphanumeric.
{
name: "is upper alphanumeric",
checkFunc: v.isUpperAlphanumeric,
shouldErr: func(i int) bool {
return i < 0x20 || i > 0x7E || (i > 0x60 && i < 0x7B)
return i <= 0x1F || i > 0x7E || (i > 0x60 && i < 0x7B)
},
},
}
Expand All @@ -165,6 +165,11 @@ func TestValidators__isAlphanumeric(t *testing.T) {
err := tt.checkFunc(chr)
shouldError := tt.shouldErr(i)

switch chr {
case `¢`, `¦`, `¬`, `±`: // skip valid ASCII characters
continue
}

if shouldError && err == nil {
t.Errorf("expected rune %x (%s) to be non-alphanumeric", i, chr)
} else if !shouldError && err != nil {
Expand All @@ -178,13 +183,13 @@ func TestValidators__isAlphanumeric(t *testing.T) {
func TestValidators__isAlphanumericExamples(t *testing.T) {
v := validator{}

validCases := []string{`|`}
validCases := []string{`|`, `¦`, `¢`, `¬`, `±`}
for i := range validCases {
err := v.isAlphanumeric(validCases[i])
require.NoError(t, err, fmt.Sprintf("input: %q", validCases[i]))
}

invalidCases := []string{`¦`}
invalidCases := []string{`©`, `®`, `§101.1`, `ã`, `}
for i := range invalidCases {
err := v.isAlphanumeric(invalidCases[i])
require.ErrorIs(t, err, ErrNonAlphanumeric, fmt.Sprintf("input: %q", invalidCases[i]))
Expand Down

0 comments on commit 85b7e3c

Please sign in to comment.