Repository: go-playground/validator
Pull request #811 (2021-07-31 22:37:26)

Feature/currency codes
## Fixes Or Enhances

Adds the option to validate ISO 4217 currency codes

**Make sure that you've checked the boxes below before you submit PR:**
- [x] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/41806675/badge)](https://coveralls.io/builds/41806675)

Coverage decreased (-0.0005%) to 75.358% when pulling **dfa9a0cf15af3bf10c62d91184cac57c53f82da2 on metinogurlu:feature/currency-codes** into **9a5bce32538f319bf69aebb3aca90d394bc6d0cb on go-playground:master**.


Patch:
diff --git a/README.md b/README.md
index da5b0be..f56cff1 100644
--- a/README.md
+++ b/README.md
@@ -170,6 +170,7 @@ Baked-in Validations
 | iso3166_1_alpha3 | Three-letter country code (ISO 3166-1 alpha-3) |
 | iso3166_1_alpha_numeric | Numeric country code (ISO 3166-1 numeric) |
 | iso3166_2 | Country subdivision code (ISO 3166-2) |
+| iso4217 | Currency code (ISO 4217) |
 | json | JSON |
 | jwt | JSON Web Token (JWT) |
 | latitude | Latitude |
diff --git a/baked_in.go b/baked_in.go
index d175ecc..f5fd239 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -192,6 +192,8 @@ var (
 		"iso3166_1_alpha3":              isIso3166Alpha3,
 		"iso3166_1_alpha_numeric":       isIso3166AlphaNumeric,
 		"iso3166_2":                     isIso31662,
+		"iso4217":                       isIso4217,
+		"iso4217_numeric":               isIso4217Numeric,
 		"bcp47_language_tag":            isBCP47LanguageTag,
 		"postcode_iso3166_alpha2":       isPostcodeByIso3166Alpha2,
 		"postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field,
@@ -2365,6 +2367,28 @@ func isIso31662(fl FieldLevel) bool {
 	return iso3166_2[val]
 }
 
+// isIso4217 is the validation function for validating if the current field's value is a valid iso4217 currency code.
+func isIso4217(fl FieldLevel) bool {
+	val := fl.Field().String()
+	return iso4217[val]
+}
+
+// isIso4217Numeric is the validation function for validating if the current field's value is a valid iso4217 numeric currency code.
+func isIso4217Numeric(fl FieldLevel) bool {
+	field := fl.Field()
+
+	var code int
+	switch field.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		code = int(field.Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		code = int(field.Uint())
+	default:
+		panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+	}
+	return iso4217_numeric[code]
+}
+
 // isBCP47LanguageTag is the validation function for validating if the current field's value is a valid BCP 47 language tag, as parsed by language.Parse
 func isBCP47LanguageTag(fl FieldLevel) bool {
 	field := fl.Field()
diff --git a/currency_codes.go b/currency_codes.go
new file mode 100644
index 0000000..a5cd9b1
--- /dev/null
+++ b/currency_codes.go
@@ -0,0 +1,79 @@
+package validator
+
+var iso4217 = map[string]bool{
+	"AFN": true, "EUR": true, "ALL": true, "DZD": true, "USD": true,
+	"AOA": true, "XCD": true, "ARS": true, "AMD": true, "AWG": true,
+	"AUD": true, "AZN": true, "BSD": true, "BHD": true, "BDT": true,
+	"BBD": true, "BYN": true, "BZD": true, "XOF": true, "BMD": true,
+	"INR": true, "BTN": true, "BOB": true, "BOV": true, "BAM": true,
+	"BWP": true, "NOK": true, "BRL": true, "BND": true, "BGN": true,
+	"BIF": true, "CVE": true, "KHR": true, "XAF": true, "CAD": true,
+	"KYD": true, "CLP": true, "CLF": true, "CNY": true, "COP": true,
+	"COU": true, "KMF": true, "CDF": true, "NZD": true, "CRC": true,
+	"HRK": true, "CUP": true, "CUC": true, "ANG": true, "CZK": true,
+	"DKK": true, "DJF": true, "DOP": true, "EGP": true, "SVC": true,
+	"ERN": true, "SZL": true, "ETB": true, "FKP": true, "FJD": true,
+	"XPF": true, "GMD": true, "GEL": true, "GHS": true, "GIP": true,
+	"GTQ": true, "GBP": true, "GNF": true, "GYD": true, "HTG": true,
+	"HNL": true, "HKD": true, "HUF": true, "ISK": true, "IDR": true,
+	"XDR": true, "IRR": true, "IQD": true, "ILS": true, "JMD": true,
+	"JPY": true, "JOD": true, "KZT": true, "KES": true, "KPW": true,
+	"KRW": true, "KWD": true, "KGS": true, "LAK": true, "LBP": true,
+	"LSL": true, "ZAR": true, "LRD": true, "LYD": true, "CHF": true,
+	"MOP": true, "MKD": true, "MGA": true, "MWK": true, "MYR": true,
+	"MVR": true, "MRU": true, "MUR": true, "XUA": true, "MXN": true,
+	"MXV": true, "MDL": true, "MNT": true, "MAD": true, "MZN": true,
+	"MMK": true, "NAD": true, "NPR": true, "NIO": true, "NGN": true,
+	"OMR": true, "PKR": true, "PAB": true, "PGK": true, "PYG": true,
+	"PEN": true, "PHP": true, "PLN": true, "QAR": true, "RON": true,
+	"RUB": true, "RWF": true, "SHP": true, "WST": true, "STN": true,
+	"SAR": true, "RSD": true, "SCR": true, "SLL": true, "SGD": true,
+	"XSU": true, "SBD": true, "SOS": true, "SSP": true, "LKR": true,
+	"SDG": true, "SRD": true, "SEK": true, "CHE": true, "CHW": true,
+	"SYP": true, "TWD": true, "TJS": true, "TZS": true, "THB": true,
+	"TOP": true, "TTD": true, "TND": true, "TRY": true, "TMT": true,
+	"UGX": true, "UAH": true, "AED": true, "USN": true, "UYU": true,
+	"UYI": true, "UYW": true, "UZS": true, "VUV": true, "VES": true,
+	"VND": true, "YER": true, "ZMW": true, "ZWL": true, "XBA": true,
+	"XBB": true, "XBC": true, "XBD": true, "XTS": true, "XXX": true,
+	"XAU": true, "XPD": true, "XPT": true, "XAG": true,
+}
+
+var iso4217_numeric = map[int]bool{
+	8: true, 12: true, 32: true, 36: true, 44: true,
+	48: true, 50: true, 51: true, 52: true, 60: true,
+	64: true, 68: true, 72: true, 84: true, 90: true,
+	96: true, 104: true, 108: true, 116: true, 124: true,
+	132: true, 136: true, 144: true, 152: true, 156: true,
+	170: true, 174: true, 188: true, 191: true, 192: true,
+	203: true, 208: true, 214: true, 222: true, 230: true,
+	232: true, 238: true, 242: true, 262: true, 270: true,
+	292: true, 320: true, 324: true, 328: true, 332: true,
+	340: true, 344: true, 348: true, 352: true, 356: true,
+	360: true, 364: true, 368: true, 376: true, 388: true,
+	392: true, 398: true, 400: true, 404: true, 408: true,
+	410: true, 414: true, 417: true, 418: true, 422: true,
+	426: true, 430: true, 434: true, 446: true, 454: true,
+	458: true, 462: true, 480: true, 484: true, 496: true,
+	498: true, 504: true, 512: true, 516: true, 524: true,
+	532: true, 533: true, 548: true, 554: true, 558: true,
+	566: true, 578: true, 586: true, 590: true, 598: true,
+	600: true, 604: true, 608: true, 634: true, 643: true,
+	646: true, 654: true, 682: true, 690: true, 694: true,
+	702: true, 704: true, 706: true, 710: true, 728: true,
+	748: true, 752: true, 756: true, 760: true, 764: true,
+	776: true, 780: true, 784: true, 788: true, 800: true,
+	807: true, 818: true, 826: true, 834: true, 840: true,
+	858: true, 860: true, 882: true, 886: true, 901: true,
+	927: true, 928: true, 929: true, 930: true, 931: true,
+	932: true, 933: true, 934: true, 936: true, 938: true,
+	940: true, 941: true, 943: true, 944: true, 946: true,
+	947: true, 948: true, 949: true, 950: true, 951: true,
+	952: true, 953: true, 955: true, 956: true, 957: true,
+	958: true, 959: true, 960: true, 961: true, 962: true,
+	963: true, 964: true, 965: true, 967: true, 968: true,
+	969: true, 970: true, 971: true, 972: true, 973: true,
+	975: true, 976: true, 977: true, 978: true, 979: true,
+	980: true, 981: true, 984: true, 985: true, 986: true,
+	990: true, 994: true, 997: true, 999: true,
+}


Test patch:
diff --git a/validator_test.go b/validator_test.go
index fcbe6a0..f694203 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -11083,6 +11083,62 @@ func TestIsIso3166AlphaNumericValidation(t *testing.T) {
 	}, "Bad field type string")
 }
 
+func TestIsIso4217Validation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"iso4217"`
+		expected bool
+	}{
+		{"TRY", true},
+		{"EUR", true},
+		{"USA", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, "iso4217")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso4217 failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso4217 failed Error: %s", i, errs)
+			}
+		}
+	}
+}
+
+func TestIsIso4217NumericValidation(t *testing.T) {
+	tests := []struct {
+		value    int `validate:"iso4217_numeric"`
+		expected bool
+	}{
+		{8, true},
+		{12, true},
+		{13, false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, "iso4217_numeric")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso4217 failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso4217 failed Error: %s", i, errs)
+			}
+		}
+	}
+}
+
 func TestTimeZoneValidation(t *testing.T) {
 	tests := []struct {
 		value    string `validate:"timezone"`