Repository: go-playground/validator
Pull request #615 (2020-05-28 19:37:45)

Add country code validation
Validate country code based on iso3166 standard.
validators:
  - iso3166_1_alpha2
  - iso3166_1_alpha3
  - iso3166_1_alpha_numeric
alias:
  - country_code

Enhances 

**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.

Change Details:

add validators:
  - iso3166_1_alpha2
  - iso3166_1_alpha3
  - iso3166_1_alpha_numeric
add alias:
  - country_code "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric"


@go-playground/admins


Patch:
diff --git a/baked_in.go b/baked_in.go
index c5729a7..ca3d3b9 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -59,7 +59,8 @@ var (
 	// defines a common or complex set of validation(s) to simplify
 	// adding validation to structs.
 	bakedInAliases = map[string]string{
-		"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
+		"iscolor":      "hexcolor|rgb|rgba|hsl|hsla",
+		"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
 	}
 
 	// BakedInValidators is the default map of ValidationFunc
@@ -184,6 +185,9 @@ var (
 		"uppercase":            isUppercase,
 		"datetime":             isDatetime,
 		"timezone":             isTimeZone,
+    "iso3166_1_alpha2":        isIso3166Alpha2,
+		"iso3166_1_alpha3":        isIso3166Alpha3,
+		"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
 	}
 )
 
