Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for all country codes #17

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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):

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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");
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 10 additions & 20 deletions src/yup-phone-lite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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);
});
Expand Down
75 changes: 34 additions & 41 deletions src/yup-phone-lite.ts
Original file line number Diff line number Diff line change
@@ -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" {
Expand All @@ -8,63 +11,53 @@ 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;
}
}

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;
}
Expand Down