defmodule IbanEx.Parser do @moduledoc false alias IbanEx.{Country, Iban, Validator} import IbanEx.Commons, only: [normalize_and_slice: 2] @type iban_string() :: String.t() @type country_code_string() :: <<_::16>> @type check_digits_string() :: <<_::16>> @type iban() :: IbanEx.Iban.t() @type iban_or_error() :: {:ok, iban()} | {:invalid_checksum, binary()} | {:invalid_format, binary()} | {:invalid_length, binary()} | {:can_not_parse_map, binary()} | {:unsupported_country_code, binary()} @spec parse({:ok, binary()}) :: iban_or_error() def parse({:ok, iban_string}), do: parse(iban_string) def parse(iban_string, options \\ [incomplete: false]) def parse(iban_string, incomplete: false) do case Validator.validate(iban_string) do {:ok, valid_iban} -> iban_map = %{ country_code: country_code(valid_iban), check_digits: check_digits(valid_iban) } bban_map = iban_string |> bban() |> parse_bban(iban_map.country_code) {:ok, struct(Iban, Map.merge(iban_map, bban_map))} {:error, error_type} -> {:error, error_type} end end def parse(iban_string, incomplete: true) do iban_map = %{ country_code: country_code(iban_string), check_digits: check_digits(iban_string) } bban = bban(iban_string) case Country.is_country_code_supported?(iban_map.country_code) do true -> result = parse_bban(bban, iban_map.country_code, incomplete: true) |> Map.merge(iban_map) {:ok, struct(Iban, result)} false -> {:error, :unsupported_country_code} end end @spec parse_bban(binary(), <<_::16>>) :: map() def parse_bban(bban_string, country_code, options \\ [incomplete: false]) def parse_bban(bban_string, country_code, incomplete: true) do case Country.is_country_code_supported?(country_code) do true -> country_code |> Country.country_module() |> parse_bban_by_country_rules(bban_string) false -> %{} end end def parse_bban(bban_string, country_code, incomplete: false) do case Country.is_country_code_supported?(country_code) do true -> Country.country_module(country_code).rule() |> parse_bban_by_regex(bban_string) false -> %{} end end defp parse_bban_by_country_rules(country_module, bban_string) do for {field, rule} <- country_module.rules, into: %{}, do: {field, normalize_and_slice(bban_string, rule.range)} end defp parse_bban_by_regex(_regex, nil), do: %{} defp parse_bban_by_regex(regex, bban_string) do case Regex.named_captures(regex, bban_string) do map when is_map(map) -> for {key, val} <- map, into: %{}, do: {String.to_atom(key), val} nil -> %{} end end @spec country_code(iban_string()) :: country_code_string() def country_code(iban_string), do: normalize_and_slice(iban_string, 0..1) @spec check_digits(binary()) :: check_digits_string() def check_digits(iban_string), do: normalize_and_slice(iban_string, 2..3) @spec bban(binary()) :: binary() def bban(iban_string), do: normalize_and_slice(iban_string, 4..-1//1) end