Skip to content

Commit 4d5dc1d

Browse files
Merge pull request #10 from warmwaffles/parse-bic-into-components
Adds a struct to Bankster.Bic
2 parents bb8abe7 + d8d0671 commit 4d5dc1d

File tree

2 files changed

+283
-16
lines changed

2 files changed

+283
-16
lines changed

lib/bankster/bic.ex

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,164 @@
11
defmodule Bankster.Bic do
22
@moduledoc """
33
Module provides some SWIFT BIC related functions.
4+
5+
See: https://en.wikipedia.org/wiki/ISO_9362
6+
"""
7+
8+
defstruct [:bank, :country, :location, :branch]
9+
10+
@type t :: %__MODULE__{
11+
bank: binary(),
12+
country: binary(),
13+
location: binary(),
14+
branch: binary() | nil
15+
}
16+
17+
@bic_regex ~r/^(?<bank>[a-zA-Z]{4})\s*(?<country>[a-zA-Z]{2})\s*(?<location>[0-9a-zA-Z]{2})\s*(?<branch>[0-9a-zA-Z]{3})?$/
18+
19+
@doc """
20+
Parses a BIC string into its components.
21+
22+
## Examples
23+
iex> Bankster.Bic.parse("INVALIDBIC")
24+
:error
25+
26+
iex> Bankster.Bic.parse("AAAABBCC123")
27+
%Bankster.Bic{bank: "AAAA", country: "BB", location: "CC", branch: "123"}
28+
29+
iex> Bankster.Bic.parse("AAAABBCC")
30+
%Bankster.Bic{bank: "AAAA", country: "BB", location: "CC", branch: nil}
31+
32+
iex> Bankster.Bic.parse("AAAA BB CC 123")
33+
%Bankster.Bic{bank: "AAAA", country: "BB", location: "CC", branch: "123"}
34+
35+
iex> Bankster.Bic.parse("AAAA BB CC")
36+
%Bankster.Bic{bank: "AAAA", country: "BB", location: "CC", branch: nil}
37+
38+
iex> Bankster.Bic.parse("AAAA BB CC 123")
39+
%Bankster.Bic{bank: "AAAA", country: "BB", location: "CC", branch: "123"}
40+
"""
41+
@spec parse(term()) :: {:ok, t()} | :error
42+
def parse(bic) when is_binary(bic) do
43+
case Regex.named_captures(@bic_regex, bic) do
44+
nil ->
45+
:error
46+
47+
captures ->
48+
{
49+
:ok,
50+
%__MODULE__{
51+
bank: captures["bank"],
52+
country: captures["country"],
53+
location: captures["location"],
54+
branch: presence(captures["branch"])
55+
}
56+
}
57+
end
58+
end
59+
60+
def parse(_), do: :error
61+
62+
@doc """
63+
Parses a BIC string into its components.
64+
65+
Will raise `ArgumentError` if the value can not be parsed.
466
"""
67+
@spec parse!(term()) :: t()
68+
def parse!(bic) do
69+
case parse(bic) do
70+
{:ok, parsed} ->
71+
parsed
572

6-
## -- Module attributes
7-
@bic_validation_regex ~r/^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$/
73+
:error ->
74+
raise ArgumentError, message: "invalid argument bic: #{inspect(bic)}"
75+
end
76+
end
877

9-
## -- Functions
1078
@doc """
11-
Validates a given string whether it's a valid SWIFT BIC
79+
Turns a `Bankster.Bic` into a string.
80+
81+
## Options
82+
83+
* `:format` - Specifies the format to use when encoding.
84+
* `:case` - Specifies the character case when encoding.
85+
86+
The values for `:format` can be:
87+
88+
* `:compact` - No spaces between components. (default)
89+
* `:pretty` - Adds spaces between components
90+
91+
The values for `:case` can be:
92+
93+
* `:upper` - All characters are made to be upper case. (default)
94+
* `:lower` - All characters are made to be lower case.
95+
* `:leave` - Leaves the characters in the case they are in.
96+
"""
97+
@spec to_string(t()) :: binary()
98+
@spec to_string(t(), [{:format, :pretty | :compact}]) :: binary()
99+
def to_string(
100+
%__MODULE__{bank: bank, country: country, location: location, branch: branch} = bic,
101+
opts \\ []
102+
) do
103+
if bank == nil do
104+
raise ArgumentError, ":bank is required, got: #{inspect(bic)}"
105+
end
106+
107+
if country == nil do
108+
raise ArgumentError, ":country is required, got: #{inspect(bic)}"
109+
end
110+
111+
if location == nil do
112+
raise ArgumentError, ":location is required, got: #{inspect(bic)}"
113+
end
114+
115+
string =
116+
case opts[:format] do
117+
:pretty ->
118+
if branch do
119+
"#{bank} #{country} #{location} #{branch}"
120+
else
121+
"#{bank} #{country} #{location}"
122+
end
123+
124+
_ ->
125+
"#{bank}#{country}#{location}#{branch}"
126+
end
127+
128+
case opts[:case] do
129+
:leave -> string
130+
:lower -> String.downcase(string)
131+
_ -> String.upcase(string)
132+
end
133+
end
134+
135+
@doc """
136+
Validates a given string whether it's a valid SWIFT BIC.
137+
138+
> #### Note {: .info}
139+
> This ignores whitespace between the components.
12140
13141
## Examples
14142
iex> Bankster.Bic.valid?("INVALIDBIC")
15143
false
144+
145+
iex> Bankster.Bic.valid?("AAAABBCC123")
146+
true
147+
148+
iex> Bankster.Bic.valid?("AAAABBCC")
149+
true
150+
151+
iex> Bankster.Bic.valid?("AAAA BB CC 123")
152+
true
16153
"""
17154
@spec valid?(binary()) :: boolean()
18-
def valid?(bic) when is_binary(bic),
19-
do: Regex.match?(@bic_validation_regex, String.replace(bic, ~r/\s*/, ""))
155+
def valid?(bic) when is_binary(bic), do: Regex.match?(@bic_regex, bic)
156+
def valid?(_), do: false
157+
158+
defp presence(""), do: nil
159+
defp presence(val), do: val
160+
end
20161

