From 288484ecb6bcdeff866b6b0670239f612c98eb80 Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 17:45:12 +0100 Subject: [PATCH 1/7] Added country specific post code validation --- formats_by_country.go | 185 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 formats_by_country.go diff --git a/formats_by_country.go b/formats_by_country.go new file mode 100644 index 0000000..071d994 --- /dev/null +++ b/formats_by_country.go @@ -0,0 +1,185 @@ +package postcode + +import "regexp" + +type PostCodeValidator struct { + CountryRegexMapping map[string]string +} + +func NewPostCodeValidator() *PostCodeValidator { + return &PostCodeValidator{ + CountryRegexMapping: map[string]string{ + "GB": `\?i^?i([A-Z]){1}([0-9][0-9]|[0-9]|[A-Z][0-9][A-Z]|[A-Z][0-9][0-9]|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z][A-z]){1}$`, + "JE": `\?i^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "GG": `\?i^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "IM": `\?i^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$$`, + "US": `\?i^([0-9]{5})(?:-([0-9]{4}))?$`, + "CA": `\?i^([ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ])\s*([0-9][ABCEGHJKLMNPRSTVWXYZ][0-9])$`, + "IE": `\?i^([AC-FHKNPRTV-Y][0-9]{2}|D6W)[ -]?[0-9AC-FHKNPRTV-Y]{4}$`, + "DE": `\?i^\d{5}$`, + "JP": `\?i^\d{3}-\d{4}$`, + "FR": `\?i^\d{2}[ ]?\d{3}$`, + "AU": `\?i^\d{4}$`, + "IT": `\?i^\d{5}$`, + "CH": `\?i^\d{4}$`, + "AT": `\?i^(?!0)\d{4}$`, + "ES": `\?i^(?:0[1-9]|[1-4]\d|5[0-2])\d{3}$`, + "NL": `\?i^\d{4}[ ]?[A-Z]{2}$`, + "BE": `\?i^\d{4}$`, + "DK": `\?i^\d{4}$`, + "SE": `\?i^(SE-)?\d{3}[ ]?\d{2}$`, + "NO": `\?i^\d{4}$`, + "BR": `\?i^\d{5}[\-]?\d{3}$`, + "PT": `\?i^\d{4}([\-]\d{3})?$`, + "FI": `\?i^(FI-|AX-)?\d{5}$`, + "AX": `\?i^22\d{3}$`, + "KR": `\?i^\d{5}$`, + "CN": `\?i^\d{6}$`, + "TW": `\?i^\d{3}(\d{2})?$`, + "SG": `\?i^\d{6}$`, + "DZ": `\?i^\d{5}$`, + "AD": `\?i^AD\d{3}$`, + "AR": `\?i^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$`, + "AM": `\?i^(37)?\d{4}$`, + "AZ": `\?i^\d{4}$`, + "BH": `\?i^((1[0-2]|[2-9])\d{2})?$`, + "BD": `\?i^\d{4}$`, + "BB": `\?i^(BB\d{5})?$`, + "BY": `\?i^\d{6}$`, + "BM": `\?i^[A-Z]{2}[ ]?[A-Z0-9]{2}$`, + "BA": `\?i^\d{5}$`, + "IO": `\?i^BBND 1ZZ$`, + "BN": `\?i^[A-Z]{2}[ ]?\d{4}$`, + "BG": `\?i^\d{4}$`, + "KH": `\?i^\d{5}$`, + "CV": `\?i^\d{4}$`, + "CL": `\?i^\d{7}$`, + "CR": `\?i^(\d{4,5}|\d{3}-\d{4})$`, + "HR": `\?i^(HR-)?\d{5}$`, + "CY": `\?i^\d{4}$`, + "CZ": `\?i^\d{3}[ ]?\d{2}$`, + "DO": `\?i^\d{5}$`, + "EC": `\?i^([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?$`, + "EG": `\?i^\d{5}$`, + "EE": `\?i^\d{5}$`, + "FO": `\?i^\d{3}$`, + "GE": `\?i^\d{4}$`, + "GR": `\?i^\d{3}[ ]?\d{2}$`, + "GL": `\?i^39\d{2}$`, + "GT": `\?i^\d{5}$`, + "HT": `\?i^\d{4}$`, + "HN": `\?i^(?:\d{5})?$`, + "HU": `\?i^\d{4}$`, + "IS": `\?i^\d{3}$`, + "IN": `\?i^\d{6}$`, + "ID": `\?i^\d{5}$`, + "IL": `\?i^\d{5,7}$`, + "JO": `\?i^\d{5}$`, + "KZ": `\?i^\d{6}$`, + "KE": `\?i^\d{5}$`, + "KW": `\?i^\d{5}$`, + "LA": `\?i^\d{5}$`, + "LV": `\?i^(LV-)?\d{4}$`, + "LB": `\?i^(\d{4}([ ]?\d{4})?)?$`, + "LI": `\?i^(948[5-9])|(949[0-7])$`, + "LT": `\?i^(LT-)?\d{5}$`, + "LU": `\?i^(L-)?\d{4}$`, + "MK": `\?i^\d{4}$`, + "MY": `\?i^\d{5}$`, + "MV": `\?i^\d{5}$`, + "MT": `\?i^[A-Z]{3}[ ]?\d{2,4}$`, + "MU": `\?i^((\d|[A-Z])\d{4})?$`, + "MX": `\?i^\d{5}$`, + "MD": `\?i^\d{4}$`, + "MC": `\?i^980\d{2}$`, + "MA": `\?i^\d{5}$`, + "NP": `\?i^\d{5}$`, + "NZ": `\?i^\d{4}$`, + "NI": `\?i^((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?$`, + "NG": `\?i^(\d{6})?$`, + "OM": `\?i^(PC )?\d{3}$`, + "PA": `\?i^\d{4}$`, + "PK": `\?i^\d{5}$`, + "PY": `\?i^\d{4}$`, + "PH": `\?i^\d{4}$`, + "PL": `\?i^\d{2}-\d{3}$`, + "PR": `\?i^00[679]\d{2}([ \-]\d{4})?$`, + "RO": `\?i^\d{6}$`, + "RU": `\?i^\d{6}$`, + "SM": `\?i^4789\d$`, + "SA": `\?i^\d{5}$`, + "SN": `\?i^\d{5}$`, + "SK": `\?i^\d{3}[ ]?\d{2}$`, + "SI": `\?i^(SI-)?\d{4}$`, + "ZA": `\?i^\d{4}$`, + "LK": `\?i^\d{5}$`, + "TJ": `\?i^\d{6}$`, + "TH": `\?i^\d{5}$`, + "TN": `\?i^\d{4}$`, + "TR": `\?i^\d{5}$`, + "TM": `\?i^\d{6}$`, + "UA": `\?i^\d{5}$`, + "UY": `\?i^\d{5}$`, + "UZ": `\?i^\d{6}$`, + "VA": `\?i^00120$`, + "VE": `\?i^\d{4}$`, + "ZM": `\?i^\d{5}$`, + "AS": `\?i^96799$`, + "CC": `\?i^6799$`, + "CK": `\?i^\d{4}$`, + "RS": `\?i^\d{5,6}$`, + "ME": `\?i^8\d{4}$`, + "CS": `\?i^\d{5}$`, + "YU": `\?i^\d{5}$`, + "CX": `\?i^6798$`, + "ET": `\?i^\d{4}$`, + "FK": `\?i^FIQQ 1ZZ$`, + "NF": `\?i^2899$`, + "FM": `\?i^(9694[1-4])([ \-]\d{4})?$`, + "GF": `\?i^9[78]3\d{2}$`, + "GN": `\?i^\d{3}$`, + "GP": `\?i^9[78][01]\d{2}$`, + "GS": `\?i^SIQQ 1ZZ$`, + "GU": `\?i^969[123]\d([ \-]\d{4})?$`, + "GW": `\?i^\d{4}$`, + "HM": `\?i^\d{4}$`, + "IQ": `\?i^\d{5}$`, + "KG": `\?i^\d{6}$`, + "LR": `\?i^\d{4}$`, + "LS": `\?i^\d{3}$`, + "MG": `\?i^\d{3}$`, + "MH": `\?i^969[67]\d([ \-]\d{4})?$`, + "MN": `\?i^\d{6}$`, + "MP": `\?i^9695[012]([ \-]\d{4})?$`, + "MQ": `\?i^9[78]2\d{2}$`, + "NC": `\?i^988\d{2}$`, + "NE": `\?i^\d{4}$`, + "VI": `\?i^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`, + "VN": `\?i^\d{6}$`, + "PF": `\?i^987\d{2}$`, + "PG": `\?i^\d{3}$`, + "PM": `\?i^9[78]5\d{2}$`, + "PN": `\?i^PCRN 1ZZ$`, + "PW": `\?i^96940$`, + "RE": `\?i^9[78]4\d{2}$`, + "SH": `\?i^(ASCN|STHL) 1ZZ$`, + "SJ": `\?i^\d{4}$`, + "SO": `\?i^\d{5}$`, + "SZ": `\?i^[HLMS]\d{3}$`, + "TC": `\?i^TKCA 1ZZ$`, + "WF": `\?i^986\d{2}$`, + "XK": `\?i^\d{5}$`, + "YT": `\?i^976\d{2}$`, + "INTL": `\?i^(?:[A-Z0-9]+([- ]?[A-Z0-9]+)*)?$`, + }, + } +} + +func (v *PostCodeValidator) ValidatePostalCode(countryCode, postalCode string) bool { + regex, ok := regexp.Compile(v.CountryRegexMapping[countryCode]) + if ok != nil { + return false + } + + return regex.MatchString(postalCode) +} From 0591218e84c5e8006923d20ac396ac315f8a680d Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 17:45:23 +0100 Subject: [PATCH 2/7] Fixed wording on test cases --- postcode_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postcode_test.go b/postcode_test.go index ff3c164..8cc560a 100644 --- a/postcode_test.go +++ b/postcode_test.go @@ -51,12 +51,12 @@ func TestValidate(t *testing.T) { expected: ErrShort, }, { - description: "Inexistent country code", + description: "Non-existant country code", input: "TY 1234", expected: ErrInvalidCountry, }, { - description: "Inexistent postal code format", + description: "Non-existant postal code format", input: "11111111111", expected: ErrInvalidFormat, }, From 1800667802244841510302350a02ffdd6eb0d89b Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 18:22:59 +0100 Subject: [PATCH 3/7] Added test assertions --- go.mod | 6 ++++++ go.sum | 6 ++++++ postcode_test.go | 7 +++---- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 go.sum diff --git a/go.mod b/go.mod index f1b5b97..4da6e24 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module github.com/adrg/postcode go 1.14 + +require ( + github.com/google/go-cmp v0.5.9 // indirect + github.com/pkg/errors v0.9.1 // indirect + gotest.tools v2.2.0+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bc7511f --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/postcode_test.go b/postcode_test.go index 8cc560a..9cfc8aa 100644 --- a/postcode_test.go +++ b/postcode_test.go @@ -2,6 +2,8 @@ package postcode import ( "testing" + + "gotest.tools/assert" ) func TestValidate(t *testing.T) { @@ -63,9 +65,6 @@ func TestValidate(t *testing.T) { } for _, testCase := range testCases { - t.Logf("Validating `%s` (%s)", testCase.input, testCase.description) - if err := Validate(testCase.input); err != testCase.expected { - t.Errorf("expected: %v; got: %v", testCase.expected, err) - } + assert.Equal(t, Validate(testCase.input), testCase.expected) } } From 529cbb8528030fa9dc270fde53cc74187116fc1b Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 18:24:15 +0100 Subject: [PATCH 4/7] renamed formats_by_country to postcode_by_country --- formats_by_country.go | 185 --------------------------------------- postcode_by_country.go | 194 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 185 deletions(-) delete mode 100644 formats_by_country.go create mode 100644 postcode_by_country.go diff --git a/formats_by_country.go b/formats_by_country.go deleted file mode 100644 index 071d994..0000000 --- a/formats_by_country.go +++ /dev/null @@ -1,185 +0,0 @@ -package postcode - -import "regexp" - -type PostCodeValidator struct { - CountryRegexMapping map[string]string -} - -func NewPostCodeValidator() *PostCodeValidator { - return &PostCodeValidator{ - CountryRegexMapping: map[string]string{ - "GB": `\?i^?i([A-Z]){1}([0-9][0-9]|[0-9]|[A-Z][0-9][A-Z]|[A-Z][0-9][0-9]|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z][A-z]){1}$`, - "JE": `\?i^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, - "GG": `\?i^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, - "IM": `\?i^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$$`, - "US": `\?i^([0-9]{5})(?:-([0-9]{4}))?$`, - "CA": `\?i^([ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ])\s*([0-9][ABCEGHJKLMNPRSTVWXYZ][0-9])$`, - "IE": `\?i^([AC-FHKNPRTV-Y][0-9]{2}|D6W)[ -]?[0-9AC-FHKNPRTV-Y]{4}$`, - "DE": `\?i^\d{5}$`, - "JP": `\?i^\d{3}-\d{4}$`, - "FR": `\?i^\d{2}[ ]?\d{3}$`, - "AU": `\?i^\d{4}$`, - "IT": `\?i^\d{5}$`, - "CH": `\?i^\d{4}$`, - "AT": `\?i^(?!0)\d{4}$`, - "ES": `\?i^(?:0[1-9]|[1-4]\d|5[0-2])\d{3}$`, - "NL": `\?i^\d{4}[ ]?[A-Z]{2}$`, - "BE": `\?i^\d{4}$`, - "DK": `\?i^\d{4}$`, - "SE": `\?i^(SE-)?\d{3}[ ]?\d{2}$`, - "NO": `\?i^\d{4}$`, - "BR": `\?i^\d{5}[\-]?\d{3}$`, - "PT": `\?i^\d{4}([\-]\d{3})?$`, - "FI": `\?i^(FI-|AX-)?\d{5}$`, - "AX": `\?i^22\d{3}$`, - "KR": `\?i^\d{5}$`, - "CN": `\?i^\d{6}$`, - "TW": `\?i^\d{3}(\d{2})?$`, - "SG": `\?i^\d{6}$`, - "DZ": `\?i^\d{5}$`, - "AD": `\?i^AD\d{3}$`, - "AR": `\?i^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$`, - "AM": `\?i^(37)?\d{4}$`, - "AZ": `\?i^\d{4}$`, - "BH": `\?i^((1[0-2]|[2-9])\d{2})?$`, - "BD": `\?i^\d{4}$`, - "BB": `\?i^(BB\d{5})?$`, - "BY": `\?i^\d{6}$`, - "BM": `\?i^[A-Z]{2}[ ]?[A-Z0-9]{2}$`, - "BA": `\?i^\d{5}$`, - "IO": `\?i^BBND 1ZZ$`, - "BN": `\?i^[A-Z]{2}[ ]?\d{4}$`, - "BG": `\?i^\d{4}$`, - "KH": `\?i^\d{5}$`, - "CV": `\?i^\d{4}$`, - "CL": `\?i^\d{7}$`, - "CR": `\?i^(\d{4,5}|\d{3}-\d{4})$`, - "HR": `\?i^(HR-)?\d{5}$`, - "CY": `\?i^\d{4}$`, - "CZ": `\?i^\d{3}[ ]?\d{2}$`, - "DO": `\?i^\d{5}$`, - "EC": `\?i^([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?$`, - "EG": `\?i^\d{5}$`, - "EE": `\?i^\d{5}$`, - "FO": `\?i^\d{3}$`, - "GE": `\?i^\d{4}$`, - "GR": `\?i^\d{3}[ ]?\d{2}$`, - "GL": `\?i^39\d{2}$`, - "GT": `\?i^\d{5}$`, - "HT": `\?i^\d{4}$`, - "HN": `\?i^(?:\d{5})?$`, - "HU": `\?i^\d{4}$`, - "IS": `\?i^\d{3}$`, - "IN": `\?i^\d{6}$`, - "ID": `\?i^\d{5}$`, - "IL": `\?i^\d{5,7}$`, - "JO": `\?i^\d{5}$`, - "KZ": `\?i^\d{6}$`, - "KE": `\?i^\d{5}$`, - "KW": `\?i^\d{5}$`, - "LA": `\?i^\d{5}$`, - "LV": `\?i^(LV-)?\d{4}$`, - "LB": `\?i^(\d{4}([ ]?\d{4})?)?$`, - "LI": `\?i^(948[5-9])|(949[0-7])$`, - "LT": `\?i^(LT-)?\d{5}$`, - "LU": `\?i^(L-)?\d{4}$`, - "MK": `\?i^\d{4}$`, - "MY": `\?i^\d{5}$`, - "MV": `\?i^\d{5}$`, - "MT": `\?i^[A-Z]{3}[ ]?\d{2,4}$`, - "MU": `\?i^((\d|[A-Z])\d{4})?$`, - "MX": `\?i^\d{5}$`, - "MD": `\?i^\d{4}$`, - "MC": `\?i^980\d{2}$`, - "MA": `\?i^\d{5}$`, - "NP": `\?i^\d{5}$`, - "NZ": `\?i^\d{4}$`, - "NI": `\?i^((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?$`, - "NG": `\?i^(\d{6})?$`, - "OM": `\?i^(PC )?\d{3}$`, - "PA": `\?i^\d{4}$`, - "PK": `\?i^\d{5}$`, - "PY": `\?i^\d{4}$`, - "PH": `\?i^\d{4}$`, - "PL": `\?i^\d{2}-\d{3}$`, - "PR": `\?i^00[679]\d{2}([ \-]\d{4})?$`, - "RO": `\?i^\d{6}$`, - "RU": `\?i^\d{6}$`, - "SM": `\?i^4789\d$`, - "SA": `\?i^\d{5}$`, - "SN": `\?i^\d{5}$`, - "SK": `\?i^\d{3}[ ]?\d{2}$`, - "SI": `\?i^(SI-)?\d{4}$`, - "ZA": `\?i^\d{4}$`, - "LK": `\?i^\d{5}$`, - "TJ": `\?i^\d{6}$`, - "TH": `\?i^\d{5}$`, - "TN": `\?i^\d{4}$`, - "TR": `\?i^\d{5}$`, - "TM": `\?i^\d{6}$`, - "UA": `\?i^\d{5}$`, - "UY": `\?i^\d{5}$`, - "UZ": `\?i^\d{6}$`, - "VA": `\?i^00120$`, - "VE": `\?i^\d{4}$`, - "ZM": `\?i^\d{5}$`, - "AS": `\?i^96799$`, - "CC": `\?i^6799$`, - "CK": `\?i^\d{4}$`, - "RS": `\?i^\d{5,6}$`, - "ME": `\?i^8\d{4}$`, - "CS": `\?i^\d{5}$`, - "YU": `\?i^\d{5}$`, - "CX": `\?i^6798$`, - "ET": `\?i^\d{4}$`, - "FK": `\?i^FIQQ 1ZZ$`, - "NF": `\?i^2899$`, - "FM": `\?i^(9694[1-4])([ \-]\d{4})?$`, - "GF": `\?i^9[78]3\d{2}$`, - "GN": `\?i^\d{3}$`, - "GP": `\?i^9[78][01]\d{2}$`, - "GS": `\?i^SIQQ 1ZZ$`, - "GU": `\?i^969[123]\d([ \-]\d{4})?$`, - "GW": `\?i^\d{4}$`, - "HM": `\?i^\d{4}$`, - "IQ": `\?i^\d{5}$`, - "KG": `\?i^\d{6}$`, - "LR": `\?i^\d{4}$`, - "LS": `\?i^\d{3}$`, - "MG": `\?i^\d{3}$`, - "MH": `\?i^969[67]\d([ \-]\d{4})?$`, - "MN": `\?i^\d{6}$`, - "MP": `\?i^9695[012]([ \-]\d{4})?$`, - "MQ": `\?i^9[78]2\d{2}$`, - "NC": `\?i^988\d{2}$`, - "NE": `\?i^\d{4}$`, - "VI": `\?i^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`, - "VN": `\?i^\d{6}$`, - "PF": `\?i^987\d{2}$`, - "PG": `\?i^\d{3}$`, - "PM": `\?i^9[78]5\d{2}$`, - "PN": `\?i^PCRN 1ZZ$`, - "PW": `\?i^96940$`, - "RE": `\?i^9[78]4\d{2}$`, - "SH": `\?i^(ASCN|STHL) 1ZZ$`, - "SJ": `\?i^\d{4}$`, - "SO": `\?i^\d{5}$`, - "SZ": `\?i^[HLMS]\d{3}$`, - "TC": `\?i^TKCA 1ZZ$`, - "WF": `\?i^986\d{2}$`, - "XK": `\?i^\d{5}$`, - "YT": `\?i^976\d{2}$`, - "INTL": `\?i^(?:[A-Z0-9]+([- ]?[A-Z0-9]+)*)?$`, - }, - } -} - -func (v *PostCodeValidator) ValidatePostalCode(countryCode, postalCode string) bool { - regex, ok := regexp.Compile(v.CountryRegexMapping[countryCode]) - if ok != nil { - return false - } - - return regex.MatchString(postalCode) -} diff --git a/postcode_by_country.go b/postcode_by_country.go new file mode 100644 index 0000000..226daf4 --- /dev/null +++ b/postcode_by_country.go @@ -0,0 +1,194 @@ +package postcode + +import ( + "regexp" + "strings" +) + +type PostCodeValidator struct { + CountryRegexMapping map[string]string +} + +// Regexes taken from https://github.com/melwynfurtado/postcode-validator +func NewPostCodeValidator() *PostCodeValidator { + return &PostCodeValidator{ + CountryRegexMapping: map[string]string{ + "GB": `^?i([A-Z]){1}([0-9][0-9]|[0-9]|[A-Z][0-9][A-Z]|[A-Z][0-9][0-9]|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z][A-z]){1}$`, + "JE": `^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "GG": `^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, + "IM": `^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$$`, + "US": `^([0-9]{5})(?:-([0-9]{4}))?$`, + "CA": `^([ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ])\s*([0-9][ABCEGHJKLMNPRSTVWXYZ][0-9])$`, + "IE": `^([AC-FHKNPRTV-Y][0-9]{2}|D6W)[ -]?[0-9AC-FHKNPRTV-Y]{4}$`, + "DE": `^\d{5}$`, + "JP": `^\d{3}-\d{4}$`, + "FR": `\d{2}[ ]?\d{3}`, + "AU": `^\d{4}$`, + "IT": `^\d{5}$`, + "CH": `^\d{4}$`, + "AT": `^(?!0)\d{4}$`, + "ES": `^(?:0[1-9]|[1-4]\d|5[0-2])\d{3}$`, + "NL": `^\d{4}[ ]?[A-Z]{2}$`, + "BE": `^\d{4}$`, + "DK": `^\d{4}$`, + "SE": `^(SE-)?\d{3}[ ]?\d{2}$`, + "NO": `^\d{4}$`, + "BR": `^\d{5}[\-]?\d{3}$`, + "PT": `^\d{4}([\-]\d{3})?$`, + "FI": `^(FI-|AX-)?\d{5}$`, + "AX": `^22\d{3}$`, + "KR": `^\d{5}$`, + "CN": `^\d{6}$`, + "TW": `^\d{3}(\d{2})?$`, + "SG": `^\d{6}$`, + "DZ": `^\d{5}$`, + "AD": `^AD\d{3}$`, + "AR": `^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$`, + "AM": `^(37)?\d{4}$`, + "AZ": `^\d{4}$`, + "BH": `^((1[0-2]|[2-9])\d{2})?$`, + "BD": `^\d{4}$`, + "BB": `^(BB\d{5})?$`, + "BY": `^\d{6}$`, + "BM": `^[A-Z]{2}[ ]?[A-Z0-9]{2}$`, + "BA": `^\d{5}$`, + "IO": `^BBND 1ZZ$`, + "BN": `^[A-Z]{2}[ ]?\d{4}$`, + "BG": `^\d{4}$`, + "KH": `^\d{5}$`, + "CV": `^\d{4}$`, + "CL": `^\d{7}$`, + "CR": `^(\d{4,5}|\d{3}-\d{4})$`, + "HR": `^(HR-)?\d{5}$`, + "CY": `^\d{4}$`, + "CZ": `^\d{3}[ ]?\d{2}$`, + "DO": `^\d{5}$`, + "EC": `^([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?$`, + "EG": `^\d{5}$`, + "EE": `^\d{5}$`, + "FO": `^\d{3}$`, + "GE": `^\d{4}$`, + "GR": `^\d{3}[ ]?\d{2}$`, + "GL": `^39\d{2}$`, + "GT": `^\d{5}$`, + "HT": `^\d{4}$`, + "HN": `^(?:\d{5})?$`, + "HU": `^\d{4}$`, + "IS": `^\d{3}$`, + "IN": `^\d{6}$`, + "ID": `^\d{5}$`, + "IL": `^\d{5,7}$`, + "JO": `^\d{5}$`, + "KZ": `^\d{6}$`, + "KE": `^\d{5}$`, + "KW": `^\d{5}$`, + "LA": `^\d{5}$`, + "LV": `^(LV-)?\d{4}$`, + "LB": `^(\d{4}([ ]?\d{4})?)?$`, + "LI": `^(948[5-9])|(949[0-7])$`, + "LT": `^(LT-)?\d{5}$`, + "LU": `^(L-)?\d{4}$`, + "MK": `^\d{4}$`, + "MY": `^\d{5}$`, + "MV": `^\d{5}$`, + "MT": `^[A-Z]{3}[ ]?\d{2,4}$`, + "MU": `^((\d|[A-Z])\d{4})?$`, + "MX": `^\d{5}$`, + "MD": `^\d{4}$`, + "MC": `^980\d{2}$`, + "MA": `^\d{5}$`, + "NP": `^\d{5}$`, + "NZ": `^\d{4}$`, + "NI": `^((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?$`, + "NG": `^(\d{6})?$`, + "OM": `^(PC )?\d{3}$`, + "PA": `^\d{4}$`, + "PK": `^\d{5}$`, + "PY": `^\d{4}$`, + "PH": `^\d{4}$`, + "PL": `^\d{2}-\d{3}$`, + "PR": `^00[679]\d{2}([ \-]\d{4})?$`, + "RO": `^\d{6}$`, + "RU": `^\d{6}$`, + "SM": `^4789\d$`, + "SA": `^\d{5}$`, + "SN": `^\d{5}$`, + "SK": `^\d{3}[ ]?\d{2}$`, + "SI": `^(SI-)?\d{4}$`, + "ZA": `^\d{4}$`, + "LK": `^\d{5}$`, + "TJ": `^\d{6}$`, + "TH": `^\d{5}$`, + "TN": `^\d{4}$`, + "TR": `^\d{5}$`, + "TM": `^\d{6}$`, + "UA": `^\d{5}$`, + "UY": `^\d{5}$`, + "UZ": `^\d{6}$`, + "VA": `^00120$`, + "VE": `^\d{4}$`, + "ZM": `^\d{5}$`, + "AS": `^96799$`, + "CC": `^6799$`, + "CK": `^\d{4}$`, + "RS": `^\d{5,6}$`, + "ME": `^8\d{4}$`, + "CS": `^\d{5}$`, + "YU": `^\d{5}$`, + "CX": `^6798$`, + "ET": `^\d{4}$`, + "FK": `^FIQQ 1ZZ$`, + "NF": `^2899$`, + "FM": `^(9694[1-4])([ \-]\d{4})?$`, + "GF": `^9[78]3\d{2}$`, + "GN": `^\d{3}$`, + "GP": `^9[78][01]\d{2}$`, + "GS": `^SIQQ 1ZZ$`, + "GU": `^969[123]\d([ \-]\d{4})?$`, + "GW": `^\d{4}$`, + "HM": `^\d{4}$`, + "IQ": `^\d{5}$`, + "KG": `^\d{6}$`, + "LR": `^\d{4}$`, + "LS": `^\d{3}$`, + "MG": `^\d{3}$`, + "MH": `^969[67]\d([ \-]\d{4})?$`, + "MN": `^\d{6}$`, + "MP": `^9695[012]([ \-]\d{4})?$`, + "MQ": `^9[78]2\d{2}$`, + "NC": `^988\d{2}$`, + "NE": `^\d{4}$`, + "VI": `^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`, + "VN": `^\d{6}$`, + "PF": `^987\d{2}$`, + "PG": `^\d{3}$`, + "PM": `^9[78]5\d{2}$`, + "PN": `^PCRN 1ZZ$`, + "PW": `^96940$`, + "RE": `^9[78]4\d{2}$`, + "SH": `^(ASCN|STHL) 1ZZ$`, + "SJ": `^\d{4}$`, + "SO": `^\d{5}$`, + "SZ": `^[HLMS]\d{3}$`, + "TC": `^TKCA 1ZZ$`, + "WF": `^986\d{2}$`, + "XK": `^\d{5}$`, + "YT": `^976\d{2}$`, + "INTL": `^(?:[A-Z0-9]+([- ]?[A-Z0-9]+)*)?$`, + }, + } +} + +func (v *PostCodeValidator) ValidatePostalCode(countryCode, postalCode string) bool { + regexForCountry, ok := v.CountryRegexMapping[strings.ToUpper(countryCode)] + if !ok { + return false + } + + regex, err := regexp.Compile(regexForCountry) + if err != nil { + return false + } + + return regex.MatchString(strings.ToUpper(postalCode)) +} From 6d01e2ec35fbb35233accad789a5ad7577db76f2 Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 18:24:45 +0100 Subject: [PATCH 5/7] Added tests for validation by country --- postcode_by_country_test.go | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 postcode_by_country_test.go diff --git a/postcode_by_country_test.go b/postcode_by_country_test.go new file mode 100644 index 0000000..366e633 --- /dev/null +++ b/postcode_by_country_test.go @@ -0,0 +1,90 @@ +package postcode + +import ( + "testing" + + "gotest.tools/assert" +) + +func TestValidateByCountry(t *testing.T) { + testCases := []struct { + description string + country string + postCode string + expected bool + }{ + { + description: "Paris, France", + country: "FR", + postCode: "75008", + expected: true, + }, + { + description: "Brussels, Belgium", + country: "BE", + postCode: "1000", + expected: true, + }, + { + description: "Utrecht, The Netherlands", + country: "NL", + postCode: "3511 ax", + expected: true, + }, + { + description: "Utrecht, The Netherlands, Alt", + country: "NL", + postCode: "3511AX", + expected: true, + }, + { + description: "Hannover, Germany", + country: "DE", + postCode: "30179", + expected: true, + }, + { + description: "Vilnius, Lithuania", + country: "LT", + postCode: "LT-00200", + expected: true, + }, + { + description: "Empty postal code", + country: "FR", + postCode: "", + expected: false, + }, + { + description: "Short postal code", + country: "FR", + postCode: "A", + expected: false, + }, + { + description: "Non-existant country code", + country: "TY", + postCode: "TY 1234", + expected: false, + }, + { + description: "Non-existant postal code format", + country: "US", + postCode: "11111111111", + expected: false, + }, + { + description: "Postal code for wrong country", + country: "FR", + postCode: "1111", + expected: false, + }, + } + + postCodeValidator := NewPostCodeValidator() + + for _, testCase := range testCases { + isValid := postCodeValidator.ValidatePostalCode(testCase.country, testCase.postCode) + assert.Equal(t, isValid, testCase.expected) + } +} From 873c2c0e829fc778137304cf7050351d086fb1f0 Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Mon, 23 Jan 2023 18:25:04 +0100 Subject: [PATCH 6/7] Updated readme for country specific validation --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d69ce64..268c943 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ func main() { if err := postcode.Validate("10007"); err != nil { // Treat error. } + + postCodeValidator := NewPostCodeValidator() + if isValid := postCodeValidator.ValidatePostalCode("FR", "1000"); !isValid { + // Do stuff here + } } ``` From 9b06e5040a758b1b6e062c588d3f24ab72c5079e Mon Sep 17 00:00:00 2001 From: Jerome Illgner Date: Wed, 22 Mar 2023 10:55:59 +0100 Subject: [PATCH 7/7] Improved support for GB postcodes --- postcode_by_country.go | 11 ++++++----- postcode_by_country_test.go | 33 +++++++++++++++++++++++---------- postcode_test.go | 9 ++++++++- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/postcode_by_country.go b/postcode_by_country.go index 226daf4..6a841df 100644 --- a/postcode_by_country.go +++ b/postcode_by_country.go @@ -1,6 +1,7 @@ package postcode import ( + "errors" "regexp" "strings" ) @@ -13,7 +14,7 @@ type PostCodeValidator struct { func NewPostCodeValidator() *PostCodeValidator { return &PostCodeValidator{ CountryRegexMapping: map[string]string{ - "GB": `^?i([A-Z]){1}([0-9][0-9]|[0-9]|[A-Z][0-9][A-Z]|[A-Z][0-9][0-9]|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z][A-z]){1}$`, + "GB": `^(?i)([A-Z]){1}([0-9][0-9]|[0-9]|[A-Z][0-9][A-Z]|[A-Z][0-9][0-9]|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-z][A-z]){1}$`, "JE": `^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, "GG": `^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$`, "IM": `^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$$`, @@ -179,16 +180,16 @@ func NewPostCodeValidator() *PostCodeValidator { } } -func (v *PostCodeValidator) ValidatePostalCode(countryCode, postalCode string) bool { +func (v *PostCodeValidator) ValidatePostalCode(countryCode, postalCode string) (bool, error) { regexForCountry, ok := v.CountryRegexMapping[strings.ToUpper(countryCode)] if !ok { - return false + return false, errors.New("country code not found") } regex, err := regexp.Compile(regexForCountry) if err != nil { - return false + return false, errors.New("regex does not compile " + err.Error()) } - return regex.MatchString(strings.ToUpper(postalCode)) + return regex.MatchString(strings.ToUpper(postalCode)), nil } diff --git a/postcode_by_country_test.go b/postcode_by_country_test.go index 366e633..5fbecde 100644 --- a/postcode_by_country_test.go +++ b/postcode_by_country_test.go @@ -8,10 +8,11 @@ import ( func TestValidateByCountry(t *testing.T) { testCases := []struct { - description string - country string - postCode string - expected bool + description string + country string + postCode string + expected bool + errorExpected bool }{ { description: "Paris, France", @@ -62,10 +63,11 @@ func TestValidateByCountry(t *testing.T) { expected: false, }, { - description: "Non-existant country code", - country: "TY", - postCode: "TY 1234", - expected: false, + description: "Non-existant country code", + country: "TY", + postCode: "TY 1234", + expected: false, + errorExpected: true, }, { description: "Non-existant postal code format", @@ -79,12 +81,23 @@ func TestValidateByCountry(t *testing.T) { postCode: "1111", expected: false, }, + { + description: "UK Postcode", + country: "GB", + postCode: "KT111AT", + expected: true, + }, } postCodeValidator := NewPostCodeValidator() for _, testCase := range testCases { - isValid := postCodeValidator.ValidatePostalCode(testCase.country, testCase.postCode) - assert.Equal(t, isValid, testCase.expected) + t.Run(testCase.description, func(tt *testing.T) { + isValid, err := postCodeValidator.ValidatePostalCode(testCase.country, testCase.postCode) + if !testCase.errorExpected { + assert.NilError(tt, err) + } + assert.Equal(tt, isValid, testCase.expected) + }) } } diff --git a/postcode_test.go b/postcode_test.go index 9cfc8aa..8a8b441 100644 --- a/postcode_test.go +++ b/postcode_test.go @@ -62,9 +62,16 @@ func TestValidate(t *testing.T) { input: "11111111111", expected: ErrInvalidFormat, }, + { + description: "Valid UK postcode", + input: "KT11 1AT", + expected: nil, + }, } for _, testCase := range testCases { - assert.Equal(t, Validate(testCase.input), testCase.expected) + t.Run(testCase.description, func(tt *testing.T) { + assert.Equal(tt, Validate(testCase.input), testCase.expected) + }) } }