Skua.Phone (Skua v0.1.0)

Copy Markdown View Source

Self-contained phone harness β€” country data, country/dial-code helpers, E.164 normalization, display formatting, and an Ecto changeset validator.

No third-party dependency by default (the country list ships in Skua.Phone.Countries). For full libphonenumber-grade validation install the optional ex_phone_number via mix skua.install --with-phone, which swaps valid?/1 to delegate to it.

Changeset usage

import Skua.Phone, only: [validate_phone: 3]

def changeset(lead, attrs) do
  lead
  |> cast(attrs, [:phone])
  |> validate_phone(:phone, required: true)
end

The phone field component (Skua.Components.Phone) writes a canonical E.164 string (e.g. "+15555555550") into a hidden input, so the value you validate and store is already normalized.

Summary

Functions

Dial code (string) for an ISO-3166 alpha-2 code; defaults to "1".

All {name, iso2, dial} country tuples.

ISO-3166 flag emoji for an alpha-2 code (e.g. "US" -> πŸ‡ΊπŸ‡Έ).

Just the digits of any phone-ish string.

Canonical E.164 for a country + national number, or "" if the national part is empty.

Filters the country list by name, ISO code, or dial-code prefix.

Country-specific national formatting (US/CA get (555) 555-0100).

Infers the ISO code from a full number by longest dial-code prefix; defaults to "US".

Strips the dial code and returns the formatted national number for display.

Normalizes any phone string to canonical E.164 ("+" <> digits).

Strict, metadata-backed validity via ex_phone_number (opt-in β€” install it with mix skua.install --with-phone). Falls back to valid?/1 when the library isn't present, so calling this never crashes a build without it.

Lenient E.164 shape check β€” a leading + followed by 7–15 digits.

Ecto changeset validator β€” bring your own validation.

Functions

calling_code(iso)

Dial code (string) for an ISO-3166 alpha-2 code; defaults to "1".

countries()

All {name, iso2, dial} country tuples.

country_to_flag(code)

ISO-3166 flag emoji for an alpha-2 code (e.g. "US" -> πŸ‡ΊπŸ‡Έ).

digits(value)

Just the digits of any phone-ish string.

e164(iso, national)

Canonical E.164 for a country + national number, or "" if the national part is empty.

iex> Skua.Phone.e164("US", "(555) 555-0100")
"+15555550100"

filter(query)

Filters the country list by name, ISO code, or dial-code prefix.

format_national(digits, iso)

Country-specific national formatting (US/CA get (555) 555-0100).

infer_country(phone)

Infers the ISO code from a full number by longest dial-code prefix; defaults to "US".

national_number(phone, iso)

Strips the dial code and returns the formatted national number for display.

normalize(value)

Normalizes any phone string to canonical E.164 ("+" <> digits).

iex> Skua.Phone.normalize("+1 (555) 555-0100")
"+15555550100"

strict_valid?(value)

Strict, metadata-backed validity via ex_phone_number (opt-in β€” install it with mix skua.install --with-phone). Falls back to valid?/1 when the library isn't present, so calling this never crashes a build without it.

valid?(value)

Lenient E.164 shape check β€” a leading + followed by 7–15 digits.

This is deliberately permissive: Skua does not enforce country-specific phone rules by default (that's a slippery slope and a common source of false rejections for valid international numbers). It only confirms the value looks like an E.164 number. For strict, metadata-backed validation, see strict_valid?/1 (requires ex_phone_number) or pass your own :with function to validate_phone/3.

validate_phone(changeset, field, opts \\ [])

Ecto changeset validator β€” bring your own validation.

By default it only checks the lenient valid?/1 shape. Phone validation is a slippery slope, so Skua keeps the default permissive and makes stricter rules a deliberate opt-in:

# default: just the E.164 shape
validate_phone(changeset, :phone)

# required
validate_phone(changeset, :phone, required: true)

# strict (needs ex_phone_number)
validate_phone(changeset, :phone, with: &Skua.Phone.strict_valid?/1)

# your own rule (e.g. US-only)
validate_phone(changeset, :phone, with: fn p -> String.starts_with?(p, "+1") end)

Options: :required (boolean), :message (string), :with (a (String.t() -> boolean) validator; defaults to &valid?/1).