@@ -2255,3 +2259,31 @@ func isTimeZone(fl FieldLevel) bool {
 
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
+
+// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code.
+func isIso3166Alpha2(fl FieldLevel) bool {
+	val := fl.Field().String()
+	return iso3166_1_alpha2[val]
+}
+
+// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
+func isIso3166Alpha3(fl FieldLevel) bool {
+	val := fl.Field().String()
+	return iso3166_1_alpha3[val]
+}
+
+// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
+func isIso3166AlphaNumeric(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() % 1000)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		code = int(field.Uint() % 1000)
+	default:
+		panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+	}
+	return iso3166_1_alpha_numeric[code]
+}
diff --git a/country_codes.go b/country_codes.go
new file mode 100644
index 0000000..ef81ead
--- /dev/null
+++ b/country_codes.go
@@ -0,0 +1,162 @@
+package validator
+
+var iso3166_1_alpha2 = map[string]bool{
+	// see: https://www.iso.org/iso-3166-country-codes.html
+	"AF": true, "AX": true, "AL": true, "DZ": true, "AS": true,
+	"AD": true, "AO": true, "AI": true, "AQ": true, "AG": true,
+	"AR": true, "AM": true, "AW": true, "AU": true, "AT": true,
+	"AZ": true, "BS": true, "BH": true, "BD": true, "BB": true,
+	"BY": true, "BE": true, "BZ": true, "BJ": true, "BM": true,
+	"BT": true, "BO": true, "BQ": true, "BA": true, "BW": true,
+	"BV": true, "BR": true, "IO": true, "BN": true, "BG": true,
+	"BF": true, "BI": true, "KH": true, "CM": true, "CA": true,
+	"CV": true, "KY": true, "CF": true, "TD": true, "CL": true,
+	"CN": true, "CX": true, "CC": true, "CO": true, "KM": true,
+	"CG": true, "CD": true, "CK": true, "CR": true, "CI": true,
+	"HR": true, "CU": true, "CW": true, "CY": true, "CZ": true,
+	"DK": true, "DJ": true, "DM": true, "DO": true, "EC": true,
+	"EG": true, "SV": true, "GQ": true, "ER": true, "EE": true,
+	"ET": true, "FK": true, "FO": true, "FJ": true, "FI": true,
+	"FR": true, "GF": true, "PF": true, "TF": true, "GA": true,
+	"GM": true, "GE": true, "DE": true, "GH": true, "GI": true,
+	"GR": true, "GL": true, "GD": true, "GP": true, "GU": true,
+	"GT": true, "GG": true, "GN": true, "GW": true, "GY": true,
+	"HT": true, "HM": true, "VA": true, "HN": true, "HK": true,
+	"HU": true, "IS": true, "IN": true, "ID": true, "IR": true,
+	"IQ": true, "IE": true, "IM": true, "IL": true, "IT": true,
+	"JM": true, "JP": true, "JE": true, "JO": true, "KZ": true,
+	"KE": true, "KI": true, "KP": true, "KR": true, "KW": true,
+	"KG": true, "LA": true, "LV": true, "LB": true, "LS": true,
+	"LR": true, "LY": true, "LI": true, "LT": true, "LU": true,
+	"MO": true, "MK": true, "MG": true, "MW": true, "MY": true,
+	"MV": true, "ML": true, "MT": true, "MH": true, "MQ": true,
+	"MR": true, "MU": true, "YT": true, "MX": true, "FM": true,
+	"MD": true, "MC": true, "MN": true, "ME": true, "MS": true,
+	"MA": true, "MZ": true, "MM": true, "NA": true, "NR": true,
+	"NP": true, "NL": true, "NC": true, "NZ": true, "NI": true,
+	"NE": true, "NG": true, "NU": true, "NF": true, "MP": true,
+	"NO": true, "OM": true, "PK": true, "PW": true, "PS": true,
+	"PA": true, "PG": true, "PY": true, "PE": true, "PH": true,
+	"PN": true, "PL": true, "PT": true, "PR": true, "QA": true,
+	"RE": true, "RO": true, "RU": true, "RW": true, "BL": true,
+	"SH": true, "KN": true, "LC": true, "MF": true, "PM": true,
+	"VC": true, "WS": true, "SM": true, "ST": true, "SA": true,
+	"SN": true, "RS": true, "SC": true, "SL": true, "SG": true,
+	"SX": true, "SK": true, "SI": true, "SB": true, "SO": true,
+	"ZA": true, "GS": true, "SS": true, "ES": true, "LK": true,
+	"SD": true, "SR": true, "SJ": true, "SZ": true, "SE": true,
+	"CH": true, "SY": true, "TW": true, "TJ": true, "TZ": true,
+	"TH": true, "TL": true, "TG": true, "TK": true, "TO": true,
+	"TT": true, "TN": true, "TR": true, "TM": true, "TC": true,
+	"TV": true, "UG": true, "UA": true, "AE": true, "GB": true,
+	"US": true, "UM": true, "UY": true, "UZ": true, "VU": true,
+	"VE": true, "VN": true, "VG": true, "VI": true, "WF": true,
+	"EH": true, "YE": true, "ZM": true, "ZW": true,
+}
+
+var iso3166_1_alpha3 = map[string]bool{
+	// see: https://www.iso.org/iso-3166-country-codes.html
+	"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
+	"AGO": true, "AIA": true, "ATA": true, "ATG": true, "ARG": true,
+	"ARM": true, "ABW": true, "AUS": true, "AUT": true, "AZE": true,
+	"BHS": true, "BHR": true, "BGD": true, "BRB": true, "BLR": true,
+	"BEL": true, "BLZ": true, "BEN": true, "BMU": true, "BTN": true,
+	"BOL": true, "BES": true, "BIH": true, "BWA": true, "BVT": true,
+	"BRA": true, "IOT": true, "BRN": true, "BGR": true, "BFA": true,
+	"BDI": true, "CPV": true, "KHM": true, "CMR": true, "CAN": true,
+	"CYM": true, "CAF": true, "TCD": true, "CHL": true, "CHN": true,
+	"CXR": true, "CCK": true, "COL": true, "COM": true, "COD": true,
+	"COG": true, "COK": true, "CRI": true, "HRV": true, "CUB": true,
+	"CUW": true, "CYP": true, "CZE": true, "CIV": true, "DNK": true,
+	"DJI": true, "DMA": true, "DOM": true, "ECU": true, "EGY": true,
+	"SLV": true, "GNQ": true, "ERI": true, "EST": true, "SWZ": true,
+	"ETH": true, "FLK": true, "FRO": true, "FJI": true, "FIN": true,
+	"FRA": true, "GUF": true, "PYF": true, "ATF": true, "GAB": true,
+	"GMB": true, "GEO": true, "DEU": true, "GHA": true, "GIB": true,
+	"GRC": true, "GRL": true, "GRD": true, "GLP": true, "GUM": true,
+	"GTM": true, "GGY": true, "GIN": true, "GNB": true, "GUY": true,
+	"HTI": true, "HMD": true, "VAT": true, "HND": true, "HKG": true,
+	"HUN": true, "ISL": true, "IND": true, "IDN": true, "IRN": true,
+	"IRQ": true, "IRL": true, "IMN": true, "ISR": true, "ITA": true,
+	"JAM": true, "JPN": true, "JEY": true, "JOR": true, "KAZ": true,
+	"KEN": true, "KIR": true, "PRK": true, "KOR": true, "KWT": true,
+	"KGZ": true, "LAO": true, "LVA": true, "LBN": true, "LSO": true,
+	"LBR": true, "LBY": true, "LIE": true, "LTU": true, "LUX": true,
+	"MAC": true, "MDG": true, "MWI": true, "MYS": true, "MDV": true,
+	"MLI": true, "MLT": true, "MHL": true, "MTQ": true, "MRT": true,
+	"MUS": true, "MYT": true, "MEX": true, "FSM": true, "MDA": true,
+	"MCO": true, "MNG": true, "MNE": true, "MSR": true, "MAR": true,
+	"MOZ": true, "MMR": true, "NAM": true, "NRU": true, "NPL": true,
+	"NLD": true, "NCL": true, "NZL": true, "NIC": true, "NER": true,
+	"NGA": true, "NIU": true, "NFK": true, "MKD": true, "MNP": true,
+	"NOR": true, "OMN": true, "PAK": true, "PLW": true, "PSE": true,
+	"PAN": true, "PNG": true, "PRY": true, "PER": true, "PHL": true,
+	"PCN": true, "POL": true, "PRT": true, "PRI": true, "QAT": true,
+	"ROU": true, "RUS": true, "RWA": true, "REU": true, "BLM": true,
+	"SHN": true, "KNA": true, "LCA": true, "MAF": true, "SPM": true,
+	"VCT": true, "WSM": true, "SMR": true, "STP": true, "SAU": true,
+	"SEN": true, "SRB": true, "SYC": true, "SLE": true, "SGP": true,
+	"SXM": true, "SVK": true, "SVN": true, "SLB": true, "SOM": true,
+	"ZAF": true, "SGS": true, "SSD": true, "ESP": true, "LKA": true,
+	"SDN": true, "SUR": true, "SJM": true, "SWE": true, "CHE": true,
+	"SYR": true, "TWN": true, "TJK": true, "TZA": true, "THA": true,
+	"TLS": true, "TGO": true, "TKL": true, "TON": true, "TTO": true,
+	"TUN": true, "TUR": true, "TKM": true, "TCA": true, "TUV": true,
+	"UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true,
+	"USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true,
+	"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
+	"YEM": true, "ZMB": true, "ZWE": true, "ALA": true,
+}
+var iso3166_1_alpha_numeric = map[int]bool{
+	// see: https://www.iso.org/iso-3166-country-codes.html
+	4: true, 8: true, 12: true, 16: true, 20: true,
+	24: true, 660: true, 10: true, 28: true, 32: true,
+	51: true, 533: true, 36: true, 40: true, 31: true,
+	44: true, 48: true, 50: true, 52: true, 112: true,
+	56: true, 84: true, 204: true, 60: true, 64: true,
+	68: true, 535: true, 70: true, 72: true, 74: true,
+	76: true, 86: true, 96: true, 100: true, 854: true,
+	108: true, 132: true, 116: true, 120: true, 124: true,
+	136: true, 140: true, 148: true, 152: true, 156: true,
+	162: true, 166: true, 170: true, 174: true, 180: true,
+	178: true, 184: true, 188: true, 191: true, 192: true,
+	531: true, 196: true, 203: true, 384: true, 208: true,
+	262: true, 212: true, 214: true, 218: true, 818: true,
+	222: true, 226: true, 232: true, 233: true, 748: true,
+	231: true, 238: true, 234: true, 242: true, 246: true,
+	250: true, 254: true, 258: true, 260: true, 266: true,
+	270: true, 268: true, 276: true, 288: true, 292: true,
+	300: true, 304: true, 308: true, 312: true, 316: true,
+	320: true, 831: true, 324: true, 624: true, 328: true,
+	332: true, 334: true, 336: true, 340: true, 344: true,
+	348: true, 352: true, 356: true, 360: true, 364: true,
+	368: true, 372: true, 833: true, 376: true, 380: true,
+	388: true, 392: true, 832: true, 400: true, 398: true,
+	404: true, 296: true, 408: true, 410: true, 414: true,
+	417: true, 418: true, 428: true, 422: true, 426: true,
+	430: true, 434: true, 438: true, 440: true, 442: true,
+	446: true, 450: true, 454: true, 458: true, 462: true,
+	466: true, 470: true, 584: true, 474: true, 478: true,
+	480: true, 175: true, 484: true, 583: true, 498: true,
+	492: true, 496: true, 499: true, 500: true, 504: true,
+	508: true, 104: true, 516: true, 520: true, 524: true,
+	528: true, 540: true, 554: true, 558: true, 562: true,
+	566: true, 570: true, 574: true, 807: true, 580: true,
+	578: true, 512: true, 586: true, 585: true, 275: true,
+	591: true, 598: true, 600: true, 604: true, 608: true,
+	612: true, 616: true, 620: true, 630: true, 634: true,
+	642: true, 643: true, 646: true, 638: true, 652: true,
+	654: true, 659: true, 662: true, 663: true, 666: true,
+	670: true, 882: true, 674: true, 678: true, 682: true,
+	686: true, 688: true, 690: true, 694: true, 702: true,
+	534: true, 703: true, 705: true, 90: true, 706: true,
+	710: true, 239: true, 728: true, 724: true, 144: true,
+	729: true, 740: true, 744: true, 752: true, 756: true,
+	760: true, 158: true, 762: true, 834: true, 764: true,
+	626: true, 768: true, 772: true, 776: true, 780: true,
+	788: true, 792: true, 795: true, 796: true, 798: true,
+	800: true, 804: true, 784: true, 826: true, 581: true,
+	840: true, 858: true, 860: true, 548: true, 862: true,
+	704: true, 92: true, 850: true, 876: true, 732: true,
+	887: true, 894: true, 716: true, 248: true,
+}
diff --git a/doc.go b/doc.go
index 0ab483b..a816c20 100644
--- a/doc.go
+++ b/doc.go
@@ -1200,6 +1200,27 @@ Supplied format must match the official Go time format layout as documented in h
 
 	Usage: datetime=2006-01-02
 
