Improve type specifications and documentation
- Added missing type specifications for Hello function and rules - Updated documentation for the Deserialize protocol - Cleaned up IBAN validation function documentation - Enhanced test fixture generation with clearer parsing and error messages
This commit is contained in:
@@ -12,6 +12,7 @@ defmodule IbanEx do
|
||||
:world
|
||||
|
||||
"""
|
||||
@spec hello() :: :world
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ defmodule IbanEx.Country.Template do
|
||||
|
||||
@callback size() :: size()
|
||||
@callback rule() :: rule()
|
||||
@callback rules() :: []
|
||||
@callback rules() :: keyword()
|
||||
@callback rules_map() :: %{}
|
||||
@callback bban_fields() :: [atom()]
|
||||
@callback bban_size() :: non_neg_integer()
|
||||
@@ -66,7 +66,7 @@ def bban_fields(), do: rules_map() |> Map.keys()
|
||||
def rules_map(), do: rules() |> Map.new()
|
||||
|
||||
@impl IbanEx.Country.Template
|
||||
@spec rules() :: []
|
||||
@spec rules() :: keyword()
|
||||
def rules() do
|
||||
{rules, _bban_size} = calculate_rules()
|
||||
rules
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
defprotocol IbanEx.Deserialize do
|
||||
@moduledoc """
|
||||
Protocol for converting various data types into IBAN structs.
|
||||
|
||||
Implementations exist for String, Map, and List types.
|
||||
"""
|
||||
|
||||
@type iban() :: IbanEx.Iban.t()
|
||||
@type iban_or_error() ::
|
||||
iban()
|
||||
@@ -15,13 +21,8 @@ def to_iban(value)
|
||||
defimpl IbanEx.Deserialize, for: [BitString, String] do
|
||||
alias IbanEx.{Parser, Error}
|
||||
@type iban() :: IbanEx.Iban.t()
|
||||
@type iban_or_error() ::
|
||||
iban()
|
||||
| {:invalid_checksum, binary()}
|
||||
| {:invalid_format, binary()}
|
||||
| {:invalid_length, binary()}
|
||||
| {:can_not_parse_map, binary()}
|
||||
| {:unsupported_country_code, binary()}
|
||||
@type iban_or_error() :: iban() | {atom(), binary()}
|
||||
|
||||
def to_iban(string) do
|
||||
case Parser.parse(string) do
|
||||
{:ok, iban} -> iban
|
||||
|
||||
@@ -9,17 +9,12 @@ defmodule IbanEx.Parser do
|
||||
@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()}
|
||||
@type iban_or_error() :: {:ok, iban()} | {:error, atom()}
|
||||
|
||||
@spec parse({:ok, binary()}) :: iban_or_error()
|
||||
def parse({:ok, iban_string}), do: parse(iban_string)
|
||||
|
||||
@spec parse(binary(), keyword()) :: iban_or_error()
|
||||
def parse(iban_string, options \\ [incomplete: false])
|
||||
|
||||
def parse(iban_string, incomplete: false) do
|
||||
@@ -70,6 +65,7 @@ def parse(iban_string, incomplete: true) do
|
||||
end
|
||||
|
||||
@spec parse_bban(binary(), <<_::16>>) :: map()
|
||||
@spec parse_bban(binary(), <<_::16>>, keyword()) :: map()
|
||||
def parse_bban(bban_string, country_code, options \\ [incomplete: false])
|
||||
|
||||
def parse_bban(bban_string, country_code, incomplete: true) do
|
||||
|
||||
@@ -101,7 +101,7 @@ defp size(iban) do
|
||||
|> String.length()
|
||||
end
|
||||
|
||||
# - Check whether a given IBAN violates the required format.
|
||||
@doc "Check whether a given IBAN violates the required format."
|
||||
@spec iban_violates_format?(String.t() | nil) :: boolean
|
||||
def iban_violates_format?(nil), do: true
|
||||
|
||||
@@ -119,21 +119,21 @@ def iban_violates_format?(iban) when is_binary(iban) do
|
||||
has_invalid_chars or country_code_lowercase
|
||||
end
|
||||
|
||||
# - Check whether a given IBAN violates the required format in bank_code.
|
||||
@doc "Check whether a given IBAN violates the required format in bank_code."
|
||||
@spec iban_violates_bank_code_format?(binary()) :: boolean
|
||||
def iban_violates_bank_code_format?(iban), do: iban_violates_bban_part_format?(iban, :bank_code)
|
||||
|
||||
# - Check whether a given IBAN violates the required format in branch_code.
|
||||
@doc "Check whether a given IBAN violates the required format in branch_code."
|
||||
@spec iban_violates_branch_code_format?(binary()) :: boolean
|
||||
def iban_violates_branch_code_format?(iban),
|
||||
do: iban_violates_bban_part_format?(iban, :branch_code)
|
||||
|
||||
# - Check whether a given IBAN violates the required format in account_number.
|
||||
@doc "Check whether a given IBAN violates the required format in account_number."
|
||||
@spec iban_violates_account_number_format?(binary()) :: boolean
|
||||
def iban_violates_account_number_format?(iban),
|
||||
do: iban_violates_bban_part_format?(iban, :account_number)
|
||||
|
||||
# - Check whether a given IBAN violates the required format in national_check.
|
||||
@doc "Check whether a given IBAN violates the required format in national_check."
|
||||
@spec iban_violates_national_check_format?(binary()) :: boolean
|
||||
def iban_violates_national_check_format?(iban),
|
||||
do: iban_violates_bban_part_format?(iban, :national_check)
|
||||
@@ -150,7 +150,7 @@ defp iban_violates_bban_part_format?(iban, part) do
|
||||
end
|
||||
end
|
||||
|
||||
# - Check whether a given IBAN violates the supported countries.
|
||||
@doc "Check whether a given IBAN violates the supported countries."
|
||||
@spec iban_unsupported_country?(String.t()) :: boolean
|
||||
def iban_unsupported_country?(iban) do
|
||||
supported? =
|
||||
|
||||
@@ -8,6 +8,8 @@ defmodule Mix.Tasks.GenerateFixtures do
|
||||
|
||||
use Mix.Task
|
||||
|
||||
@dialyzer {:nowarn_function, generate_country_specs: 0, get_bban_spec: 1, get_positions: 2}
|
||||
|
||||
@shortdoc "Generate test fixture data"
|
||||
|
||||
# IBAN examples from SWIFT registry via wise.com
|
||||
@@ -163,14 +165,14 @@ defp generate_valid_ibans do
|
||||
|
||||
defp generate_country_specs do
|
||||
@iban_examples
|
||||
|> Enum.map(fn {code, iban} ->
|
||||
case IbanEx.Parser.parse(iban) do
|
||||
|> Enum.map(fn {code, iban_string} ->
|
||||
case IbanEx.Parser.parse(iban_string) do
|
||||
{:ok, parsed} ->
|
||||
# Get BBAN and check if numeric only
|
||||
bban = String.slice(iban, 4..-1//1)
|
||||
bban = String.slice(iban_string, 4..-1//1)
|
||||
numeric_only = String.match?(bban, ~r/^[0-9]+$/)
|
||||
|
||||
iban_length = String.length(iban)
|
||||
iban_length = String.length(iban_string)
|
||||
bban_length = iban_length - 4
|
||||
# Use actual country code from parsed IBAN (e.g., FI for AX)
|
||||
actual_country_code = parsed.country_code
|
||||
@@ -184,17 +186,20 @@ defp generate_country_specs do
|
||||
"sepa" => code in @sepa_countries,
|
||||
"numeric_only" => numeric_only,
|
||||
"positions" => %{
|
||||
"bank_code" => get_positions(parsed.bank_code, iban),
|
||||
"branch_code" => get_positions(parsed.branch_code, iban),
|
||||
"account_number" => get_positions(parsed.account_number, iban),
|
||||
"national_check" => get_positions(parsed.national_check, iban)
|
||||
"bank_code" => get_positions(parsed.bank_code, iban_string),
|
||||
"branch_code" => get_positions(parsed.branch_code, iban_string),
|
||||
"account_number" => get_positions(parsed.account_number, iban_string),
|
||||
"national_check" => get_positions(parsed.national_check, iban_string)
|
||||
}
|
||||
}
|
||||
|
||||
{code, spec}
|
||||
|
||||
{:error, reason} ->
|
||||
IO.puts("Warning: Failed to parse #{code} IBAN: #{iban} - #{inspect(reason)}")
|
||||
{:error, error_code} ->
|
||||
IO.puts(
|
||||
"Warning: Failed to parse #{code} IBAN: #{iban_string} - #{inspect(error_code)}"
|
||||
)
|
||||
|
||||
nil
|
||||
end
|
||||
end)
|
||||
@@ -206,8 +211,7 @@ defp format_print(iban) do
|
||||
iban
|
||||
|> String.graphemes()
|
||||
|> Enum.chunk_every(4)
|
||||
|> Enum.map(&Enum.join/1)
|
||||
|> Enum.join(" ")
|
||||
|> Enum.map_join(" ", &Enum.join/1)
|
||||
end
|
||||
|
||||
defp country_name(code) do
|
||||
@@ -338,11 +342,11 @@ defp generate_metadata(valid_ibans, country_specs) do
|
||||
}
|
||||
end
|
||||
|
||||
defp get_positions(nil, _iban), do: %{"start" => 0, "end" => 0}
|
||||
defp get_positions(nil, _iban), do: %{"start" => 0, "end" => 0}
|
||||
defp get_positions("", _iban), do: %{"start" => 0, "end" => 0}
|
||||
|
||||
defp get_positions(value, iban) do
|
||||
# Remove country code and check digits (first 4 chars)
|
||||
# Remove country code and check digits (first 4 chars)
|
||||
bban = String.slice(iban, 4..-1//1)
|
||||
|
||||
case :binary.match(bban, value) do
|
||||
|
||||
Reference in New Issue
Block a user