Proposal: return an `isValid` flag in the response to simplify calling code
See original GitHub issueFirstly I’d like to say a huge thanks for this library. We’ve been using it for years in production to parse user-supplied mobile phone numbers into E.164 format, and it has worked beautifully well. ❤️
I noticed that the API response has changed for v3, which is great - it’s much nicer to work with an object as the response, rather than a tuple. That being said, I was hoping to propose a further improvement, based on an assumption that I’m making (which of course could be wrong).
Right now the response object takes the following format:
{
phoneNumber: string | null;
countryIso2: string | null;
countryIso3: string | null;
countryCode: string | null;
}
If I want to ensure that I always return a valid phone number and country ISO code, I have to do the following:
import phone from 'phone';
interface ParsedPhoneNumber {
phoneNumber: string;
countryIso2: string;
countryIso3: string;
}
function parsePhoneNumber(phoneNumberInput: string): ParsedPhoneNumber | null {
const { phoneNumber, countryIso2, countryIso3 } = phone(phoneNumberInput);
// Explicitly check every field for null, so that TypeScript can infer that each is of type `string` below
if(phoneNumber === null || countryIso2 === null || countryIso3 === null) {
return null;
}
// At this point, TS has inferred that all three fields are of type `string` - i.e. the `null` types have been erased.
// We can therefore return them all, satisfying the `ParsedPhoneNumber` return type.
return {
phoneNumber,
countryIso2,
countryIso3,
};
}
However, if the following assumption is true, this can be simplified via the use of a discriminator field. The assumption is that either:
- All of these properties will be strings, or
- All of these properties will be null
Is that correct? If so, we can use TypeScript’s discriminated union support to simplify the above. Consider the following types:
interface PhoneNumberInvalidResponse {
isValid: false;
}
interface PhoneNumberValidResponse {
isValid: true;
phoneNumber: string;
countryIso2: string;
countryIso3: string;
}
type PhoneNumberResponse = PhoneNumberInvalidResponse | PhoneNumberValidResponse;
Now, if the phone
library could return the type PhoneNumberResponse
, i.e. either the valid or the invalid response, we could do the following in code:
import phone from 'phone';
interface ParsedPhoneNumber {
phoneNumber: string;
countryIso2: string;
countryIso3: string;
}
function parsePhoneNumber(phoneNumberInput: string): ParsedPhoneNumber | null {
const parsed = phone(phoneNumberInput);
// We only need to check the `isValid` field, since this is a discriminator.
if(!parsed.isValid) {
return null;
}
// At this point, TS knows that `parsed.isValid` must be `true`.
// It can therefore infer that the type of `parsed` must be `PhoneNumberValidResponse`,
// and so pulling out these fields will guarantee they are of type `string`.
// This means we don't need to explicitly check that each field is not `null`.
const { phoneNumber, countryIso2, countryIso3 } = parsed;
return {
phoneNumber,
countryIso2,
countryIso3,
};
}
Of course, this all depends on the assumption that if a phone number is valid, it also has the associated country codes returned with it. If this is the case, then using a discriminated union could arguably improve the data model, since it is currently acceptible (according to the return type) for countryIso2
to be a string and countryIso3
to be null
.
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
I think it indeed raise some usability issue, I’ll discuss with the team soon to inspect and improve the interface
FWIW I think the original proposal is sound, just that it would need to be released as a v4 given that it is a breaking change. But I can definitely see the benefits of always returning a
phoneNumber
field whether theisValid
flag is set or not.Thanks again for implementing this so quickly!