Skip to content

Commit

Permalink
Update Validation
Browse files Browse the repository at this point in the history
+ is_possible_number_for_type?/2
+ is_possible_number_for_type_with_reason?/2

Co-authored-by: josemrb <[email protected]>
  • Loading branch information
tomciopp and josemrb authored Feb 20, 2022
1 parent 6c01108 commit 1cc1992
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 0 deletions.
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

0 comments on commit 1cc1992

Please sign in to comment.