Current section
Files
Jump to
Current section
Files
lib/validators/phone_number.ex
defmodule EctoCommons.PhoneNumberValidator do
@moduledoc """
Validate a phone number.
This validator uses libphonenumber under the hood to
validate phone numbers.
This validator expects a `:country` argument to be passed
so it can check the number corresponds to the given country.
If you pass it `""` or `nil` it won't complain as long as the number
is a valid E164 format that can be parsed without the country info.
## Examples
iex> types = %{phone_number: :string}
iex> params = %{phone_number: "0610234567"}
iex> Ecto.Changeset.cast({%{}, types}, params, Map.keys(types))
...> |> validate_phone_number(:phone_number, country: "fr")
#Ecto.Changeset<action: nil, changes: %{phone_number: "0610234567"}, errors: [], data: %{}, valid?: true>
iex> types = %{phone_number: :string}
iex> params = %{phone_number: "798765432L"}
iex> Ecto.Changeset.cast({%{}, types}, params, Map.keys(types))
...> |> validate_phone_number(:phone_number, country: "ch")
#Ecto.Changeset<action: nil, changes: %{phone_number: "798765432L"}, errors: [], data: %{}, valid?: true>
# Country can be ignored in E164 formatted numbers
iex> types = %{phone_number: :string}
iex> params = %{phone_number: "+16502530000"}
iex> Ecto.Changeset.cast({%{}, types}, params, Map.keys(types))
...> |> validate_phone_number(:phone_number)
#Ecto.Changeset<action: nil, changes: %{phone_number: "+16502530000"}, errors: [], data: %{}, valid?: true>
iex> types = %{phone_number: :string}
iex> params = %{phone_number: "01 23 45 67 89"}
iex> Ecto.Changeset.cast({%{}, types}, params, Map.keys(types))
...> |> validate_phone_number(:phone_number, country: nil)
#Ecto.Changeset<action: nil, changes: %{phone_number: "01 23 45 67 89"}, errors: [phone_number: {\"is not a valid phone number\", [validation: :phone_number]}], data: %{}, valid?: false>
"""
import Ecto.Changeset
def validate_phone_number(changeset, field, opts \\ []) do
validate_change(changeset, field, {:phone_number, opts}, fn _, value ->
country_isoalpha2 = Keyword.get(opts, :country) || ""
country_isoalpha2 = String.upcase(country_isoalpha2)
with {:ok, parsed} <- ExPhoneNumber.parse(value, country_isoalpha2),
true <- ExPhoneNumber.is_valid_number?(parsed) do
[]
else
_err ->
[{field, {message(opts, "is not a valid phone number"), [validation: :phone_number]}}]
end
end)
end
defp message(opts, key \\ :message, default) do
Keyword.get(opts, key, default)
end
end