From 6fdc4b9bd9ee91b172d265d1bb7681963a943e7a Mon Sep 17 00:00:00 2001 From: Sandu Luca Date: Fri, 14 Jul 2023 11:55:44 +0300 Subject: [PATCH] add support for all country codes --- README.md | 30 +++++++++------ package-lock.json | 14 +++---- package.json | 2 +- src/yup-phone-lite.test.ts | 30 +++++---------- src/yup-phone-lite.ts | 75 +++++++++++++++++--------------------- 5 files changed, 70 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 41ccf6b..c4d877a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ This package is a fork of [yup-phone](https://github.com/abhisekp/yup-phone) made by [abhisekp](https://github.com/abhisekp). It replaces [**google-libphonenumber**](https://www.npmjs.com/package/google-libphonenumber) with the much smaller port [**libphonenumber-js**](https://www.npmjs.com/package/libphonenumber-js) with the intention of drastically reducing the bundle size. -One difference between this and the original package is that there is no `strict` option for checking a phone number's country, it will always validate against the country code you pass in (or `US` by default). This is because there is no "lenient" option for `libphonenumber-js` like there is with `google-libphonenumber`. The only other difference is that a few phone numbers will slip through the cracks and give false positives (at least according to the tests written for the original package). If either of those is an issue for you, go ahead and use the original package! - [![yup-phone minzipped size](https://badgen.net/bundlephobia/minzip/yup-phone?label=yup-phone "yup-phone bundlephobia")](https://bundlephobia.com/result?p=yup-phone) [![yup-phone-lite minzipped size](https://badgen.net/bundlephobia/minzip/yup-phone-lite?label=yup-phone-lite "yup-phone-lite bundlephobia")](https://bundlephobia.com/result?p=yup-phone-lite) @@ -39,29 +37,35 @@ Then create a schema like you normally would with `yup` except using the `.phone ```js Yup.string() - .phone("US", "Please enter a valid phone number") + .phone("US", true, "Please enter a valid phone number") .required("A phone number is required"); ``` ### API ```js -.phone(countryCode, errorMessage) +.phone(countryCode, strict, errorMessage) ``` #### `countryCode` -Type: `CountryCode | CountryCode[]` — Default: `"US"` +Type: `CountryCode` -You can pass either a single country code string, or an array of country codes. This field mirrors the [country code argument for libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js#country-code). Here is their definition of a country code: +You can pass a single country code string. This field mirrors the [country code argument for libphonenumber-js](https://github.com/catamphetamine/libphonenumber-js#country-code). Here is their definition of a country code: > A "country code" is a [two-letter ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) (like `US`). > > This library supports all [officially assigned](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements) ISO alpha-2 country codes, plus a few extra ones like: `AC` ([Ascension Island](https://en.wikipedia.org/wiki/Ascension_Island)), `TA` ([Tristan da Cunha](https://en.wikipedia.org/wiki/Tristan_da_Cunha)), `XK` ([Kosovo](https://en.wikipedia.org/wiki/Kosovo)). +#### `strict` + +Type: `boolean` - Default: `false` + +How strictly should it check. Validate phone number strictly in the given region + #### `errorMessage` -Type: `string` — Default: `"${path} must be a valid phone number for region ${countryCode}"` +Type: `string` — Default: `"${path} must be a valid phone number."` This field is the error message returned by `yup` when the validation fails. Here is [the yup documentation explaining it](https://github.com/jquense/yup#mixedtestname-string-message-string--function-test-function-schema): @@ -78,7 +82,7 @@ Here are the yup params you can use in your string: ```js // e.g. -.phone("US", "${path} must be a valid phone number for region US"); +.phone("US", true, "${path} must be a valid phone number for region US"); ``` ## Examples @@ -89,7 +93,7 @@ import * as Yup from "yup"; import "yup-phone-lite"; // require("yup-phone-lite"); -// validate any phone number (defaults to "US" for country) +// validate any phone number const phoneSchema = Yup.string().phone().required(); phoneSchema.isValid("(541) 754-3010").then(console.log); // → true @@ -103,7 +107,7 @@ import * as Yup from "yup"; import "yup-phone-lite"; // require("yup-phone-lite"); -// validate phone number for a country other than the US +// validate phone number for a country const phoneSchema = Yup.string().phone("IN").required(); phoneSchema.isValid("+919876543210").then(console.log); // → true @@ -118,8 +122,10 @@ import "yup-phone-lite"; // require("yup-phone-lite"); // validate phone number in the given region with custom error message -// NOTE: in order to pass a custom error message you must include the country code as the first argument, even if using the default "US" -const phoneSchema = Yup.string().phone("IN", "${path} is invalid").required(); +// NOTE: in order to pass a custom error message you must include the country code as the first argument +const phoneSchema = Yup.string() + .phone("IN", true, "${path} is invalid") + .required(); try { phoneSchema.validateSync("+1-541-754-3010"); diff --git a/package-lock.json b/package-lock.json index 90f3e78..1f238b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.1", "license": "MIT", "dependencies": { - "libphonenumber-js": "^1.10.18" + "libphonenumber-js": "^1.10.37" }, "devDependencies": { "@babel/cli": "^7.20.7", @@ -7667,9 +7667,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.10.18", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz", - "integrity": "sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og==" + "version": "1.10.37", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.37.tgz", + "integrity": "sha512-Z10PCaOCiAxbUxLyR31DNeeNugSVP6iv/m7UrSKS5JHziEMApJtgku4e9Q69pzzSC9LnQiM09sqsGf2ticZnMw==" }, "node_modules/lilconfig": { "version": "2.0.6", @@ -15388,9 +15388,9 @@ } }, "libphonenumber-js": { - "version": "1.10.18", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz", - "integrity": "sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og==" + "version": "1.10.37", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.37.tgz", + "integrity": "sha512-Z10PCaOCiAxbUxLyR31DNeeNugSVP6iv/m7UrSKS5JHziEMApJtgku4e9Q69pzzSC9LnQiM09sqsGf2ticZnMw==" }, "lilconfig": { "version": "2.0.6", diff --git a/package.json b/package.json index 834d644..431b3b6 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ }, "types": "dist/types/yup-phone-lite.d.ts", "dependencies": { - "libphonenumber-js": "^1.10.18" + "libphonenumber-js": "^1.10.37" }, "peerDependencies": { "yup": ">0.32.0" diff --git a/src/yup-phone-lite.test.ts b/src/yup-phone-lite.test.ts index 0547726..32dc956 100644 --- a/src/yup-phone-lite.test.ts +++ b/src/yup-phone-lite.test.ts @@ -3,37 +3,24 @@ import "./yup-phone-lite"; describe("yup-phone-lite validation", () => { it("validate phone number with US (USA) region", () => { - const phoneSchema = Yup.string().phone("US").required(); + const phoneSchema = Yup.string().phone("US", true).required(); expect(phoneSchema.isValidSync("9876543210")).toBe(false); expect(phoneSchema.isValidSync("(541) 754-3010")).toBe(true); // Domestic expect(phoneSchema.isValidSync("(999) 974–2042")).toBe(false); expect(phoneSchema.isValidSync("+1-541-754-3010")).toBe(true); // International expect(phoneSchema.isValidSync("1-541-754-3010")).toBe(true); // Dialed in the US expect(phoneSchema.isValidSync("(212) 345-4567")).toBe(true); - }); - - it("validate phone number with IN (India) region", () => { - const phoneSchema = Yup.string().phone("IN").required(); expect(phoneSchema.isValidSync("+19876543210")).toBe(false); - expect(phoneSchema.isValidSync("+919876543210")).toBe(true); - expect(phoneSchema.isValidSync("9876543210")).toBe(true); - expect(phoneSchema.isValidSync("+9124 4723300")).toBe(false); - expect(phoneSchema.isValidSync("+1 345 9490088")).toBe(true); }); - it("validate phone number with US (USA) or IN (India) region", () => { - const phoneSchema = Yup.string().phone(["US", "IN"]).required(); - expect(phoneSchema.isValidSync("9876543210")).toBe(true); - expect(phoneSchema.isValidSync("(541) 754-3010")).toBe(true); - expect(phoneSchema.isValidSync("(999) 974–2042")).toBe(true); - expect(phoneSchema.isValidSync("+1-541-754-3010")).toBe(true); - expect(phoneSchema.isValidSync("1-541-754-3010")).toBe(true); - expect(phoneSchema.isValidSync("(212) 345-4567")).toBe(true); + it("validate phone number with IN (India) region", () => { + const phoneSchema = Yup.string().phone("IN", true).required(); + expect(phoneSchema.isValidSync("0404 999 999")).toBe(false); expect(phoneSchema.isValidSync("+19876543210")).toBe(false); expect(phoneSchema.isValidSync("+919876543210")).toBe(true); expect(phoneSchema.isValidSync("9876543210")).toBe(true); expect(phoneSchema.isValidSync("+9124 4723300")).toBe(false); - expect(phoneSchema.isValidSync("+1 345 9490088")).toBe(true); + expect(phoneSchema.isValidSync("+1 345 9490088")).toBe(false); }); it("validate phone number with AU (Australia) region", () => { @@ -101,8 +88,11 @@ describe("yup-phone-lite validation", () => { }); it("does not perform required field validation without required chain", () => { - const phoneSchema = Yup.string().phone(); - expect(phoneSchema.isValidSync("")).toBe(true); + const phoneSchema = Yup.string().phone("US", true, "is invalid").required(); + expect(phoneSchema.isValidSync("")).toBe(false); + expect(() => { + phoneSchema.validateSync("+1 345 9490088"); + }).toThrow("is invalid"); const requiredPhoneSchema = Yup.string().phone().required(); expect(requiredPhoneSchema.isValidSync("")).toBe(false); }); diff --git a/src/yup-phone-lite.ts b/src/yup-phone-lite.ts index 213b52a..1e8f896 100644 --- a/src/yup-phone-lite.ts +++ b/src/yup-phone-lite.ts @@ -1,5 +1,8 @@ -import { isValidPhoneNumber, isSupportedCountry } from "libphonenumber-js"; -import * as Yup from "yup"; +import { + parsePhoneNumberWithError, + isValidNumberForRegion, +} from "libphonenumber-js"; +import { addMethod, string } from "yup"; import type { CountryCode } from "libphonenumber-js/types"; declare module "yup" { @@ -8,10 +11,12 @@ declare module "yup" { * Check for phone number validity. * * @param countryCode - The country code to check against (default: `"US"`) + * @param strict - How strictly should it check. * @param errorMessage - The error message to return if validation fails */ phone( - countryCode?: CountryCode | CountryCode[], + countryCode?: CountryCode, + strict?: boolean, errorMessage?: string ): StringSchema; } @@ -19,52 +24,40 @@ declare module "yup" { const YUP_PHONE_METHOD = "phone"; -const isValidCountryCode = (countryCode?: string): boolean => { - if (typeof countryCode !== "string") { - return false; - } - - return isSupportedCountry(countryCode); -}; - -Yup.addMethod( - Yup.string, +addMethod( + string, YUP_PHONE_METHOD, - function yupPhoneLite( - countryCode: CountryCode | CountryCode[] = "US", - errorMessage?: string + function yupPhone( + countryCode?: CountryCode, + strict = false, + errorMessage = "" ) { - const countryCodes: CountryCode[] = - typeof countryCode === "string" ? [countryCode] : [...countryCode]; - - let validCountryCodes = countryCodes.filter(isValidCountryCode); - - if (!validCountryCodes.length) { - validCountryCodes = ["US"]; + let errMsg = typeof errorMessage === "string" && errorMessage; + if (!errMsg) { + if (countryCode) { + errMsg = `\${path} must be a valid phone number for region ${countryCode}`; + } else { + // eslint-disable-next-line no-template-curly-in-string + errMsg = "${path} must be a valid phone number."; + } } - const errMsg = - typeof errorMessage === "string" && errorMessage - ? errorMessage - : `\${path} must be a valid phone number for region${ - validCountryCodes.length > 1 ? "s" : "" - } ${validCountryCodes.join(", ")}`; - - return this.test(YUP_PHONE_METHOD, errMsg, (value?: string) => { + return this.test(YUP_PHONE_METHOD, errMsg, (value = "") => { try { - if (value === undefined || value === "") { - return true; + const phoneNumber = parsePhoneNumberWithError(value, countryCode); + + if (!phoneNumber.isPossible()) { + return false; } - const isValid = validCountryCodes.reduce( - (isValidAccum, validCountryCode) => { - const isValidPhone = isValidPhoneNumber(value, validCountryCode); + /* check if the countryCode provided should be used as + default country code or strictly followed + */ + if (strict && countryCode) { + return isValidNumberForRegion(value, countryCode); + } - return isValidAccum || isValidPhone; - }, - false - ); - return isValid; + return phoneNumber.isValid(); } catch { return false; }