From d441759ef69aebe4154658cd4057a3ad86cf06d8 Mon Sep 17 00:00:00 2001 From: Thomas Cioppettini <544875+tomciopp@users.noreply.github.com> Date: Fri, 1 Oct 2021 11:53:29 -0400 Subject: [PATCH 1/2] feat(ExPhoneNumber.Utilities#truncate_too_long_number/1): Add function that can truncate too long phone numbers. --- lib/ex_phone_number/utilities.ex | 31 ++++++++++ test/ex_phone_number/utilities_test.exs | 77 +++++++++++++++++++++++++ test/support/phone_number_fixture.ex | 7 +++ 3 files changed, 115 insertions(+) create mode 100644 test/ex_phone_number/utilities_test.exs diff --git a/lib/ex_phone_number/utilities.ex b/lib/ex_phone_number/utilities.ex index 510a992..3afeae9 100644 --- a/lib/ex_phone_number/utilities.ex +++ b/lib/ex_phone_number/utilities.ex @@ -1,5 +1,6 @@ defmodule ExPhoneNumber.Utilities do alias ExPhoneNumber.Constants.Values + alias ExPhoneNumber.Model.PhoneNumber alias ExPhoneNumber.Metadata.PhoneNumberDescription def is_nil_or_empty?(nil), do: true @@ -34,4 +35,34 @@ defmodule ExPhoneNumber.Utilities do _ -> false end end + + @doc """ + Attempts to extract a valid number from a phone number that is too long to be + valid, and resets the PhoneNumber object passed in to that valid version. If + no valid number could be extracted, the PhoneNumber object passed in will not + be modified. + """ + @spec truncate_too_long_number(%PhoneNumber{}) :: {:ok, %PhoneNumber{}} | {:error, %PhoneNumber{}} + def truncate_too_long_number(%PhoneNumber{} = phone_number) do + case truncate_number(phone_number) do + :error -> {:error, phone_number} + other -> other + end + end + + def truncate_too_long_number(other) do + raise ArgumentError, "expected an %ExPhoneNumber.Model.PhoneNumber{} received #{inspect other}" + end + + defp truncate_number(%PhoneNumber{national_number: 0}) do + :error + end + + defp truncate_number(number) do + if ExPhoneNumber.Validation.is_valid_number?(number) do + {:ok, number} + else + truncate_number(Map.replace(number, :national_number, div(number.national_number, 10))) + end + end end diff --git a/test/ex_phone_number/utilities_test.exs b/test/ex_phone_number/utilities_test.exs new file mode 100644 index 0000000..306cdc6 --- /dev/null +++ b/test/ex_phone_number/utilities_test.exs @@ -0,0 +1,77 @@ +defmodule ExPhoneNumber.UtilitiesTest do + alias ExPhoneNumber.Model.PhoneNumber + alias ExPhoneNumber.{PhoneNumberFixture, Utilities} + + use ExUnit.Case, async: true + + describe ".truncate_too_long_number/1" do + test "when a valid number is passed in" do + valid = %PhoneNumber{ + country_code: 39, + national_number: 2234567890, + italian_leading_zero: true + } + + {result, phone_number} = Utilities.truncate_too_long_number(valid) + + assert result == :ok + assert phone_number == valid + end + + test "GB number 080 1234 5678, but entered with 4 extra digits at the end." do + invalid = %PhoneNumber{country_code: 44, national_number: 80_123_456_780_123} + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :ok + assert phone_number == %PhoneNumber{country_code: 44, national_number: 8012345678} + end + + test "IT number 022 3456 7890, but entered with 3 extra digits at the end." do + invalid = %PhoneNumber{ + country_code: 39, + national_number: 2234567890123, + italian_leading_zero: true + } + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :ok + assert phone_number == %PhoneNumber{ + country_code: 39, + national_number: 2234567890, + italian_leading_zero: true + } + end + + test "US number 650-253-0000, but entered with one additional digit at the end." do + invalid = %PhoneNumber{country_code: 1, national_number: 65025300001} + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :ok + assert phone_number == %PhoneNumber{country_code: 1, national_number: 6502530000} + end + + test "US Toll free number, but entered with one additional digit at the end." do + invalid = PhoneNumberFixture.international_toll_free_too_long() + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :ok + assert phone_number == PhoneNumberFixture.international_toll_free() + end + + test "A number with an invalid prefix is passed in (US numbers cannot have prefix 240)" do + invalid = PhoneNumberFixture.us_invalid_prefix() + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :error + assert phone_number == invalid + end + + test "A number that is too short" do + invalid = %PhoneNumber{country_code: 1, national_number: 1234} + {result, phone_number} = Utilities.truncate_too_long_number(invalid) + + assert result == :error + assert phone_number == invalid + end + end +end \ No newline at end of file diff --git a/test/support/phone_number_fixture.ex b/test/support/phone_number_fixture.ex index a173404..c3bd00e 100644 --- a/test/support/phone_number_fixture.ex +++ b/test/support/phone_number_fixture.ex @@ -658,6 +658,13 @@ defmodule ExPhoneNumber.PhoneNumberFixture do } end + def us_invalid_prefix() do + %PhoneNumber{ + country_code: 1, + national_number: 2401234567 + } + end + def uz_fixed_line() do %PhoneNumber{ country_code: 998, From 397280cf7897d40eb19d3cc347a4bcd3b0ded8e0 Mon Sep 17 00:00:00 2001 From: josemrb Date: Sat, 9 Oct 2021 04:30:12 -0400 Subject: [PATCH 2/2] Refactor --- lib/ex_phone_number/extraction.ex | 34 ++++++++++ lib/ex_phone_number/model/phone_number.ex | 9 +++ lib/ex_phone_number/utilities.ex | 31 --------- test/ex_phone_number/extraction_test.exs | 19 ++++++ test/ex_phone_number/utilities_test.exs | 77 ----------------------- test/support/phone_number_fixture.ex | 36 ++++++++++- 6 files changed, 95 insertions(+), 111 deletions(-) delete mode 100644 test/ex_phone_number/utilities_test.exs diff --git a/lib/ex_phone_number/extraction.ex b/lib/ex_phone_number/extraction.ex index fefa832..878f6e1 100644 --- a/lib/ex_phone_number/extraction.ex +++ b/lib/ex_phone_number/extraction.ex @@ -232,4 +232,38 @@ defmodule ExPhoneNumber.Extraction do {false, number} end end + + @doc """ + Attempts to extract a valid number from a phone number that is too long to be + valid, and resets the PhoneNumber object passed in to that valid version. If + no valid number could be extracted, the PhoneNumber object passed in will not + be modified. + + Implements `i18n.phonenumbers.PhoneNumberUtil.prototype.truncateTooLongNumber` + """ + @spec truncate_too_long_number(%PhoneNumber{}) :: {boolean(), %PhoneNumber{}} + def truncate_too_long_number(%PhoneNumber{} = phone_number) do + if is_valid_number?(phone_number) do + {true, phone_number} + else + national_number = PhoneNumber.get_national_number_or_default(phone_number) + truncate_too_long_number(phone_number, national_number) + end + end + + defp truncate_too_long_number(%PhoneNumber{} = phone_number, national_number) do + national_number = div(national_number, 10) + phone_number_copy = %PhoneNumber{phone_number | national_number: national_number} + + cond do + national_number == 0 or is_possible_number_with_reason?(phone_number_copy) == ValidationResults.too_short() -> + {false, phone_number} + + not is_valid_number?(phone_number_copy) -> + truncate_too_long_number(phone_number, national_number) + + true -> + {true, phone_number_copy} + end + end end diff --git a/lib/ex_phone_number/model/phone_number.ex b/lib/ex_phone_number/model/phone_number.ex index c724807..483906b 100644 --- a/lib/ex_phone_number/model/phone_number.ex +++ b/lib/ex_phone_number/model/phone_number.ex @@ -32,6 +32,15 @@ defmodule ExPhoneNumber.Model.PhoneNumber do not is_nil(phone_number.country_code) end + @national_number_default 0 + def get_national_number_or_default(phone_number = %PhoneNumber{}) do + if is_nil(phone_number.national_number) do + @national_number_default + else + phone_number.national_number + end + end + @spec has_national_number?(%PhoneNumber{}) :: boolean() def has_national_number?(phone_number = %PhoneNumber{}) do not is_nil(phone_number.national_number) diff --git a/lib/ex_phone_number/utilities.ex b/lib/ex_phone_number/utilities.ex index 3afeae9..510a992 100644 --- a/lib/ex_phone_number/utilities.ex +++ b/lib/ex_phone_number/utilities.ex @@ -1,6 +1,5 @@ defmodule ExPhoneNumber.Utilities do alias ExPhoneNumber.Constants.Values - alias ExPhoneNumber.Model.PhoneNumber alias ExPhoneNumber.Metadata.PhoneNumberDescription def is_nil_or_empty?(nil), do: true @@ -35,34 +34,4 @@ defmodule ExPhoneNumber.Utilities do _ -> false end end - - @doc """ - Attempts to extract a valid number from a phone number that is too long to be - valid, and resets the PhoneNumber object passed in to that valid version. If - no valid number could be extracted, the PhoneNumber object passed in will not - be modified. - """ - @spec truncate_too_long_number(%PhoneNumber{}) :: {:ok, %PhoneNumber{}} | {:error, %PhoneNumber{}} - def truncate_too_long_number(%PhoneNumber{} = phone_number) do - case truncate_number(phone_number) do - :error -> {:error, phone_number} - other -> other - end - end - - def truncate_too_long_number(other) do - raise ArgumentError, "expected an %ExPhoneNumber.Model.PhoneNumber{} received #{inspect other}" - end - - defp truncate_number(%PhoneNumber{national_number: 0}) do - :error - end - - defp truncate_number(number) do - if ExPhoneNumber.Validation.is_valid_number?(number) do - {:ok, number} - else - truncate_number(Map.replace(number, :national_number, div(number.national_number, 10))) - end - end end diff --git a/test/ex_phone_number/extraction_test.exs b/test/ex_phone_number/extraction_test.exs index bd71363..8d2a1c3 100644 --- a/test/ex_phone_number/extraction_test.exs +++ b/test/ex_phone_number/extraction_test.exs @@ -6,6 +6,7 @@ defmodule ExPhoneNumber.ExtractionTest do import ExPhoneNumber.Extraction alias ExPhoneNumber.Metadata + alias ExPhoneNumber.PhoneNumberFixture alias ExPhoneNumber.RegionCodeFixture alias ExPhoneNumber.Constants.CountryCodeSource alias ExPhoneNumber.Constants.ErrorMessages @@ -233,4 +234,22 @@ defmodule ExPhoneNumber.ExtractionTest do assert CountryCodeSource.from_default_country() == phone_number.country_code_source end end + + describe ".truncate_too_long_number/1" do + test "TruncateTooLongNumber" do + assert {true, PhoneNumberFixture.gb_toll_free()} == truncate_too_long_number(PhoneNumberFixture.gb_toll_free_too_long()) + + assert {true, PhoneNumberFixture.it_number2()} == truncate_too_long_number(PhoneNumberFixture.it_number2_too_long()) + + assert {true, PhoneNumberFixture.us_number()} == truncate_too_long_number(PhoneNumberFixture.us_long_number()) + + assert {true, PhoneNumberFixture.international_toll_free()} == truncate_too_long_number(PhoneNumberFixture.international_toll_free_too_long()) + + assert {true, PhoneNumberFixture.gb_toll_free()} == truncate_too_long_number(PhoneNumberFixture.gb_toll_free()) + + assert {false, PhoneNumberFixture.us_invalid_prefix()} == truncate_too_long_number(PhoneNumberFixture.us_invalid_prefix()) + + assert {false, PhoneNumberFixture.us_short_number()} == truncate_too_long_number(PhoneNumberFixture.us_short_number()) + end + end end diff --git a/test/ex_phone_number/utilities_test.exs b/test/ex_phone_number/utilities_test.exs deleted file mode 100644 index 306cdc6..0000000 --- a/test/ex_phone_number/utilities_test.exs +++ /dev/null @@ -1,77 +0,0 @@ -defmodule ExPhoneNumber.UtilitiesTest do - alias ExPhoneNumber.Model.PhoneNumber - alias ExPhoneNumber.{PhoneNumberFixture, Utilities} - - use ExUnit.Case, async: true - - describe ".truncate_too_long_number/1" do - test "when a valid number is passed in" do - valid = %PhoneNumber{ - country_code: 39, - national_number: 2234567890, - italian_leading_zero: true - } - - {result, phone_number} = Utilities.truncate_too_long_number(valid) - - assert result == :ok - assert phone_number == valid - end - - test "GB number 080 1234 5678, but entered with 4 extra digits at the end." do - invalid = %PhoneNumber{country_code: 44, national_number: 80_123_456_780_123} - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :ok - assert phone_number == %PhoneNumber{country_code: 44, national_number: 8012345678} - end - - test "IT number 022 3456 7890, but entered with 3 extra digits at the end." do - invalid = %PhoneNumber{ - country_code: 39, - national_number: 2234567890123, - italian_leading_zero: true - } - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :ok - assert phone_number == %PhoneNumber{ - country_code: 39, - national_number: 2234567890, - italian_leading_zero: true - } - end - - test "US number 650-253-0000, but entered with one additional digit at the end." do - invalid = %PhoneNumber{country_code: 1, national_number: 65025300001} - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :ok - assert phone_number == %PhoneNumber{country_code: 1, national_number: 6502530000} - end - - test "US Toll free number, but entered with one additional digit at the end." do - invalid = PhoneNumberFixture.international_toll_free_too_long() - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :ok - assert phone_number == PhoneNumberFixture.international_toll_free() - end - - test "A number with an invalid prefix is passed in (US numbers cannot have prefix 240)" do - invalid = PhoneNumberFixture.us_invalid_prefix() - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :error - assert phone_number == invalid - end - - test "A number that is too short" do - invalid = %PhoneNumber{country_code: 1, national_number: 1234} - {result, phone_number} = Utilities.truncate_too_long_number(invalid) - - assert result == :error - assert phone_number == invalid - end - end -end \ No newline at end of file diff --git a/test/support/phone_number_fixture.ex b/test/support/phone_number_fixture.ex index c3bd00e..3a48c4f 100644 --- a/test/support/phone_number_fixture.ex +++ b/test/support/phone_number_fixture.ex @@ -23,7 +23,7 @@ defmodule ExPhoneNumber.PhoneNumberFixture do def ad_number() do %PhoneNumber{ country_code: 376, - national_number: 12345 + national_number: 12_345 } end @@ -291,6 +291,13 @@ defmodule ExPhoneNumber.PhoneNumberFixture do } end + def gb_toll_free_too_long() do + %PhoneNumber{ + country_code: 44, + national_number: 80_123_456_780_123 + } + end + def gb_shared_cost() do %PhoneNumber{ country_code: 44, @@ -357,6 +364,22 @@ defmodule ExPhoneNumber.PhoneNumberFixture do } end + def it_number2() do + %PhoneNumber{ + country_code: 39, + national_number: 2_234_567_890, + italian_leading_zero: true + } + end + + def it_number2_too_long() do + %PhoneNumber{ + country_code: 39, + national_number: 2_234_567_890_123, + italian_leading_zero: true + } + end + def it_premium() do %PhoneNumber{ country_code: 39, @@ -598,6 +621,13 @@ defmodule ExPhoneNumber.PhoneNumberFixture do } end + def us_short_number() do + %PhoneNumber{ + country_code: 1, + national_number: 1_234 + } + end + def us_short_by_one_number() do %PhoneNumber{ country_code: 1, @@ -658,10 +688,10 @@ defmodule ExPhoneNumber.PhoneNumberFixture do } end - def us_invalid_prefix() do + def us_invalid_prefix() do %PhoneNumber{ country_code: 1, - national_number: 2401234567 + national_number: 2_401_234_567 } end