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

Updated regex for E.164 compliant to support phone number with extension. #1

Merged
merged 2 commits into from
Jan 28, 2025
Merged
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
12 changes: 8 additions & 4 deletions src/scalars/PhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { GraphQLScalarType, Kind } from 'graphql';
import { createGraphQLError } from '../error.js';

const PHONE_NUMBER_REGEX = /^\+[1-9]\d{6,14}$/;
// The regex supports all phone numbers compliant with the E.164 international format standard,
// which includes country codes (Optional), area codes, and local numbers and extension (optional). For more information on E.164 formatting,
// Regex: https://regex101.com/r/nol2F6/1
// Ex. +62 (21) 9175 5194, 2191755194, +1 123-456-7890 12345, +1 (123) 456-7890
const PHONE_NUMBER_REGEX = /^\+?\d{1,3}(\-|\x20)?\(?\d+\)?((\-|\x20)?\d+)+$/;

export const GraphQLPhoneNumber = /*#__PURE__*/ new GraphQLScalarType({
name: 'PhoneNumber',
Expand All @@ -16,7 +20,7 @@ export const GraphQLPhoneNumber = /*#__PURE__*/ new GraphQLScalarType({

if (!PHONE_NUMBER_REGEX.test(value)) {
throw createGraphQLError(
`Value is not a valid phone number of the form +17895551234 (7-15 digits): ${value}`,
`Invalid phone number: ${value}. Please ensure it's in a valid format. The country code is optional, and Spaces and dashes are allowed. Examples: +1 (123) 456-7890, +44 (20) 2121 2222, or 123 456-7890.`,
);
}

Expand All @@ -30,7 +34,7 @@ export const GraphQLPhoneNumber = /*#__PURE__*/ new GraphQLScalarType({

if (!PHONE_NUMBER_REGEX.test(value)) {
throw createGraphQLError(
`Value is not a valid phone number of the form +17895551234 (7-15 digits): ${value}`,
`Invalid phone number: ${value}. Please ensure it's in a valid format. The country code is optional, and Spaces and dashes are allowed. Examples: +1 (123) 456-7890, +44 (20) 2121 2222, or 123 456-7890.`,
);
}

Expand All @@ -47,7 +51,7 @@ export const GraphQLPhoneNumber = /*#__PURE__*/ new GraphQLScalarType({

if (!PHONE_NUMBER_REGEX.test(ast.value)) {
throw createGraphQLError(
`Value is not a valid phone number of the form +17895551234 (7-15 digits): ${ast.value}`,
`Invalid phone number: ${ast.value}. Please ensure it's in a valid format. The country code is optional, and Spaces and dashes are allowed. Examples: +1 (123) 456-7890, +44 (20) 2121 2222, or 123 456-7890.`,
{ nodes: ast },
);
}
Expand Down
121 changes: 69 additions & 52 deletions tests/PhoneNumber.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,80 @@
import { Kind } from 'graphql/language';
import { GraphQLPhoneNumber } from '../src/scalars/PhoneNumber.js';

function PhoneNumberError(value: string) {
return `Invalid phone number: ${value}. Please ensure it's in a valid format. The country code is optional, and Spaces and dashes are allowed. Examples: +1 (123) 456-7890, +44 (20) 2121 2222, or 123 456-7890.`
}

describe('PhoneNumber', () => {
describe('valid', () => {
test('serialize', () => {
expect(GraphQLPhoneNumber.serialize('+16075551234')).toBe('+16075551234');
describe('valid formats', () => {
describe('with country code', () => {
test('serialize', () => {
expect(GraphQLPhoneNumber.serialize('+16075551234')).toBe('+16075551234');
});

test('parseValue', () => {
expect(GraphQLPhoneNumber.parseValue('+16075551234')).toBe('+16075551234');
});

test('parseLiteral', () => {
expect(
GraphQLPhoneNumber.parseLiteral({ value: '+16075551234', kind: Kind.STRING }, {}),
).toBe('+16075551234');
});
});
describe('without country code', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('7895551234')).not.toThrow()
});

test('parseValue', () => {
expect(GraphQLPhoneNumber.parseValue('+16075551234')).toBe('+16075551234');
test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('123 456-7890')).not.toThrow()
});

test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral({ value: '789 555 1234', kind: Kind.STRING }, {}),
).not.toThrow();
});
});
describe('different formatting', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('62-(21)-9175-5194')).not.toThrow()
});

test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('+622191755194')).not.toThrow()
});

test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('+62 (21) 9175 5194')).not.toThrow()
});

test('parseLiteral', () => {
expect(
GraphQLPhoneNumber.parseLiteral({ value: '+16075551234', kind: Kind.STRING }, {}),
).toBe('+16075551234');
test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral({ value: '+1 (123) 456-7890', kind: Kind.STRING }, {}),
).not.toThrow();
});
});
});

describe('invalid', () => {
describe('not a phone number', () => {
describe('invalid case', () => {
describe('contains Non-Numeric Characters', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('this is not a phone number')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.serialize('98aaa333')).toThrow(PhoneNumberError('98aaa333'));
});

test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('this is not a phone number')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.parseValue('98aaa333ppp')).toThrow(PhoneNumberError('98aaa333ppp'));
});

test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral(
{ value: 'this is not a phone number', kind: Kind.STRING },
{ value: '98aa', kind: Kind.STRING },
{},
),
).toThrow(/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/);
).toThrow(PhoneNumberError('98aa'));
});
});

Expand All @@ -60,65 +96,46 @@ describe('PhoneNumber', () => {
});
});

describe('too long', () => {
describe('wrong formate', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('+1789555123456789')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
});

test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('+1789555123456789')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.serialize('+17 89- 5')).toThrow(PhoneNumberError('+17 89- 5'));
});

test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral({ value: '+1789555123456789', kind: Kind.STRING }, {}),
).toThrow(/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/);
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('+1 ( 123 ) 456-7890')).toThrow(PhoneNumberError('+1 ( 123 ) 456-7890'));
});
});

describe('too small', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('+123')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.serialize('+1[123]456 7890')).toThrow(PhoneNumberError('+1[123]456 7890'));
});

test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('+123')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.parseValue('+(178)95 55 5678')).toThrow(PhoneNumberError('+(178)95 55 5678'));
});

test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral({ value: '+123', kind: Kind.STRING }, {}),
).toThrow(/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/);
GraphQLPhoneNumber.parseLiteral({ value: '1789 [555] 1234', kind: Kind.STRING }, {}),
).toThrow(PhoneNumberError('1789 [555] 1234'));
});
});

describe('no plus sign', () => {
describe('too small', () => {
test('serialize', () => {
expect(() => GraphQLPhoneNumber.serialize('17895551234')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.serialize('+12')).toThrow(PhoneNumberError('+12'));
});

test('parseValue', () => {
expect(() => GraphQLPhoneNumber.parseValue('17895551234')).toThrow(
/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/,
);
expect(() => GraphQLPhoneNumber.parseValue('+12')).toThrow(PhoneNumberError('+12'));
});

test('parseLiteral', () => {
expect(() =>
GraphQLPhoneNumber.parseLiteral({ value: '17895551234', kind: Kind.STRING }, {}),
).toThrow(/^Value is not a valid phone number of the form \+17895551234 \(7-15 digits\)/);
GraphQLPhoneNumber.parseLiteral({ value: '+12', kind: Kind.STRING }, {}),
).toThrow(PhoneNumberError('+12'));
});
});

describe('support more countries', () => {
test('support Singapore numbers - 10 digits', () => {
expect(() => {
Expand Down