Current section
Files
Jump to
Current section
Files
lib/validators.ex
defmodule ExIban.Validators do
@moduledoc """
Set of validation rules to perform on checking IBAN account number.
"""
import ExIban.Rules
import ExIban.Parser
@doc """
Performs validation checks:
- length greater or equal to 5 (:too_short)
- only ilegal chars used [A-Z0-9] (bad_chars)
- known country code (:unknown_country_code)
- correct length for given country (:bad_length)
- correct bban format for given country (:bad_format)
- checking digits (:bad_check_digits)
## Examples
iex> ExIban.Validators.issues("GB82")
{[:too_short], nil}
iex> ExIban.Validators.issues("GB_+)*")
{[:bad_chars, :bad_length, :bad_format, :bad_check_digits], {"GB", "_+", ")*", 6, "GB_+)*"}}
iex> ExIban.Validators.issues("GB82 WEST 1234 5698 7654 32")
{[], {"GB", "82", "WEST12345698765432", 22, "GB82WEST12345698765432"}}
"""
@spec issues(binary) :: {list, {bitstring, bitstring, bitstring, integer, bitstring} | nil}
def issues(iban) when byte_size(iban) < 5, do: {[:too_short], nil}
def issues(iban) do
iban
|> parse
|> do_validation
end
defp do_validation(iban) do
iban
|> check_chars
|> check_country_code
|> check_length
|> check_format
|> check_digits
end
defp check_chars({_, _, _, _, iban} = parsed_iban) do
cond do
not Regex.match?(~r/^[A-Z0-9]+$/, iban) -> {[:bad_chars], parsed_iban}
true -> {[], parsed_iban}
end
end
defp check_country_code({errors, {country_code, _, _, _, _} = parsed_iban}) do
cond do
rules |> Map.get(country_code) |> is_nil ->
{errors ++ [:unknown_country_code], parsed_iban}
true -> {errors, parsed_iban}
end
end
defp check_length({errors,
{country_code, _, _, iban_length, _} = parsed_iban}) do
cond do
rules |> Map.get(country_code, %{}) |> Map.get("length") != iban_length ->
{errors ++ [:bad_length], parsed_iban}
true -> {errors, parsed_iban}
end
end
defp check_format({errors,
{country_code, _, bban, _, _} = parsed_iban}) do
{:ok, reg} = rules
|> Map.get(country_code, %{})
|> Map.get("bban_pattern", "")
|> Regex.compile
cond do
not Regex.match?(reg, bban) -> {errors ++ [:bad_format], parsed_iban}
true -> {errors, parsed_iban}
end
end
defp check_digits({errors,
{country_code, check_digits, bban, _, _} = parsed_iban}) do
chars = String.to_char_list(bban <> country_code <> check_digits)
numbers = for byte <- chars, into: [] do
case byte do
byte when byte in 48..57 -> List.to_string([byte])
byte when byte in 65..90 -> Integer.to_string(byte - 55)
_ -> ""
end
end
cond do
numbers |> Enum.join |> String.to_integer |> rem(97) != 1 ->
{errors ++ [:bad_check_digits], parsed_iban}
true -> {errors, parsed_iban}
end
end
end