Skip to content

Commit f32fea8

Browse files
authored
Add country code validation (#615)
1 parent 704a814 commit f32fea8

File tree

4 files changed

+308
-2
lines changed

4 files changed

+308
-2
lines changed

baked_in.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ var (
5959
// defines a common or complex set of validation(s) to simplify
6060
// adding validation to structs.
6161
bakedInAliases = map[string]string{
62-
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
62+
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
63+
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
6364
}
6465

6566
// BakedInValidators is the default map of ValidationFunc
@@ -184,6 +185,9 @@ var (
184185
"uppercase": isUppercase,
185186
"datetime": isDatetime,
186187
"timezone": isTimeZone,
188+
"iso3166_1_alpha2": isIso3166Alpha2,
189+
"iso3166_1_alpha3": isIso3166Alpha3,
190+
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
187191
}
188192
)
189193

@@ -2255,3 +2259,31 @@ func isTimeZone(fl FieldLevel) bool {
22552259

22562260
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
22572261
}
2262+
2263+
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code.
2264+
func isIso3166Alpha2(fl FieldLevel) bool {
2265+
val := fl.Field().String()
2266+
return iso3166_1_alpha2[val]
2267+
}
2268+
2269+
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
2270+
func isIso3166Alpha3(fl FieldLevel) bool {
2271+
val := fl.Field().String()
2272+
return iso3166_1_alpha3[val]
2273+
}
2274+
2275+
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
2276+
func isIso3166AlphaNumeric(fl FieldLevel) bool {
2277+
field := fl.Field()
2278+
2279+
var code int
2280+
switch field.Kind() {
2281+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
2282+
code = int(field.Int() % 1000)
2283+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
2284+
code = int(field.Uint() % 1000)
2285+
default:
2286+
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
2287+
}
2288+
return iso3166_1_alpha_numeric[code]
2289+
}

country_codes.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package validator
2+
3+
var iso3166_1_alpha2 = map[string]bool{
4+
// see: https://www.iso.org/iso-3166-country-codes.html
5+
"AF": true, "AX": true, "AL": true, "DZ": true, "AS": true,
6+
"AD": true, "AO": true, "AI": true, "AQ": true, "AG": true,
7+
"AR": true, "AM": true, "AW": true, "AU": true, "AT": true,
8+
"AZ": true, "BS": true, "BH": true, "BD": true, "BB": true,
9+
"BY": true, "BE": true, "BZ": true, "BJ": true, "BM": true,
10+
"BT": true, "BO": true, "BQ": true, "BA": true, "BW": true,
11+
"BV": true, "BR": true, "IO": true, "BN": true, "BG": true,
12+
"BF": true, "BI": true, "KH": true, "CM": true, "CA": true,
13+
"CV": true, "KY": true, "CF": true, "TD": true, "CL": true,
14+
"CN": true, "CX": true, "CC": true, "CO": true, "KM": true,
15+
"CG": true, "CD": true, "CK": true, "CR": true, "CI": true,
16+
"HR": true, "CU": true, "CW": true, "CY": true, "CZ": true,
17+
"DK": true, "DJ": true, "DM": true, "DO": true, "EC": true,
18+
"EG": true, "SV": true, "GQ": true, "ER": true, "EE": true,
19+
"ET": true, "FK": true, "FO": true, "FJ": true, "FI": true,
20+
"FR": true, "GF": true, "PF": true, "TF": true, "GA": true,
21+
"GM": true, "GE": true, "DE": true, "GH": true, "GI": true,
22+
"GR": true, "GL": true, "GD": true, "GP": true, "GU": true,
23+
"GT": true, "GG": true, "GN": true, "GW": true, "GY": true,
24+
"HT": true, "HM": true, "VA": true, "HN": true, "HK": true,
25+
"HU": true, "IS": true, "IN": true, "ID": true, "IR": true,
26+
"IQ": true, "IE": true, "IM": true, "IL": true, "IT": true,
27+
"JM": true, "JP": true, "JE": true, "JO": true, "KZ": true,
28+
"KE": true, "KI": true, "KP": true, "KR": true, "KW": true,
29+
"KG": true, "LA": true, "LV": true, "LB": true, "LS": true,
30+
"LR": true, "LY": true, "LI": true, "LT": true, "LU": true,
31+
"MO": true, "MK": true, "MG": true, "MW": true, "MY": true,
32+
"MV": true, "ML": true, "MT": true, "MH": true, "MQ": true,
33+
"MR": true, "MU": true, "YT": true, "MX": true, "FM": true,
34+
"MD": true, "MC": true, "MN": true, "ME": true, "MS": true,
35+
"MA": true, "MZ": true, "MM": true, "NA": true, "NR": true,
36+
"NP": true, "NL": true, "NC": true, "NZ": true, "NI": true,
37+
"NE": true, "NG": true, "NU": true, "NF": true, "MP": true,
38+
"NO": true, "OM": true, "PK": true, "PW": true, "PS": true,
39+
"PA": true, "PG": true, "PY": true, "PE": true, "PH": true,
40+
"PN": true, "PL": true, "PT": true, "PR": true, "QA": true,
41+
"RE": true, "RO": true, "RU": true, "RW": true, "BL": true,
42+
"SH": true, "KN": true, "LC": true, "MF": true, "PM": true,
43+
"VC": true, "WS": true, "SM": true, "ST": true, "SA": true,
44+
"SN": true, "RS": true, "SC": true, "SL": true, "SG": true,
45+
"SX": true, "SK": true, "SI": true, "SB": true, "SO": true,
46+
"ZA": true, "GS": true, "SS": true, "ES": true, "LK": true,
47+
"SD": true, "SR": true, "SJ": true, "SZ": true, "SE": true,
48+
"CH": true, "SY": true, "TW": true, "TJ": true, "TZ": true,
49+
"TH": true, "TL": true, "TG": true, "TK": true, "TO": true,
50+
"TT": true, "TN": true, "TR": true, "TM": true, "TC": true,
51+
"TV": true, "UG": true, "UA": true, "AE": true, "GB": true,
52+
"US": true, "UM": true, "UY": true, "UZ": true, "VU": true,
53+
"VE": true, "VN": true, "VG": true, "VI": true, "WF": true,
54+
"EH": true, "YE": true, "ZM": true, "ZW": true,
55+
}
56+
57+
var iso3166_1_alpha3 = map[string]bool{
58+
// see: https://www.iso.org/iso-3166-country-codes.html
59+
"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
60+
"AGO": true, "AIA": true, "ATA": true, "ATG": true, "ARG": true,
61+
"ARM": true, "ABW": true, "AUS": true, "AUT": true, "AZE": true,
62+
"BHS": true, "BHR": true, "BGD": true, "BRB": true, "BLR": true,
63+
"BEL": true, "BLZ": true, "BEN": true, "BMU": true, "BTN": true,
64+
"BOL": true, "BES": true, "BIH": true, "BWA": true, "BVT": true,
65+
"BRA": true, "IOT": true, "BRN": true, "BGR": true, "BFA": true,
66+
"BDI": true, "CPV": true, "KHM": true, "CMR": true, "CAN": true,
67+
"CYM": true, "CAF": true, "TCD": true, "CHL": true, "CHN": true,
68+
"CXR": true, "CCK": true, "COL": true, "COM": true, "COD": true,
69+
"COG": true, "COK": true, "CRI": true, "HRV": true, "CUB": true,
70+
"CUW": true, "CYP": true, "CZE": true, "CIV": true, "DNK": true,
71+
"DJI": true, "DMA": true, "DOM": true, "ECU": true, "EGY": true,
72+
"SLV": true, "GNQ": true, "ERI": true, "EST": true, "SWZ": true,
73+
"ETH": true, "FLK": true, "FRO": true, "FJI": true, "FIN": true,
74+
"FRA": true, "GUF": true, "PYF": true, "ATF": true, "GAB": true,
75+
"GMB": true, "GEO": true, "DEU": true, "GHA": true, "GIB": true,
76+
"GRC": true, "GRL": true, "GRD": true, "GLP": true, "GUM": true,
77+
"GTM": true, "GGY": true, "GIN": true, "GNB": true, "GUY": true,
78+
"HTI": true, "HMD": true, "VAT": true, "HND": true, "HKG": true,
79+
"HUN": true, "ISL": true, "IND": true, "IDN": true, "IRN": true,
80+
"IRQ": true, "IRL": true, "IMN": true, "ISR": true, "ITA": true,
81+
"JAM": true, "JPN": true, "JEY": true, "JOR": true, "KAZ": true,
82+
"KEN": true, "KIR": true, "PRK": true, "KOR": true, "KWT": true,
83+
"KGZ": true, "LAO": true, "LVA": true, "LBN": true, "LSO": true,
84+
"LBR": true, "LBY": true, "LIE": true, "LTU": true, "LUX": true,
85+
"MAC": true, "MDG": true, "MWI": true, "MYS": true, "MDV": true,
86+
"MLI": true, "MLT": true, "MHL": true, "MTQ": true, "MRT": true,
87+
"MUS": true, "MYT": true, "MEX": true, "FSM": true, "MDA": true,
88+
"MCO": true, "MNG": true, "MNE": true, "MSR": true, "MAR": true,
89+
"MOZ": true, "MMR": true, "NAM": true, "NRU": true, "NPL": true,
90+
"NLD": true, "NCL": true, "NZL": true, "NIC": true, "NER": true,
91+
"NGA": true, "NIU": true, "NFK": true, "MKD": true, "MNP": true,
92+
"NOR": true, "OMN": true, "PAK": true, "PLW": true, "PSE": true,
93+
"PAN": true, "PNG": true, "PRY": true, "PER": true, "PHL": true,
94+
"PCN": true, "POL": true, "PRT": true, "PRI": true, "QAT": true,
95+
"ROU": true, "RUS": true, "RWA": true, "REU": true, "BLM": true,
96+
"SHN": true, "KNA": true, "LCA": true, "MAF": true, "SPM": true,
97+
"VCT": true, "WSM": true, "SMR": true, "STP": true, "SAU": true,
98+
"SEN": true, "SRB": true, "SYC": true, "SLE": true, "SGP": true,
99+
"SXM": true, "SVK": true, "SVN": true, "SLB": true, "SOM": true,
100+
"ZAF": true, "SGS": true, "SSD": true, "ESP": true, "LKA": true,
101+
"SDN": true, "SUR": true, "SJM": true, "SWE": true, "CHE": true,
102+
"SYR": true, "TWN": true, "TJK": true, "TZA": true, "THA": true,
103+
"TLS": true, "TGO": true, "TKL": true, "TON": true, "TTO": true,
104+
"TUN": true, "TUR": true, "TKM": true, "TCA": true, "TUV": true,
105+
"UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true,
106+
"USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true,
107+
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
108+
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true,
109+
}
110+
var iso3166_1_alpha_numeric = map[int]bool{
111+
// see: https://www.iso.org/iso-3166-country-codes.html
112+
4: true, 8: true, 12: true, 16: true, 20: true,
113+
24: true, 660: true, 10: true, 28: true, 32: true,
114+
51: true, 533: true, 36: true, 40: true, 31: true,
115+
44: true, 48: true, 50: true, 52: true, 112: true,
116+
56: true, 84: true, 204: true, 60: true, 64: true,
117+
68: true, 535: true, 70: true, 72: true, 74: true,
118+
76: true, 86: true, 96: true, 100: true, 854: true,
119+
108: true, 132: true, 116: true, 120: true, 124: true,
120+
136: true, 140: true, 148: true, 152: true, 156: true,
121+
162: true, 166: true, 170: true, 174: true, 180: true,
122+
178: true, 184: true, 188: true, 191: true, 192: true,
123+
531: true, 196: true, 203: true, 384: true, 208: true,
124+
262: true, 212: true, 214: true, 218: true, 818: true,
125+
222: true, 226: true, 232: true, 233: true, 748: true,
126+
231: true, 238: true, 234: true, 242: true, 246: true,
127+
250: true, 254: true, 258: true, 260: true, 266: true,
128+
270: true, 268: true, 276: true, 288: true, 292: true,
129+
300: true, 304: true, 308: true, 312: true, 316: true,
130+
320: true, 831: true, 324: true, 624: true, 328: true,
131+
332: true, 334: true, 336: true, 340: true, 344: true,
132+
348: true, 352: true, 356: true, 360: true, 364: true,
133+
368: true, 372: true, 833: true, 376: true, 380: true,
134+
388: true, 392: true, 832: true, 400: true, 398: true,
135+
404: true, 296: true, 408: true, 410: true, 414: true,
136+
417: true, 418: true, 428: true, 422: true, 426: true,
137+
430: true, 434: true, 438: true, 440: true, 442: true,
138+
446: true, 450: true, 454: true, 458: true, 462: true,
139+
466: true, 470: true, 584: true, 474: true, 478: true,
140+
480: true, 175: true, 484: true, 583: true, 498: true,
141+
492: true, 496: true, 499: true, 500: true, 504: true,
142+
508: true, 104: true, 516: true, 520: true, 524: true,
143+
528: true, 540: true, 554: true, 558: true, 562: true,
144+
566: true, 570: true, 574: true, 807: true, 580: true,
145+
578: true, 512: true, 586: true, 585: true, 275: true,
146+
591: true, 598: true, 600: true, 604: true, 608: true,
147+
612: true, 616: true, 620: true, 630: true, 634: true,
148+
642: true, 643: true, 646: true, 638: true, 652: true,
149+
654: true, 659: true, 662: true, 663: true, 666: true,
150+
670: true, 882: true, 674: true, 678: true, 682: true,
151+
686: true, 688: true, 690: true, 694: true, 702: true,
152+
534: true, 703: true, 705: true, 90: true, 706: true,
153+
710: true, 239: true, 728: true, 724: true, 144: true,
154+
729: true, 740: true, 744: true, 752: true, 756: true,
155+
760: true, 158: true, 762: true, 834: true, 764: true,
156+
626: true, 768: true, 772: true, 776: true, 780: true,
157+
788: true, 792: true, 795: true, 796: true, 798: true,
158+
800: true, 804: true, 784: true, 826: true, 581: true,
159+
840: true, 858: true, 860: true, 548: true, 862: true,
160+
704: true, 92: true, 850: true, 876: true, 732: true,
161+
887: true, 894: true, 716: true, 248: true,
162+
}

doc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,13 +1200,35 @@ Supplied format must match the official Go time format layout as documented in h
12001200
12011201
Usage: datetime=2006-01-02
12021202
1203+
Iso3166-1 alpha-2
1204+
1205+
This validates that a string value is a valid country code based on iso3166-1 alpha-2 standard.
1206+
see: https://www.iso.org/iso-3166-country-codes.html
1207+
1208+
Usage: iso3166_1_alpha2
1209+
1210+
Iso3166-1 alpha-3
1211+
1212+
This validates that a string value is a valid country code based on iso3166-1 alpha-3 standard.
1213+
see: https://www.iso.org/iso-3166-country-codes.html
1214+
1215+
Usage: iso3166_1_alpha3
1216+
1217+
Iso3166-1 alpha-numeric
1218+
1219+
This validates that a string value is a valid country code based on iso3166-1 alpha-numeric standard.
1220+
see: https://www.iso.org/iso-3166-country-codes.html
1221+
1222+
Usage: iso3166_1_alpha3
1223+
12031224
TimeZone
12041225
12051226
This validates that a string value is a valid time zone based on the time zone database present on the system.
12061227
Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator.
12071228
More information on https://golang.org/pkg/time/#LoadLocation
12081229
12091230
Usage: timezone
1231+
12101232
12111233
Alias Validators and Tags
12121234
@@ -1219,6 +1241,8 @@ Here is a list of the current built in alias tags:
12191241
12201242
"iscolor"
12211243
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
1244+
"country_code"
1245+
alias is "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric" (Usage: country_code)
12221246
12231247
Validator notes:
12241248

validator_test.go

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10862,6 +10862,94 @@ func TestDatetimeValidation(t *testing.T) {
1086210862
}, "Bad field type int")
1086310863
}
1086410864