21-
def valid?(_),
22-
do: false
162+
defimpl String.Chars, for: Bankster.Bic do
163+
defdelegate to_string(bic), to: Bankster.Bic
23164
end

test/bankster/bic_test.exs

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,150 @@
11
defmodule Bankster.Bic.BicTest do
22
use ExUnit.Case
33

4-
## -- Module constants
4+
alias Bankster.Bic
5+
56
@valid_bics [
67
"RBOSGGSX",
8+
"RBOS GG SX",
9+
"RBOS GGSX",
10+
"RBOSGG SX",
711
"RZTIAT22263",
12+
"RZTI AT 22 263",
13+
"RZTI AT 22263",
14+
"RZTI AT22263",
15+
"RZTIAT22 263",
16+
"RZTI AT 22263",
817
"BCEELULL",
918
"MARKDEFF",
1019
"GENODEF1JEV",
1120
"UBSWCHZH80A",
1221
"CEDELULLXXX"
1322
]
14-
@invalid_bics ["CE1EL2LLFFF", "E31DCLLFFF", "", " ", nil]
1523

16-
## -- Test cases
17-
test "valid?/1" do
18-
## -- VALID BICS
19-
for bic <- @valid_bics, do: assert(Bankster.Bic.valid?(bic) == true)
24+
@invalid_bics ["CE1EL2LLFFF", "E31DCLLFFF", "", " ", nil, "INVALIDBIC"]
25+
26+
describe "valid?" do
27+
test "valid bics" do
28+
for bic <- @valid_bics, do: assert(Bic.valid?(bic) == true)
29+
end
30+
31+
test "invalid bics" do
32+
for bic <- @invalid_bics, do: assert(Bic.valid?(bic) == false)
33+
end
34+
end
35+
36+
describe "parse" do
37+
test "invalid bics" do
38+
for bic <- @invalid_bics do
39+
assert :error = Bic.parse(bic)
40+
end
41+
42+
assert :error = Bic.parse(" AAAABBCC123")
43+
assert :error = Bic.parse(nil)
44+
assert :error = Bic.parse(~D[2020-01-01])
45+
end
46+
47+
test "valid bics" do
48+
for bic <- @valid_bics do
49+
assert {:ok, _} = Bic.parse(bic)
50+
end
51+
52+
assert {:ok, bic} = Bic.parse("RBOSGGSX")
53+
assert %{bank: "RBOS", country: "GG", location: "SX", branch: nil} = bic
54+
55+
assert {:ok, bic} = Bic.parse("RZTIAT22263")
56+
assert %Bic{bank: "RZTI", country: "AT", location: "22", branch: "263"} = bic
57+
58+
assert {:ok, bic} = Bic.parse("RZTIAT22 263")
59+
assert %Bic{bank: "RZTI", country: "AT", location: "22", branch: "263"} = bic
60+
61+
assert {:ok, bic} = Bic.parse("RZTIAT 22 263")
62+
assert %Bic{bank: "RZTI", country: "AT", location: "22", branch: "263"} = bic
63+
64+
assert {:ok, bic} = Bic.parse("RZTI AT22 263")
65+
assert %Bic{bank: "RZTI", country: "AT", location: "22", branch: "263"} = bic
66+
67+
assert {:ok, bic} = Bic.parse("BCEELULL")
68+
assert %Bic{bank: "BCEE", country: "LU", location: "LL", branch: nil} = bic
69+
70+
assert {:ok, bic} = Bic.parse("MARKDEFF")
71+
assert %Bic{bank: "MARK", country: "DE", location: "FF", branch: nil} = bic
72+
73+
assert {:ok, bic} = Bic.parse("MARK DE FF")
74+
assert %Bic{bank: "MARK", country: "DE", location: "FF", branch: nil} = bic
75+
76+
assert {:ok, bic} = Bic.parse("MARK DEFF")
77+
assert %Bic{bank: "MARK", country: "DE", location: "FF", branch: nil} = bic
78+
79+
assert {:ok, bic} = Bic.parse("MARKDE FF")
80+
assert %Bic{bank: "MARK", country: "DE", location: "FF", branch: nil} = bic
81+
82+
assert {:ok, bic} = Bic.parse("MARK DE FF")
83+
assert %Bic{bank: "MARK", country: "DE", location: "FF", branch: nil} = bic
84+
85+
assert {:ok, bic} = Bic.parse("markdeff")
86+
assert %Bic{bank: "mark", country: "de", location: "ff", branch: nil} = bic
87+
end
88+
end
89+
90+
describe "parse!" do
91+
test "valid bic" do
92+
assert %Bic{
93+
bank: "mark",
94+
country: "de",
95+
location: "ff",
96+
branch: nil
97+
} = Bic.parse!("markdeff")
98+
end
99+
100+
test "invalid bic raises an argument error" do
101+
assert_raise(ArgumentError, ~s(invalid argument bic: "CE1EL2LLFFF"), fn ->
102+
Bic.parse!("CE1EL2LLFFF")
103+
end)
104+
end
105+
end
106+
107+
describe "to_string" do
108+
test "formatting options" do
109+
bic11 = %Bic{bank: "AaAa", country: "bB", location: "cC", branch: "123"}
110+
bic8 = %Bic{bank: "AaAa", country: "bB", location: "cC", branch: nil}
111+
112+
assert "AAAABBCC123" == Bic.to_string(bic11)
113+
assert "AAAABBCC123" == Bic.to_string(bic11, case: :upper)
114+
assert "aaaabbcc123" == Bic.to_string(bic11, case: :lower)
115+
assert "AaAabBcC123" == Bic.to_string(bic11, case: :leave)
116+
assert "AAAABBCC123" == Bic.to_string(bic11, case: :upper, format: :compact)
117+
assert "aaaabbcc123" == Bic.to_string(bic11, case: :lower, format: :compact)
118+
assert "AaAabBcC123" == Bic.to_string(bic11, case: :leave, format: :compact)
119+
assert "AAAA BB CC 123" == Bic.to_string(bic11, case: :upper, format: :pretty)
120+
assert "aaaa bb cc 123" == Bic.to_string(bic11, case: :lower, format: :pretty)
121+
assert "AaAa bB cC 123" == Bic.to_string(bic11, case: :leave, format: :pretty)
122+
123+
assert "AAAABBCC" == Bic.to_string(bic8)
124+
assert "AAAABBCC" == Bic.to_string(bic8, case: :upper)
125+
assert "aaaabbcc" == Bic.to_string(bic8, case: :lower)
126+
assert "AaAabBcC" == Bic.to_string(bic8, case: :leave)
127+
assert "AAAABBCC" == Bic.to_string(bic8, case: :upper, format: :compact)
128+
assert "aaaabbcc" == Bic.to_string(bic8, case: :lower, format: :compact)
129+
assert "AaAabBcC" == Bic.to_string(bic8, case: :leave, format: :compact)
130+
assert "AAAA BB CC" == Bic.to_string(bic8, case: :upper, format: :pretty)
131+
assert "aaaa bb cc" == Bic.to_string(bic8, case: :lower, format: :pretty)
132+
assert "AaAa bB cC" == Bic.to_string(bic8, case: :leave, format: :pretty)
133+
134+
assert "AAAABBCC123" == String.Chars.to_string(bic11)
135+
assert "AAAABBCC" == String.Chars.to_string(bic8)
136+
137+
assert_raise(ArgumentError, ~r/:bank is required, got:/, fn ->
138+
String.Chars.to_string(%Bic{bank: nil, country: "BB", location: "CC", branch: "123"})
139+
end)
140+
141+
assert_raise(ArgumentError, ~r/:country is required, got:/, fn ->
142+
String.Chars.to_string(%Bic{bank: "AAAA", country: nil, location: "CC", branch: "123"})
143+
end)
20144

21-
## -- INVALID BICS
22-
for bic <- @invalid_bics, do: assert(Bankster.Bic.valid?(bic) == false)
145+
assert_raise(ArgumentError, ~r/:location is required, got:/, fn ->
146+
String.Chars.to_string(%Bic{bank: "AAAA", country: "BB", location: nil, branch: "123"})
147+
end)
148+
end
23149
end
24150
end

0 commit comments

Comments
 (0)