Skip to content

Commit

Permalink
PhoneNumber scalar is not fully compatible with E.164 format so updat…
Browse files Browse the repository at this point in the history
…ed regex to support phone number with extension (#2719)

* Update PhoneNumber.ts

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

* Added test coverage
  • Loading branch information
nthombare-mdsol authored Jan 28, 2025
1 parent 5471b05 commit c7f58da
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 56 deletions.
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

0 comments on commit c7f58da

Please sign in to comment.