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

feat(ExPhoneNumber.Validation): Add is_possible_number_for_type?/2 an… #50

Merged
merged 2 commits into from
Feb 20, 2022
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
14 changes: 14 additions & 0 deletions lib/ex_phone_number/constants/phone_number_types.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
defmodule ExPhoneNumber.Constants.PhoneNumberTypes do
@type t() ::
:fixed_line
| :mobile
| :fixed_line_or_mobile
| :toll_free
| :premium_rate
| :shared_cost
| :voip
| :personal_number
| :pager
| :uan
| :voicemail
| :unknown

def fixed_line(), do: :fixed_line

def mobile(), do: :mobile
Expand Down
2 changes: 2 additions & 0 deletions lib/ex_phone_number/constants/validation_results.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule ExPhoneNumber.Constants.ValidationResults do
@type t() :: :is_possible | :is_possible_local_only | :invalid_country_code | :too_short | :invalid_length | :too_long

def is_possible(), do: :is_possible

def is_possible_local_only(), do: :is_possible_local_only
Expand Down
9 changes: 9 additions & 0 deletions lib/ex_phone_number/model/phone_number.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ defmodule ExPhoneNumber.Model.PhoneNumber do
not is_nil(phone_number.raw_input)
end

@country_code_default 1
def get_country_code_or_default(phone_number = %PhoneNumber{}) do
if is_nil(phone_number.country_code) do
@country_code_default
else
phone_number.country_code
end
end

@country_code_source_default CountryCodeSource.from_number_with_plus_sign()
def get_country_code_source_or_default(phone_number = %PhoneNumber{}) do
if is_nil(phone_number.country_code_source) do
Expand Down
54 changes: 54 additions & 0 deletions lib/ex_phone_number/validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,60 @@ defmodule ExPhoneNumber.Validation do
end
end

@doc """
Convenience wrapper around `Validation.is_possible_number_for_type_with_reason?/2`
Instead of returning the reason for failure, this method returns true if the
number is either a possible fully-qualified number (containing the area code
and country code), or if the number could be a possible local number (with a
country code, but missing an area code). Local numbers are considered
possible if they could be possibly dialled in this format: if the area code
is needed for a call to connect, the number is not considered possible
without it.

Implements `i18n.phonenumbers.PhoneNumberUtil.prototype.isPossibleNumberForType`
"""
@spec is_possible_number_for_type?(%PhoneNumber{}, PhoneNumberTypes.t()) :: ValidationResults.t()
def is_possible_number_for_type?(%PhoneNumber{} = number, type) when is_atom(type) do
result = is_possible_number_for_type_with_reason?(number, type)

result == ValidationResults.is_possible() ||
result == ValidationResults.is_possible_local_only()
end

@doc """
Check whether a phone number is a possible number. It provides a more lenient
check than `Validation.is_valid_number/1` in the following sense:

It only checks the length of phone numbers. In particular, it doesn't
check starting digits of the number.

For some numbers (particularly fixed-line), many regions have the concept
of area code, which together with subscriber number constitute the national
significant number. It is sometimes okay to dial only the subscriber number
when dialing in the same area. This function will return
:is_possible_local_only if the subscriber-number-only version is passed in. On
the other hand, because is_valid_number validates using information on both
starting digits (for fixed line numbers, that would most likely be area
codes) and length (obviously includes the length of area codes for fixed line
numbers), it will return false for the subscriber-number-only version.

Implements `i18n.phonenumbers.PhoneNumberUtil.prototype.isPossibleNumberForTypeWithReason`
"""
@spec is_possible_number_for_type_with_reason?(%PhoneNumber{}, PhoneNumberTypes.t()) :: ValidationResults.t()
def is_possible_number_for_type_with_reason?(%PhoneNumber{} = number, type) when is_atom(type) do
national_number = PhoneNumber.get_national_significant_number(number)
country_code = PhoneNumber.get_country_code_or_default(number)

if not Metadata.is_valid_country_code?(country_code) do
ValidationResults.invalid_country_code()
else
region_code = Metadata.get_region_code_for_country_code(country_code)
metadata = Metadata.get_for_region_code_or_calling_code(country_code, region_code)

test_number_length_for_type(national_number, metadata, type)
end
end

def is_valid_possible_number_length?(metadata, number) do
!Enum.member?(
[
Expand Down
163 changes: 163 additions & 0 deletions test/ex_phone_number/validation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,167 @@ defmodule ExPhoneNumber.ValidationTest do
assert 4 == get_length_of_national_destination_code(PhoneNumberFixture.international_toll_free())
end
end

describe ".is_possible_number_for_type?/2" do
test "IsPossibleNumberForType_DifferentTypeLengths" do
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number7(), PhoneNumberTypes.fixed_line())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number7(), PhoneNumberTypes.unknown())

