Skip to content

Commit

Permalink
Add zip-code-assert
Browse files Browse the repository at this point in the history
  • Loading branch information
Megamind51 committed Jan 17, 2025
1 parent c9b90d9 commit bcef960
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 2 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The following set of extra asserts are provided by this package:
- [UsSubdivision](#ussubdivision)
- [UsZipCode](#uszipcode)
- [Uuid](#uuid)
- [ZipCode](#zipcode) (requires `@uphold/countries` and `@uphold/zip-codes`)

### AbaRoutingNumber
Tests if the value is a valid [ABA Routing Number](http://www.accuity.com/PageFiles/255/ROUTING_NUMBER_POLICY.pdf).
Expand Down Expand Up @@ -259,6 +260,14 @@ Tests if the value is a valid uuid.
#### Arguments
- `version` (optional) - the version to test the uuid for. Supported version are `3`, `4` and `5`. Defaults to test for `all` three if omitted.

### ZipCode
Tests if the value is a valid zip code.
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)).

#### Arguments
- `country` (required) - the country which zip code is from.
- `state` (optional) - the state which zip code is from.

## Usage
The following is an example for the extra ip assert:

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
"validator.js": "^2.0.0"
},
"devDependencies": {
"@uphold/countries": "https://github.com/uphold/countries#v5.1.0",
"@uphold/github-changelog-generator": "^3.4.0",
"@uphold/zip-codes": "https://github.com/uphold/zip-codes#v1.0.0",
"release-it": "^17.0.1",
"abavalidator": "^2.0.1",
"bignumber.js": "^9.0.0",
Expand Down
80 changes: 80 additions & 0 deletions src/asserts/zip-code-assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

/**
* Module dependencies.
*/

const { Assert, Validator, Violation } = require('validator.js');
const { CaZipCode, UsZipCode } = require('../index.js');
const {
common: { getCountriesAlpha2 }
} = require('@uphold/countries');
const _ = require('lodash');
const zipCodes = require('@uphold/zip-codes');

/**
* Constants.
*/

const countriesAlpha2 = getCountriesAlpha2();

/**
* Instances.
*/

const is = Assert.extend({ CaZipCode, UsZipCode });

/**
* Export `ZipCodeAssert`.
*/

module.exports = function zipCodeAssert(data = {}) {
/**
* Class name.
*/

this.__class__ = 'ZipCode';
this.country = data && data.country;
this.state = data && data.state;

/**
* Validation algorithm.
*/

this.validate = function (value) {
if (value !== null && !_.isString(value)) {
throw new Violation(this, value, { value: 'must_be_null_or_a_string' });
}

if (!_.isString(this.country)) {
throw new Violation(this, value, { country: Validator.errorCode.must_be_a_string });
}

if (!countriesAlpha2.includes(this.country)) {
throw new Violation(this, value, { reason: 'invalid-country' });
}

if (this.country === 'US' && is.usZipCode().check(value) !== true) {
throw new Violation(this, value, { reason: 'invalid-zip-code' });
}

if (this.country === 'CA' && is.caZipCode().check(value) !== true) {
throw new Violation(this, value, { reason: 'invalid-zip-code' });
}

const state = this.state && this.state.split('-')[1];

if (
this.country &&
state &&
_.has(zipCodes, `${this.country}.${state}`) &&
!(zipCodes[this.country][state] || []).find(zip => value.toUpperCase().startsWith(zip.toUpperCase()))
) {
throw new Violation(this, value, { reason: 'invalid-state-zip-code' });
}

return true;
};

return this;
};
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const Uri = require('./asserts/uri-assert.js');
const UsSubdivision = require('./asserts/us-subdivision-assert.js');
const UsZipCode = require('./asserts/us-zip-code-assert.js');
const Uuid = require('./asserts/uuid-assert.js');
const ZipCode = require('./asserts/zip-code-assert.js');

/**
* Export asserts.
Expand Down Expand Up @@ -91,5 +92,6 @@ module.exports = {
Uri,
UsSubdivision,
UsZipCode,
Uuid
Uuid,
ZipCode
};
144 changes: 144 additions & 0 deletions test/asserts/zip-code-assert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
'use strict';

/**
* Module dependencies.
*/

const { Assert: BaseAssert, Validator, Violation } = require('validator.js');
const zipCodeAssert = require('../../src/asserts/zip-code-assert');

/**
* Extend `Assert` with `UuidAssert`.
*/

const Assert = BaseAssert.extend({
ZipCode: zipCodeAssert
});

/**
* Test `ZipCodeAssert`.
*/

describe('ZipCodeAssert', () => {
it('should throw an error if the input value is invalid', () => {
const types = [[], {}, 0];

types.forEach(type => {
try {
Assert.zipCode().validate(type);

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.violation.value).toBe('must_be_null_or_a_string');
}
});
});

it('should throw an error if the country is invalid', () => {
try {
Assert.zipCode({ country: null }).validate('12345');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.country).toBe(Validator.errorCode.must_be_a_string);
}
});

it('should throw an error if the country is unknown', () => {
try {
Assert.zipCode({ country: 'ZZ' }).validate('12345');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.reason).toBe('invalid-country');
}
});

it('should throw an error if the zip code is not in a valid CA zip code format and user is from CA', () => {
try {
Assert.zipCode({ country: 'CA' }).validate('1234');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.reason).toBe('invalid-zip-code');
}
});

it('should throw an error if the zip code is not in a valid US zip code format and user if from US', () => {
try {
Assert.zipCode({ country: 'US' }).validate('1234');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.reason).toBe('invalid-zip-code');
}
});

it('should throw an error if the zip code is not valid for the given CA state', () => {
try {
Assert.zipCode({ country: 'CA', state: 'CA-AB' }).validate('A1A1A2');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.reason).toBe('invalid-state-zip-code');
}
});

it('should throw an error if the zip code is not valid for the given US state', () => {
try {
Assert.zipCode({ country: 'US', state: 'US-CA' }).validate('12345');

fail();
} catch (e) {
expect(e).toBeInstanceOf(Violation);
expect(e.show().assert).toBe('ZipCode');
expect(e.show().violation.reason).toBe('invalid-state-zip-code');
}
});

it('should accept valid CA zip codes', () => {
['A1A1A1', 'C5D-1B3', 'G0A 5B6'].forEach(code => Assert.zipCode({ country: 'CA' }).validate(code));
});

it('should accept valid US zip codes', () => {
['12345', '12345-1234', '12345 1234', '123456789'].forEach(code =>
Assert.zipCode({ country: 'US' }).validate(code)
);
});

it('should accept valid US territory zip codes', () => {
['12345', '12345-1234', '12345 1234', '123456789'].forEach(code =>
Assert.zipCode({ country: 'US', state: 'US-GU' }).validate(code)
);
});

it('should accept valid CA state zip codes', () => {
['t0A-1a1', 'T0b5Z0', 'T0C 9M9'].forEach(code => Assert.zipCode({ country: 'CA', state: 'CA-AB' }).validate(code));
});

it('should accept valid US state zip codes', () => {
['94501', '95988-1234', '95559 1234', '900011234'].forEach(code =>
Assert.zipCode({ country: 'US', state: 'US-CA' }).validate(code)
);
});

it('should accept a valid non-CA/US zip code', () => {
Assert.zipCode({ country: 'PT' }).validate('foobar');
});

it('should accept a valid non-CA/US state zip code', () => {
Assert.zipCode({ country: 'PT', state: 'PT-01' }).validate('foobar');
});
});
61 changes: 60 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,15 @@
dependencies:
"@types/yargs-parser" "*"

"@uphold/countries@https://github.com/uphold/countries#v5.1.0":
version "0.0.0"
resolved "https://github.com/uphold/countries#a76bb6d8154ebfee389d5c03768c5b4576fb93b4"
dependencies:
"@uphold/iso-3166" "https://github.com/uphold/iso-3166#v2.4.2"
"@uphold/iso-4217" "https://github.com/uphold/iso-4217#v1.2.0"
google-libphonenumber "^3.2.35"
lodash "^4.17.21"

"@uphold/github-changelog-generator@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@uphold/github-changelog-generator/-/github-changelog-generator-3.0.0.tgz#6933ecb696286a8d39a2c8a781d40d42b3f95c9f"
Expand All @@ -895,6 +904,28 @@
look-it-up "^2.1.0"
moment "^2.29.1"

"@uphold/iso-3166@https://github.com/uphold/iso-3166#v2.4.2":
version "2.4.2"
resolved "https://github.com/uphold/iso-3166#15d7bd567c3f760dedee751b1031cff770df0adc"
dependencies:
bluebird "^3.3.5"
commander "^2.9.0"
lodash "^4.11.1"
xml2js "^0.4.16"

"@uphold/iso-4217@https://github.com/uphold/iso-4217#v1.2.0":
version "1.2.0"
resolved "https://github.com/uphold/iso-4217#7596207ed6d148d54a69bb6d8a929ec3a0347ce0"
dependencies:
bluebird "^3.3.5"
commander "^2.9.0"
lodash "^4.11.1"
xml2js "^0.4.16"

"@uphold/zip-codes@https://github.com/uphold/zip-codes#v1.0.0":
version "1.0.0"
resolved "https://github.com/uphold/zip-codes#385a989dece299343f32849c3ccc2822f99aab0e"

abab@^2.0.3, abab@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz"
Expand Down Expand Up @@ -1171,6 +1202,11 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"

bluebird@^3.3.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==

boxen@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4"
Expand Down Expand Up @@ -1412,6 +1448,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"

commander@^2.9.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==

commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
Expand Down Expand Up @@ -3370,7 +3411,7 @@ lodash.uniqby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==

[email protected], lodash@^4.17.21, lodash@^4.7.0:
[email protected], lodash@^4.11.1, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
Expand Down Expand Up @@ -4243,6 +4284,11 @@ safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==

sax@>=0.6.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==

saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
Expand Down Expand Up @@ -5006,6 +5052,19 @@ xml-name-validator@^3.0.0:
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==

xml2js@^0.4.16:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"

xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==

xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"
Expand Down

0 comments on commit bcef960

Please sign in to comment.