10865+
func TestIsIso3166Alpha2Validation(t *testing.T) {
10866+
tests := []struct {
10867+
value string `validate:"iso3166_1_alpha2"`
10868+
expected bool
10869+
}{
10870+
{"PL", true},
10871+
{"POL", false},
10872+
{"AA", false},
10873+
}
10874+
10875+
validate := New()
10876+
10877+
for i, test := range tests {
10878+
10879+
errs := validate.Var(test.value, "iso3166_1_alpha2")
10880+
10881+
if test.expected {
10882+
if !IsEqual(errs, nil) {
10883+
t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs)
10884+
}
10885+
} else {
10886+
if IsEqual(errs, nil) {
10887+
t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs)
10888+
}
10889+
}
10890+
}
10891+
}
10892+
10893+
func TestIsIso3166Alpha3Validation(t *testing.T) {
10894+
tests := []struct {
10895+
value string `validate:"iso3166_1_alpha3"`
10896+
expected bool
10897+
}{
10898+
{"POL", true},
10899+
{"PL", false},
10900+
{"AAA", false},
10901+
}
10902+
10903+
validate := New()
10904+
10905+
for i, test := range tests {
10906+
10907+
errs := validate.Var(test.value, "iso3166_1_alpha3")
10908+
10909+
if test.expected {
10910+
if !IsEqual(errs, nil) {
10911+
t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs)
10912+
}
10913+
} else {
10914+
if IsEqual(errs, nil) {
10915+
t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs)
10916+
}
10917+
}
10918+
}
10919+
}
10920+
10921+
func TestIsIso3166AlphaNumericValidation(t *testing.T) {
10922+
tests := []struct {
10923+
value int
10924+
expected bool
10925+
}{
10926+
{248, true},
10927+
{0, false},
10928+
{1, false},
10929+
}
10930+
10931+
validate := New()
10932+
10933+
for i, test := range tests {
10934+
10935+
errs := validate.Var(test.value, "iso3166_1_alpha_numeric")
10936+
10937+
if test.expected {
10938+
if !IsEqual(errs, nil) {
10939+
t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs)
10940+
}
10941+
} else {
10942+
if IsEqual(errs, nil) {
10943+
t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs)
10944+
}
10945+
}
10946+
}
10947+
10948+
PanicMatches(t, func() {
10949+
_ = validate.Var("1", "iso3166_1_alpha_numeric")
10950+
}, "Bad field type string")
10951+
}
10952+
1086510953
func TestTimeZoneValidation(t *testing.T) {
1086610954
tests := []struct {
1086710955
value string `validate:"timezone"`
@@ -10901,4 +10989,4 @@ func TestTimeZoneValidation(t *testing.T) {
1090110989
PanicMatches(t, func() {
1090210990
_ = validate.Var(2, "timezone")
1090310991
}, "Bad field type int")
10904-
}
10992+
}

0 commit comments

Comments
 (0)