lib/ex_vatcheck/countries.ex

defmodule ExVatcheck.Countries do
  @moduledoc """
  A module for checking to see whether or not a VAT matches one of the expected
  patterns for EU countries. Countries handled include:

  ```
  AT: Austria
  BE: Belgium
  BG: Bulgaria
  CY: Cyprus
  CZ: Czech Republic
  DE: Germany
  DK: Denmark
  EE: Estonia
  EL: Greece
  ES: Spain
  FI: Finland
  FR: France
  GB: United Kingdom
  HR: Croatia
  HU: Hungary
  IE: Ireland
  IT: Italy
  LT: Lithuania
  LU: Luxembourg
  LV: Latvia
  MT: Malta
  NL: Netherlands
  PL: Poland
  PT: Portugal
  RO: Romania
  SE: Sweden
  SI: Slovenia
  SK: Slovakia
  ```
  """

  @regexes %{
    "AT" => ~r/\AATU[0-9]{8}\Z/u,
    "BE" => ~r/\ABE0[0-9]{9}\Z/u,
    "BG" => ~r/\ABG[0-9]{9,10}\Z/u,
    "CY" => ~r/\ACY[0-9]{8}[A-Z]\Z/u,
    "CZ" => ~r/\ACZ[0-9]{8,10}\Z/u,
    "DE" => ~r/\ADE[0-9]{9}\Z/u,
    "DK" => ~r/\ADK[0-9]{8}\Z/u,
    "EE" => ~r/\AEE[0-9]{9}\Z/u,
    "EL" => ~r/\AEL[0-9]{9}\Z/u,
    "ES" => ~r/\AES([A-Z][0-9]{8}|[0-9]{8}[A-Z]|[A-Z][0-9]{7}[A-Z])\Z/u,
    "FI" => ~r/\AFI[0-9]{8}\Z/u,
    "FR" => ~r/\AFR[A-Z0-9]{2}[0-9]{9}\Z/u,
    "GB" => ~r/\AGB([0-9]{9}|[0-9]{12}|(HA|GD)[0-9]{3})\Z/u,
    "HR" => ~r/\AHR[0-9]{11}\Z/u,
    "HU" => ~r/\AHU[0-9]{8}\Z/u,
    "IE" => ~r/\AIE([0-9][A-Z][0-9]{5}|[0-9]{7}[A-Z]?)[A-Z]\Z/u,
    "IT" => ~r/\AIT[0-9]{11}\Z/u,
    "LT" => ~r/\ALT([0-9]{9}|[0-9]{12})\Z/u,
    "LU" => ~r/\ALU[0-9]{8}\Z/u,
    "LV" => ~r/\ALV[0-9]{11}\Z/u,
    "MT" => ~r/\AMT[0-9]{8}\Z/u,
    "NL" => ~r/\ANL[0-9]{9}B[0-9]{2}\Z/u,
    "PL" => ~r/\APL[0-9]{10}\Z/u,
    "PT" => ~r/\APT[0-9]{9}\Z/u,
    "RO" => ~r/\ARO[1-9][0-9]{1,9}\Z/u,
    "SE" => ~r/\ASE[0-9]{12}\Z/u,
    "SI" => ~r/\ASI[0-9]{8}\Z/u,
    "SK" => ~r/\ASK[0-9]{10}\Z/u
  }

  @countries Map.keys(@regexes)

  @spec valid_format?(binary) :: boolean
  @doc ~S"""
  Determines whether or not a VAT identification number has a valid format by
  checking to see if it matches any of the country-specific regexes.

  Returns `true` if the VAT number matches one of the regexes, and returns `false`
  otherwise.
  """
  def valid_format?(vat) when byte_size(vat) <= 2, do: false

  def valid_format?(<<country::binary-size(2), _::binary>>) when country not in @countries,
    do: false

  def valid_format?(<<country::binary-size(2), _::binary>> = vat) do
    @regexes
    |> Map.get(country)
    |> Regex.match?(vat)
  end
end