Length checks added, validations added
This commit is contained in:
parent
51c5c1d800
commit
4447a10bf2
62
README.md
62
README.md
|
@ -12,50 +12,56 @@ In just a few letters and numbers, the IBAN captures all of the country, bank, a
|
||||||
|
|
||||||
### Successfull case to parse IBAN
|
### Successfull case to parse IBAN
|
||||||
|
|
||||||
#### Parse string with valid formatted IBAN from supported country
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
{:ok, iban} = "FI2112345600000785" |> IbanEx.Parser.parse()
|
iex> "FI2112345600000785" |> IbanEx.Parser.parse()
|
||||||
IO.inspect(iban)
|
{:ok, %IbanEx.Iban{
|
||||||
IbanEx.Iban.pretty(iban)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Success case responses
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
%IbanEx.Iban{
|
|
||||||
country_code: "FI",
|
country_code: "FI",
|
||||||
check_digits: "21",
|
check_digits: "21",
|
||||||
bank_code: "123456",
|
bank_code: "123456",
|
||||||
branch_code: nil,
|
branch_code: nil,
|
||||||
national_check: "5",
|
national_check: "5",
|
||||||
account_number: "0000078"
|
account_number: "0000078"
|
||||||
}
|
}}
|
||||||
|
|
||||||
"FI 21 123456 0000078 5"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Errors cases of IBAN parsing
|
### Errors cases of IBAN parsing
|
||||||
|
|
||||||
#### Parse strings with invalid formatted IBANs from unsupported and supported countries
|
#### To check IBAN's country is supported
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
{:error, unsupported_country_code} = "AZ21NABZ00000000137010001944" |> IbanEx.Parser.parse()
|
iex> {:error, unsupported_country_code} = IbanEx.Parser.parse("AZ21NABZ00000000137010001944")
|
||||||
IO.inspect(IbanEx.Error.message(unsupported_country_code), label: unsupported_country_code)
|
{:error, :unsupported_country_code}
|
||||||
|
iex> IbanEx.Error.message(unsupported_country_code)
|
||||||
{:error, invalid_length_code} = "AT6119043002345732012" |> IbanEx.Parser.parse()
|
"Unsupported country code"
|
||||||
IO.inspect(IbanEx.Error.message(invalid_length_code), label: invalid_length_code)
|
|
||||||
|
|
||||||
{:error, invalid_checksum} = "AT621904300234573201" |> IbanEx.Parser.parse()
|
|
||||||
IO.inspect(IbanEx.Error.message(invalid_checksum), label: invalid_checksum)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Error cases response
|
#### Validate and check IBAN length
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
unsupported_country_code: "Unsupported country code"
|
iex> {:error, invalid_length} = IbanEx.Parser.parse("AT6119043002345732012")
|
||||||
invalid_length: "IBAN violates the required length"
|
{:error, :invalid_length}
|
||||||
invalid_checksum: "IBAN's checksum is invalid"
|
iex> IbanEx.Error.message(invalid_length)
|
||||||
|
"IBAN violates the required length"
|
||||||
|
```
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
iex> {:error, length_to_long} = IbanEx.Validator.check_iban_length("AT6119043002345732012")
|
||||||
|
{:error, :length_to_long}
|
||||||
|
iex> IbanEx.Error.message(length_to_long)
|
||||||
|
"IBAN longer then required length"
|
||||||
|
iex> {:error, length_to_short} = IbanEx.Validator.check_iban_length("AT61190430023457320")
|
||||||
|
{:error, :length_to_short}
|
||||||
|
iex> IbanEx.Error.message(length_to_short)
|
||||||
|
"IBAN shorter then required length"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Validate IBAN checksum
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
iex> {:error, invalid_checksum} = IbanEx.Parser.parse("AT621904300234573201")
|
||||||
|
{:error, :invalid_checksum}
|
||||||
|
iex> IbanEx.Error.message(invalid_checksum)
|
||||||
|
"IBAN's checksum is invalid"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -65,7 +71,7 @@ The package can be installed by adding `iban_ex` to your list of dependencies in
|
||||||
```elixir
|
```elixir
|
||||||
def deps do
|
def deps do
|
||||||
[
|
[
|
||||||
{:iban_ex, "~> 0.1.1"}
|
{:iban_ex, "~> 0.1.2"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
|
@ -7,6 +7,8 @@ defmodule IbanEx.Error do
|
||||||
| :invalid_length
|
| :invalid_length
|
||||||
| :invalid_checksum
|
| :invalid_checksum
|
||||||
| :can_not_parse_map
|
| :can_not_parse_map
|
||||||
|
| :length_to_long
|
||||||
|
| :length_to_short
|
||||||
| atom()
|
| atom()
|
||||||
@type errors() :: [error()]
|
@type errors() :: [error()]
|
||||||
@errors [
|
@errors [
|
||||||
|
@ -14,7 +16,9 @@ defmodule IbanEx.Error do
|
||||||
:invalid_format,
|
:invalid_format,
|
||||||
:invalid_length,
|
:invalid_length,
|
||||||
:invalid_checksum,
|
:invalid_checksum,
|
||||||
:can_not_parse_map
|
:can_not_parse_map,
|
||||||
|
:length_to_long,
|
||||||
|
:length_to_short
|
||||||
]
|
]
|
||||||
|
|
||||||
@messages [
|
@messages [
|
||||||
|
@ -22,7 +26,9 @@ defmodule IbanEx.Error do
|
||||||
invalid_format: "IBAN violates required format",
|
invalid_format: "IBAN violates required format",
|
||||||
invalid_length: "IBAN violates the required length",
|
invalid_length: "IBAN violates the required length",
|
||||||
invalid_checksum: "IBAN's checksum is invalid",
|
invalid_checksum: "IBAN's checksum is invalid",
|
||||||
can_not_parse_map: "Can't parse map to IBAN struct"
|
can_not_parse_map: "Can't parse map to IBAN struct",
|
||||||
|
length_to_long: "IBAN longer then required length",
|
||||||
|
length_to_short: "IBAN shorter then required length"
|
||||||
]
|
]
|
||||||
|
|
||||||
@spec message(error()) :: String.t()
|
@spec message(error()) :: String.t()
|
||||||
|
|
|
@ -5,26 +5,49 @@ defmodule IbanEx.Validator do
|
||||||
alias IbanEx.Validator.Replacements
|
alias IbanEx.Validator.Replacements
|
||||||
import IbanEx.Commons, only: [normalize: 1]
|
import IbanEx.Commons, only: [normalize: 1]
|
||||||
|
|
||||||
|
defp error_accumulator(acc, error_message)
|
||||||
|
defp error_accumulator(acc, {:error, error}), do: [error | acc]
|
||||||
|
defp error_accumulator(acc, _), do: acc
|
||||||
|
|
||||||
|
defp violation_functions(),
|
||||||
|
do: [
|
||||||
|
{&__MODULE__.iban_violates_format?/1, {:error, :invalid_format}},
|
||||||
|
{&__MODULE__.iban_unsupported_country?/1, {:error, :unsupported_country_code}},
|
||||||
|
{&__MODULE__.iban_violates_length?/1, {:error, :invalid_length}},
|
||||||
|
{&__MODULE__.iban_violates_country_rule?/1, {:error, :invalid_format}},
|
||||||
|
{&__MODULE__.iban_violates_checksum?/1, {:error, :invalid_checksum}}
|
||||||
|
]
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Accumulate check results in the list of errors
|
||||||
|
Check iban_violates_format?, iban_unsupported_country?, iban_violates_length?, iban_violates_country_rule?, iban_violates_checksum?
|
||||||
|
"""
|
||||||
|
@spec violations(String.t()) :: [] | [atom()]
|
||||||
|
def violations(iban) do
|
||||||
|
violation_functions()
|
||||||
|
|> Enum.reduce([], fn {fun, value}, acc -> error_accumulator(acc, !fun.(iban) or value) end)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make checks in this order step-by-step before first error ->
|
||||||
|
|
||||||
|
iban_violates_format?,
|
||||||
|
iban_unsupported_country?,
|
||||||
|
iban_violates_length?,
|
||||||
|
iban_violates_country_rule?,
|
||||||
|
iban_violates_checksum?
|
||||||
|
|
||||||
|
"""
|
||||||
@spec validate(String.t()) :: {:ok, String.t()} | {:error, Atom.t()}
|
@spec validate(String.t()) :: {:ok, String.t()} | {:error, Atom.t()}
|
||||||
def validate(iban) do
|
def validate(iban) do
|
||||||
cond do
|
cond do
|
||||||
iban_violates_format?(iban) ->
|
iban_violates_format?(iban) -> {:error, :invalid_format}
|
||||||
{:error, :invalid_format}
|
iban_unsupported_country?(iban) -> {:error, :unsupported_country_code}
|
||||||
|
iban_violates_length?(iban) -> {:error, :invalid_length}
|
||||||
iban_unsupported_country?(iban) ->
|
iban_violates_country_rule?(iban) -> {:error, :invalid_format}
|
||||||
{:error, :unsupported_country_code}
|
iban_violates_checksum?(iban) -> {:error, :invalid_checksum}
|
||||||
|
true -> {:ok, normalize(iban)}
|
||||||
iban_violates_length?(iban) ->
|
|
||||||
{:error, :invalid_length}
|
|
||||||
|
|
||||||
iban_violates_country_rule?(iban) ->
|
|
||||||
{:error, :invalid_format}
|
|
||||||
|
|
||||||
iban_violates_checksum?(iban) ->
|
|
||||||
{:error, :invalid_checksum}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
{:ok, normalize(iban)}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,12 +60,12 @@ defmodule IbanEx.Validator do
|
||||||
|
|
||||||
# - Check whether a given IBAN violates the required format.
|
# - Check whether a given IBAN violates the required format.
|
||||||
@spec iban_violates_format?(String.t()) :: boolean
|
@spec iban_violates_format?(String.t()) :: boolean
|
||||||
defp iban_violates_format?(iban),
|
def iban_violates_format?(iban),
|
||||||
do: Regex.match?(~r/[^A-Z0-9]/i, normalize(iban))
|
do: Regex.match?(~r/[^A-Z0-9]/i, normalize(iban))
|
||||||
|
|
||||||
# - Check whether a given IBAN violates the supported countries.
|
# - Check whether a given IBAN violates the supported countries.
|
||||||
@spec iban_unsupported_country?(String.t()) :: boolean
|
@spec iban_unsupported_country?(String.t()) :: boolean
|
||||||
defp iban_unsupported_country?(iban) do
|
def iban_unsupported_country?(iban) do
|
||||||
supported? =
|
supported? =
|
||||||
iban
|
iban
|
||||||
|> Parser.country_code()
|
|> Parser.country_code()
|
||||||
|
@ -51,33 +74,52 @@ defmodule IbanEx.Validator do
|
||||||
!supported?
|
!supported?
|
||||||
end
|
end
|
||||||
|
|
||||||
# - Check whether a given IBAN violates the required length.
|
@doc "Check whether a given IBAN violates the required length."
|
||||||
@spec iban_violates_length?(String.t()) :: boolean
|
@spec iban_violates_length?(String.t()) :: boolean
|
||||||
defp iban_violates_length?(iban) do
|
def iban_violates_length?(iban) do
|
||||||
with country_code <- Parser.country_code(iban),
|
with country_code <- Parser.country_code(iban),
|
||||||
country_module <- Country.country_module(country_code) do
|
country_module when is_atom(country_module) <- Country.country_module(country_code) do
|
||||||
size(iban) != country_module.size()
|
size(iban) != country_module.size()
|
||||||
else
|
else
|
||||||
{:error, _} -> true
|
{:error, _error} -> true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# - Check whether a given IBAN violates the country rules.
|
@doc "Check length of IBAN"
|
||||||
|
@spec check_iban_length(String.t()) :: {:error, :length_to_short | :length_to_long } | :ok
|
||||||
|
def check_iban_length(iban) do
|
||||||
|
unless iban_unsupported_country?(iban) do
|
||||||
|
country_module =
|
||||||
|
iban
|
||||||
|
|> Parser.country_code()
|
||||||
|
|> Country.country_module()
|
||||||
|
|
||||||
|
case country_module.size() - size(iban) do
|
||||||
|
diff when diff > 0 -> {:error, :length_to_short}
|
||||||
|
diff when diff < 0 -> {:error, :length_to_long}
|
||||||
|
0 -> :ok
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:error, :unsupported_country_code}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Check whether a given IBAN violates the country rules"
|
||||||
@spec iban_violates_country_rule?(String.t()) :: boolean
|
@spec iban_violates_country_rule?(String.t()) :: boolean
|
||||||
defp iban_violates_country_rule?(iban) do
|
def iban_violates_country_rule?(iban) do
|
||||||
with country_code <- Parser.country_code(iban),
|
with country_code <- Parser.country_code(iban),
|
||||||
bban <- Parser.bban(iban),
|
bban <- Parser.bban(iban),
|
||||||
country_module <- Country.country_module(country_code),
|
country_module when is_atom(country_module) <- Country.country_module(country_code),
|
||||||
rule <- country_module.rule() do
|
rule <- country_module.rule() do
|
||||||
!Regex.match?(rule, bban)
|
!Regex.match?(rule, bban)
|
||||||
else
|
else
|
||||||
{:error, _} -> true
|
{:error, _error} -> true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# - Check whether a given IBAN violates the required checksum.
|
@doc "Check whether a given IBAN violates the required checksum."
|
||||||
@spec iban_violates_checksum?(String.t()) :: boolean
|
@spec iban_violates_checksum?(String.t()) :: boolean
|
||||||
defp iban_violates_checksum?(iban) do
|
def iban_violates_checksum?(iban) do
|
||||||
check_sum_base = Parser.bban(iban) <> Parser.country_code(iban) <> "00"
|
check_sum_base = Parser.bban(iban) <> Parser.country_code(iban) <> "00"
|
||||||
|
|
||||||
replacements = Replacements.replacements()
|
replacements = Replacements.replacements()
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -2,7 +2,7 @@ defmodule IbanEx.MixProject do
|
||||||
use Mix.Project
|
use Mix.Project
|
||||||
|
|
||||||
@source_url "https://g.tulz.dev/opensource/iban-ex"
|
@source_url "https://g.tulz.dev/opensource/iban-ex"
|
||||||
@version "0.1.2"
|
@version "0.1.3"
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule IbanExTest do
|
defmodule IbanExTest do
|
||||||
alias IbanEx.{Country, Iban, Parser}
|
alias IbanEx.{Country, Iban, Parser}
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
doctest_file "README.md"
|
||||||
doctest IbanEx.Country.AT
|
doctest IbanEx.Country.AT
|
||||||
doctest IbanEx.Country.BE
|
doctest IbanEx.Country.BE
|
||||||
doctest IbanEx.Country.BG
|
doctest IbanEx.Country.BG
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule IbanExValidatorTest do
|
||||||
|
alias IbanEx.{Validator}
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
test "check IBANs length" do
|
||||||
|
cases = [
|
||||||
|
{"FG2112345CC6000007", {:error, :unsupported_country_code}},
|
||||||
|
{"UK2112345CC6000007", {:error, :unsupported_country_code}},
|
||||||
|
{"FI2112345CC6000007", :ok},
|
||||||
|
{"FI2112345CC6000007a", {:error, :length_to_long}},
|
||||||
|
{"FI2112345CC600007", {:error, :length_to_short}}
|
||||||
|
]
|
||||||
|
|
||||||
|
Enum.all?(cases, fn {iban, result} ->
|
||||||
|
assert Validator.check_iban_length(iban) == result
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue