diff --git a/README.md b/README.md index d75f15b27..a079026fb 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Validator | Description **isSlug(str)** | check if the string is of type slug. **isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` **isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].

`options` is an object which can contain the keys `hourFormat` or `mode`.

`hourFormat` is a key and defaults to `'hour24'`.

`mode` is a key and defaults to `'default'`.

`hourFomat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.

`mode` can contain the values `'default'` or `'withSeconds'`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format. -**isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`. +**isTaxID(str, locale [, options])** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

`options` is an object that has no default.

`options` contains `localeOption` as key and can be used if the locale has multiple Tax Identification Number `(e.g.{localeOption:'GSTIN'})`.

`localeOption` is of type `string`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`.

Supported localeOption: `['GSTIN','PAN']` for `en-IN` locale. **isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`require_protocol` - if set to true isURL will return false if protocol is not present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`protocols` - valid protocols can be modified with this option.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`validate_length` - if set to false isURL will skip string length validation (2083 characters is IE max URL length). **isUUID(str [, version])** | check if the string is a UUID (version 1, 2, 3, 4 or 5). **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js index 933783f44..8c4c6e617 100644 --- a/src/lib/isTaxID.js +++ b/src/lib/isTaxID.js @@ -1136,6 +1136,10 @@ const taxIdFormat = { 'en-CA': /^\d{9}$/, 'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i, 'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i, + 'en-IN': { + GSTIN: /^((?!39)(?!00)[0-3][0-9]|97)([A-Z]{3}[ABCFGHLJPT][A-Z](?!0000)[0-9]{4}[A-Z])[1-9A-Z]Z[0-9 A-Z]$/, + PAN: /^[A-Z]{3}[ABCFGHLJPT][A-Z](?!0000)[0-9]{4}[A-Z]$/, + }, 'en-US': /^\d{2}[- ]{0,1}\d{7}$/, 'es-ES': /^(\d{0,8}|[XYZKLM]\d{7})[A-HJ-NP-TV-Z]$/i, 'et-EE': /^[1-6]\d{6}(00[1-9]|0[1-9][0-9]|[1-6][0-9]{2}|70[0-9]|710)\d$/, @@ -1211,30 +1215,62 @@ const sanitizeRegexes = { // sanitizeRegexes locale aliases sanitizeRegexes['nl-BE'] = sanitizeRegexes['fr-BE']; +const multipleTaxIdLocale = { + 'en-IN': { + GSTIN: true, + PAN: true, + }, +}; + +const availableOptions = { + localeOption: true, +}; + +// Validates args of isTaxID function +function validateArgs(str, locale, options) { + const localeOption = options?.localeOption; + + try { assertString(str); } catch (err) { throw new Error(`${err.message} for str`); } + try { assertString(locale); } catch (err) { throw new Error(`${err.message} for locale`); } + + for (const option in options) { + if (!(option in availableOptions)) throw new Error(`'${option}' is not available`); + } + + if (!(locale in taxIdFormat)) throw new Error(`Invalid locale '${locale}'`); + + + if (locale in multipleTaxIdLocale) { + try { assertString(localeOption); } catch (err) { throw new Error(`${err.message} for localeOption`); } + if (!(localeOption in multipleTaxIdLocale[locale])) throw new Error(`Invalid localeOption '${localeOption}'`); + } else if (localeOption || localeOption === '') { + throw new Error(`Invalid localeOption for locale '${locale}'`); + } +} + /* * Validator function * Return true if the passed string is a valid tax identification number * for the specified locale. * Throw an error exception if the locale is not supported. */ -export default function isTaxID(str, locale = 'en-US') { - assertString(str); +export default function isTaxID(str, locale = 'en-US', options) { + validateArgs(str, locale, options); + const localeOption = options?.localeOption; + // Copy TIN to avoid replacement if sanitized let strcopy = str.slice(0); + if (locale in sanitizeRegexes) { + strcopy = strcopy.replace(sanitizeRegexes[locale], ''); + } - if (locale in taxIdFormat) { - if (locale in sanitizeRegexes) { - strcopy = strcopy.replace(sanitizeRegexes[locale], ''); - } - if (!taxIdFormat[locale].test(strcopy)) { - return false; - } - - if (locale in taxIdCheck) { - return taxIdCheck[locale](strcopy); - } - // Fallthrough; not all locales have algorithmic checks + if (locale in multipleTaxIdLocale) { + if (!taxIdFormat[locale][localeOption].test(strcopy)) return false; return true; } - throw new Error(`Invalid locale '${locale}'`); + if (!taxIdFormat[locale].test(strcopy)) return false; + if (locale in taxIdCheck) return taxIdCheck[locale](strcopy); + + return true; } + diff --git a/test/validators.test.js b/test/validators.test.js index 239f172ca..908cd503a 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -12237,6 +12237,40 @@ describe('Validators', () => { '1234577A', '1234577JA'], }); + test({ + validator: 'isTaxID', + args: ['en-IN', { localeOption: 'GSTIN' }], + valid: [ + '27AAPFU0939F1ZV', + '27AASCS2460H1Z0', + '29AAGCB7383J1Z4'], + invalid: [ + '', + '00AAGCB0000J1Z4', + '29AAGCB7383J114', + '88AAPFU093921ZV', + '**AAGCB7689J1Z4', + 'S AAGCB7893J1Z4'], + }); + test({ + validator: 'isTaxID', + args: ['en-IN', { localeOption: 'PAN' }], + valid: [ + 'MFWAA0001A', + 'ABPPA2020K', + 'KLSPK0909Z', + 'ACSLY4499S'], + invalid: [ + '123PA0001A', + 'ACSZA2020K', + 'ACSP19090K', + 'ACSPA0000K', + 'ACSYA44999', + '', + 'ACS A4499A', + '*#$PA4499J', + 'ACSLY4499SK'], + }); test({ validator: 'isTaxID', args: ['en-US'], @@ -12573,6 +12607,42 @@ describe('Validators', () => { valid: [ '01-1234567'], }); + test({ + validator: 'isTaxID', + args: [123, { localeOption: 'GSTIN' }], + error: [ + '27AAPFU0939F1ZV'], + }); + test({ + validator: 'isTaxID', + args: ['en-IN', { localeOption: 123 }], + error: [ + '27AAPFU0939F1ZV'], + }); + test({ + validator: 'isTaxID', + args: ['en-IN', { localeOption: 'is-NOT' }], + error: [ + '27AAPFU0939F1ZV'], + }); + test({ + validator: 'isTaxID', + args: ['en-IN', { isNOT: 'is-NOT' }], + error: [ + '27AAPFU0939F1ZV'], + }); + test({ + validator: 'isTaxID', + args: ['en-US', { localeOption: 'GSTIN' }], + error: [ + '27AAPFU0939F1ZV'], + }); + test({ + validator: 'isTaxID', + args: ['en-IN', { localeOption: 'GSTIN' }], + error: [ + 12345566], + }); test({ validator: 'isTaxID', args: ['is-NOT'],