Skip to content

Commit ede7eab

Browse files
committed
Add zip-code-assert
1 parent c9b90d9 commit ede7eab

File tree

6 files changed

+297
-2
lines changed

6 files changed

+297
-2
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,14 @@ Tests if the value is a valid uuid.
259259
#### Arguments
260260
- `version` (optional) - the version to test the uuid for. Supported version are `3`, `4` and `5`. Defaults to test for `all` three if omitted.
261261

262+
### ZipCode
263+
Tests if the value is a valid zip code.
264+
This validator also checks if the country is a valid country from our codebase and validates if this zip code is valid based on country state (only checks countries on [zip-codes repository](https://github.com/uphold/zip-codes)).
265+
266+
#### Arguments
267+
- `country` (required) - the country which zip code is from.
268+
- `state` (optional) - the state which zip code is from.
269+
262270
## Usage
263271
The following is an example for the extra ip assert:
264272

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
"validator.js": "^2.0.0"
4242
},
4343
"devDependencies": {
44+
"@uphold/countries": "https://github.com/uphold/countries#v5.1.0",
4445
"@uphold/github-changelog-generator": "^3.4.0",
46+
"@uphold/zip-codes": "https://github.com/uphold/zip-codes#v1.0.0",
4547
"release-it": "^17.0.1",
4648
"abavalidator": "^2.0.1",
4749
"bignumber.js": "^9.0.0",

src/asserts/zip-code-assert.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
7+
const { Assert, Validator, Violation } = require('validator.js');
8+
const { CaZipCode, UsZipCode } = require('../index.js');
9+
const {
10+
common: { getCountriesAlpha2 }
11+
} = require('@uphold/countries');
12+
const _ = require('lodash');
13+
const zipCodes = require('@uphold/zip-codes');
14+
15+
/**
16+
* Constants.
17+
*/
18+
19+
const countriesAlpha2 = getCountriesAlpha2();
20+
21+
/**
22+
* Instances.
23+
*/
24+
25+
const is = Assert.extend({ CaZipCode, UsZipCode });
26+
27+
/**
28+
* Export `ZipCodeAssert`.
29+
*/
30+
31+
module.exports = function zipCodeAssert(data = {}) {
32+
/**
33+
* Class name.
34+
*/
35+
36+
this.__class__ = 'ZipCode';
37+
this.country = data && data.country;
38+
this.state = data && data.state;
39+
40+
/**
41+
* Validation algorithm.
42+
*/
43+
44+
this.validate = function (value) {
45+
if (value !== null && !_.isString(value)) {
46+
throw new Violation(this, value, { value: 'must_be_null_or_a_string' });
47+
}
48+
49+
if (!_.isString(this.country)) {
50+
throw new Violation(this, value, { country: Validator.errorCode.must_be_a_string });
51+
}
52+
53+
if (!countriesAlpha2.includes(this.country)) {
54+
throw new Violation(this, value, { reason: 'invalid-country' });
55+
}
56+
57+
if (this.country === 'US' && is.usZipCode().check(value) !== true) {
58+
throw new Violation(this, value, { reason: 'invalid-zip-code' });
59+
}
60+
61+
if (this.country === 'CA' && is.caZipCode().check(value) !== true) {
62+
throw new Violation(this, value, { reason: 'invalid-zip-code' });
63+
}
64+
65+
const state = this.state && this.state.split('-')[1];
66+
67+
if (
68+
this.country &&
69+
state &&
70+
_.has(zipCodes, `${this.country}.${state}`) &&
71+
!(zipCodes[this.country][state] || []).find(zip => value.toUpperCase().startsWith(zip.toUpperCase()))
72+
) {
73+
throw new Violation(this, value, { reason: 'invalid-state-zip-code' });
74+
}
75+
76+
return true;
77+
};
78+
79+
return this;
80+
};

src/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const Uri = require('./asserts/uri-assert.js');
4545
const UsSubdivision = require('./asserts/us-subdivision-assert.js');
4646
const UsZipCode = require('./asserts/us-zip-code-assert.js');
4747
const Uuid = require('./asserts/uuid-assert.js');
48+
const ZipCode = require('./asserts/zip-code-assert.js');
4849

4950
/**
5051
* Export asserts.
@@ -91,5 +92,6 @@ module.exports = {
9192
Uri,
9293
UsSubdivision,
9394
UsZipCode,
94-
Uuid
95+
Uuid,
96+
ZipCode
9597
};

test/asserts/zip-code-assert.test.js

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
'use strict';
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
7+
const { Assert: BaseAssert, Validator, Violation } = require('validator.js');
8+
const zipCodeAssert = require('../../src/asserts/zip-code-assert');
9+
10+
/**
11+
* Extend `Assert` with `UuidAssert`.
12+
*/
13+
14+
const Assert = BaseAssert.extend({
15+
ZipCode: zipCodeAssert
16+
});
17+
18+
/**
19+
* Test `ZipCodeAssert`.
20+
*/
21+
22+
describe('ZipCodeAssert', () => {
23+
it('should throw an error if the input value is invalid', () => {
24+
const types = [[], {}, 0];
25+
26+
types.forEach(type => {
27+
try {
28+
Assert.zipCode().validate(type);
29+
30+
fail();
31+
} catch (e) {
32+
expect(e).toBeInstanceOf(Violation);
33+
expect(e.show().assert).toBe('ZipCode');
34+
expect(e.violation.value).toBe('must_be_null_or_a_string');
35+
}
36+
});
37+
});
38+
39+
it('should throw an error if the country is invalid', () => {
40+
try {
41+
Assert.zipCode({ country: null }).validate('12345');
42+
43+
fail();
44+
} catch (e) {
45+
expect(e).toBeInstanceOf(Violation);
46+
expect(e.show().assert).toBe('ZipCode');
47+
expect(e.show().violation.country).toBe(Validator.errorCode.must_be_a_string);
48+
}
49+
});
50+
51+
it('should throw an error if the country is unknown', () => {
52+
try {
53+
Assert.zipCode({ country: 'ZZ' }).validate('12345');
54+
55+
fail();
56+
} catch (e) {
57+
expect(e).toBeInstanceOf(Violation);
58+
expect(e.show().assert).toBe('ZipCode');
59+
expect(e.show().violation.reason).toBe('invalid-country');
60+
}
61+
});
62+
63+
it('should throw an error if the zip code is not in a valid CA zip code format and user is from CA', () => {
64+
try {
65+
Assert.zipCode({ country: 'CA' }).validate('1234');
66+
67+
fail();
68+
} catch (e) {
69+
expect(e).toBeInstanceOf(Violation);
70+
expect(e.show().assert).toBe('ZipCode');
71+
expect(e.show().violation.reason).toBe('invalid-zip-code');
72+
}
73+
});
74+
75+
it('should throw an error if the zip code is not in a valid US zip code format and user if from US', () => {
76+
try {
77+
Assert.zipCode({ country: 'US' }).validate('1234');
78+
79+
fail();
80+
} catch (e) {
81+
expect(e).toBeInstanceOf(Violation);
82+
expect(e.show().assert).toBe('ZipCode');
83+
expect(e.show().violation.reason).toBe('invalid-zip-code');
84+
}
85+
});
86+
87+
it('should throw an error if the zip code is not valid for the given CA state', () => {
88+
try {
89+
Assert.zipCode({ country: 'CA', state: 'CA-AB' }).validate('A1A1A2');
90+
91+
fail();
92+
} catch (e) {
93+
expect(e).toBeInstanceOf(Violation);
94+
expect(e.show().assert).toBe('ZipCode');
95+
expect(e.show().violation.reason).toBe('invalid-state-zip-code');
96+
}
97+
});
98+
99+
it('should throw an error if the zip code is not valid for the given US state', () => {
100+
try {
101+
Assert.zipCode({ country: 'US', state: 'US-CA' }).validate('12345');
102+
103+
fail();
104+
} catch (e) {
105+
expect(e).toBeInstanceOf(Violation);
106+
expect(e.show().assert).toBe('ZipCode');
107+
expect(e.show().violation.reason).toBe('invalid-state-zip-code');
108+
}
109+
});
110+
111+
it('should accept valid CA zip codes', () => {
112+
['A1A1A1', 'C5D-1B3', 'G0A 5B6'].forEach(code => Assert.zipCode({ country: 'CA' }).validate(code));
113+
});
114+
115+
it('should accept valid US zip codes', () => {
116+
['12345', '12345-1234', '12345 1234', '123456789'].forEach(code =>
117+
Assert.zipCode({ country: 'US' }).validate(code)
118+
);
119+
});
120+
121+
it('should accept valid US territory zip codes', () => {
122+
['12345', '12345-1234', '12345 1234', '123456789'].forEach(code =>
123+
Assert.zipCode({ country: 'US', state: 'US-GU' }).validate(code)
124+
);
125+
});
126+
127+
it('should accept valid CA state zip codes', () => {
128+
['t0A-1a1', 'T0b5Z0', 'T0C 9M9'].forEach(code => Assert.zipCode({ country: 'CA', state: 'CA-AB' }).validate(code));
129+
});
130+
131+
it('should accept valid US state zip codes', () => {
132+
['94501', '95988-1234', '95559 1234', '900011234'].forEach(code =>
133+
Assert.zipCode({ country: 'US', state: 'US-CA' }).validate(code)
134+
);
135+
});
136+
137+
it('should accept a valid non-CA/US zip code', () => {
138+
Assert.zipCode({ country: 'PT' }).validate('foobar');
139+
});
140+
141+
it('should accept a valid non-CA/US state zip code', () => {
142+
Assert.zipCode({ country: 'PT', state: 'PT-01' }).validate('foobar');
143+
});
144+
});

yarn.lock

+60-1
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,15 @@
874874
dependencies:
875875
"@types/yargs-parser" "*"
876876

877+
"@uphold/countries@https://github.com/uphold/countries#v5.1.0":
878+
version "0.0.0"
879+
resolved "https://github.com/uphold/countries#a76bb6d8154ebfee389d5c03768c5b4576fb93b4"
880+
dependencies:
881+
"@uphold/iso-3166" "https://github.com/uphold/iso-3166#v2.4.2"
882+
"@uphold/iso-4217" "https://github.com/uphold/iso-4217#v1.2.0"
883+
google-libphonenumber "^3.2.35"
884+
lodash "^4.17.21"
885+
877886
"@uphold/github-changelog-generator@^3.0.0":
878887
version "3.0.0"
879888
resolved "https://registry.yarnpkg.com/@uphold/github-changelog-generator/-/github-changelog-generator-3.0.0.tgz#6933ecb696286a8d39a2c8a781d40d42b3f95c9f"
@@ -895,6 +904,28 @@
895904
look-it-up "^2.1.0"
896905
moment "^2.29.1"
897906

907+
"@uphold/iso-3166@https://github.com/uphold/iso-3166#v2.4.2":
908+
version "2.4.2"
909+
resolved "https://github.com/uphold/iso-3166#15d7bd567c3f760dedee751b1031cff770df0adc"
910+
dependencies:
911+
bluebird "^3.3.5"
912+
commander "^2.9.0"
913+
lodash "^4.11.1"
914+
xml2js "^0.4.16"
915+
916+
"@uphold/iso-4217@https://github.com/uphold/iso-4217#v1.2.0":
917+
version "1.2.0"
918+
resolved "https://github.com/uphold/iso-4217#7596207ed6d148d54a69bb6d8a929ec3a0347ce0"
919+
dependencies:
920+
bluebird "^3.3.5"
921+
commander "^2.9.0"
922+
lodash "^4.11.1"
923+
xml2js "^0.4.16"
924+
925+
"@uphold/zip-codes@https://github.com/uphold/zip-codes#v1.0.0":
926+
version "1.0.0"
927+
resolved "https://github.com/uphold/zip-codes#385a989dece299343f32849c3ccc2822f99aab0e"
928+
898929
abab@^2.0.3, abab@^2.0.5:
899930
version "2.0.5"
900931
resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz"
@@ -1171,6 +1202,11 @@ bl@^4.1.0:
11711202
inherits "^2.0.4"
11721203
readable-stream "^3.4.0"
11731204

1205+
bluebird@^3.3.5:
1206+
version "3.7.2"
1207+
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
1208+
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
1209+
11741210
boxen@^7.1.1:
11751211
version "7.1.1"
11761212
resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4"
@@ -1412,6 +1448,11 @@ combined-stream@^1.0.8:
14121448
dependencies:
14131449
delayed-stream "~1.0.0"
14141450

1451+
commander@^2.9.0:
1452+
version "2.20.3"
1453+
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
1454+
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
1455+
14151456
commander@^8.3.0:
14161457
version "8.3.0"
14171458
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
@@ -3370,7 +3411,7 @@ lodash.uniqby@^4.7.0:
33703411
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
33713412
integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==
33723413

3373-
lodash@4.17.21, lodash@^4.17.21, lodash@^4.7.0:
3414+
lodash@4.17.21, lodash@^4.11.1, lodash@^4.17.21, lodash@^4.7.0:
33743415
version "4.17.21"
33753416
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
33763417
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -4243,6 +4284,11 @@ safe-buffer@~5.2.0:
42434284
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
42444285
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
42454286

4287+
sax@>=0.6.0:
4288+
version "1.4.1"
4289+
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
4290+
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
4291+
42464292
saxes@^5.0.1:
42474293
version "5.0.1"
42484294
resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
@@ -5006,6 +5052,19 @@ xml-name-validator@^3.0.0:
50065052
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"
50075053
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
50085054

5055+
xml2js@^0.4.16:
5056+
version "0.4.23"
5057+
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
5058+
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
5059+
dependencies:
5060+
sax ">=0.6.0"
5061+
xmlbuilder "~11.0.0"
5062+
5063+
xmlbuilder@~11.0.0:
5064+
version "11.0.1"
5065+
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
5066+
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
5067+
50095068
xmlchars@^2.2.0:
50105069
version "2.2.0"
50115070
resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"

0 commit comments

Comments
 (0)