assert is_possible_number_for_type?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.fixed_line())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.unknown())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.mobile())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.toll_free())

assert is_possible_number_for_type?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.fixed_line())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.unknown())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.mobile())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.toll_free())

assert is_possible_number_for_type?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.fixed_line())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.unknown())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.mobile())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.toll_free())

assert is_possible_number_for_type?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.unknown())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.fixed_line())
assert is_possible_number_for_type?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.mobile())
refute is_possible_number_for_type?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.toll_free())
end

test "IsPossibleNumberForType_LocalOnly" do
assert is_possible_number_for_type?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.unknown())
assert is_possible_number_for_type?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.fixed_line())
refute is_possible_number_for_type?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.mobile())
end

test "IsPossibleNumberForType_DataMissingForSizeReasons" do
assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.unknown())
assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.fixed_line())

assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only2(), PhoneNumberTypes.unknown())
assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only2(), PhoneNumberTypes.fixed_line())
end

test "IsPossibleNumberForType_NumberTypeNotSupportedForRegion" do
refute is_possible_number_for_type?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.mobile())
assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.fixed_line())
assert is_possible_number_for_type?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.fixed_line_or_mobile())

refute is_possible_number_for_type?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.mobile())
refute is_possible_number_for_type?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.fixed_line())
refute is_possible_number_for_type?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.fixed_line_or_mobile())
assert is_possible_number_for_type?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.premium_rate())
end
end

describe ".is_possible_number_for_type_with_reason?/2" do
test "IsPossibleNumberForTypeWithReason_DifferentTypeLengths" do
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number7(), PhoneNumberTypes.unknown())
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number7(), PhoneNumberTypes.fixed_line())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.unknown())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.fixed_line())
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.mobile())
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number8(), PhoneNumberTypes.toll_free())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.unknown())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.fixed_line())
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.mobile())
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number9(), PhoneNumberTypes.toll_free())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.unknown())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.fixed_line())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.mobile())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number10(), PhoneNumberTypes.toll_free())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.unknown())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.fixed_line())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.mobile())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.ar_number11(), PhoneNumberTypes.toll_free())
end

test "IsPossibleNumberForTypeWithReason_LocalOnly" do
assert ValidationResults.is_possible_local_only() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.unknown())

assert ValidationResults.is_possible_local_only() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.fixed_line())

assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.de_local_only(), PhoneNumberTypes.mobile())
end

test "IsPossibleNumberForTypeWithReason_DataMissingForSizeReasons" do
assert ValidationResults.is_possible_local_only() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.unknown())

assert ValidationResults.is_possible_local_only() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.fixed_line())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only2(), PhoneNumberTypes.unknown())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only2(), PhoneNumberTypes.fixed_line())
end

test "IsPossibleNumberForTypeWithReason_NumberTypeNotSupportedForRegion" do
assert ValidationResults.invalid_length() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.mobile())

assert ValidationResults.is_possible_local_only() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_local_only(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.invalid_length() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_too_short(), PhoneNumberTypes.mobile())

assert ValidationResults.too_short() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_too_short(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.br_too_short(), PhoneNumberTypes.fixed_line())

assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.international_networks(), PhoneNumberTypes.mobile())

assert ValidationResults.too_short() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.international_networks(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.invalid_length() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.international_networks(), PhoneNumberTypes.fixed_line())

assert ValidationResults.invalid_length() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.mobile())

assert ValidationResults.invalid_length() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.fixed_line())

assert ValidationResults.invalid_length() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.is_possible() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.universal_premium_rate(), PhoneNumberTypes.premium_rate())
end

test "IsPossibleNumberForTypeWithReason_FixedLineOrMobile" do
assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number(), PhoneNumberTypes.fixed_line())
assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number(), PhoneNumberTypes.mobile())

assert ValidationResults.is_possible() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.too_short() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_invalid(), PhoneNumberTypes.fixed_line())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_invalid(), PhoneNumberTypes.mobile())

assert ValidationResults.invalid_length() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_invalid(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number2(), PhoneNumberTypes.fixed_line())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number2(), PhoneNumberTypes.mobile())

assert ValidationResults.is_possible() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_number2(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_too_long(), PhoneNumberTypes.fixed_line())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_too_long(), PhoneNumberTypes.mobile())
assert ValidationResults.too_long() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_too_long(), PhoneNumberTypes.fixed_line_or_mobile())

assert ValidationResults.is_possible() == is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_toll_free(), PhoneNumberTypes.toll_free())

assert ValidationResults.too_long() ==
is_possible_number_for_type_with_reason?(PhoneNumberFixture.sh_toll_free(), PhoneNumberTypes.fixed_line_or_mobile())
end
end
end
Loading