+Iso3166-1 alpha-2
+
+This validates that a string value is a valid country code based on iso3166-1 alpha-2 standard.
+see: https://www.iso.org/iso-3166-country-codes.html
+
+	Usage: iso3166_1_alpha2
+
+Iso3166-1 alpha-3
+
+This validates that a string value is a valid country code based on iso3166-1 alpha-3 standard.
+see: https://www.iso.org/iso-3166-country-codes.html
+
+	Usage: iso3166_1_alpha3
+
+Iso3166-1 alpha-numeric
+
+This validates that a string value is a valid country code based on iso3166-1 alpha-numeric standard.
+see: https://www.iso.org/iso-3166-country-codes.html
+
+	Usage: iso3166_1_alpha3
+
 TimeZone
 
 This validates that a string value is a valid time zone based on the time zone database present on the system.
@@ -1207,6 +1228,7 @@ Although empty value and Local value are allowed by time.LoadLocation golang fun
 More information on https://golang.org/pkg/time/#LoadLocation
 
 	Usage: timezone
+  
 
 Alias Validators and Tags
 
@@ -1219,6 +1241,8 @@ Here is a list of the current built in alias tags:
 
 	"iscolor"
 		alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
+	"country_code"
+		alias is "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric" (Usage: country_code)
 
 Validator notes:
 


Test patch:
diff --git a/validator_test.go b/validator_test.go
index f17cb93..a918bce 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -10862,6 +10862,94 @@ func TestDatetimeValidation(t *testing.T) {
 	}, "Bad field type int")
 }
 
+func TestIsIso3166Alpha2Validation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"iso3166_1_alpha2"`
+		expected bool
+	}{
+		{"PL", true},
+		{"POL", false},
+		{"AA", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, "iso3166_1_alpha2")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs)
+			}
+		}
+	}
+}
+
+func TestIsIso3166Alpha3Validation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"iso3166_1_alpha3"`
+		expected bool
+	}{
+		{"POL", true},
+		{"PL", false},
+		{"AAA", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, "iso3166_1_alpha3")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs)
+			}
+		}
+	}
+}
+
+func TestIsIso3166AlphaNumericValidation(t *testing.T) {
+	tests := []struct {
+		value    int
+		expected bool
+	}{
+		{248, true},
+		{0, false},
+		{1, false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, "iso3166_1_alpha_numeric")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs)
+			}
+		}
+	}
+
+	PanicMatches(t, func() {
+		_ = validate.Var("1", "iso3166_1_alpha_numeric")
+	}, "Bad field type string")
+}
+
 func TestTimeZoneValidation(t *testing.T) {
 	tests := []struct {
 		value    string `validate:"timezone"`
@@ -10901,4 +10989,4 @@ func TestTimeZoneValidation(t *testing.T) {
 	PanicMatches(t, func() {
 		_ = validate.Var(2, "timezone")
 	}, "Bad field type int")
-}
+}
\ No newline at end of file