/
inet.ex
131 lines (102 loc) · 3.22 KB
/
inet.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
defmodule EctoNetwork.INET do
@moduledoc ~S"""
Support for using Ecto with :inet fields
"""
@behaviour Ecto.Type
def type, do: :inet
@doc "Handle embedding format for CIDR records."
def embed_as(_), do: :self
@doc "Handle equality testing for CIDR records."
def equal?(left, right), do: left == right
@doc "Handle casting to Postgrex.INET"
def cast(%Postgrex.INET{} = address), do: {:ok, address}
def cast(address) when is_tuple(address),
do: cast(%Postgrex.INET{address: address, netmask: address_netmask(address)})
def cast(address) when is_binary(address) do
{address, netmask} =
case String.split(address, "/") do
[address] -> {address, nil}
[address, netmask] -> {address, netmask}
[address, netmask | _] -> {address, netmask}
end
parsed_address =
address
|> String.trim()
|> String.to_charlist()
|> :inet.parse_address()
parsed_netmask = cast_netmask(netmask, parsed_address)
case [parsed_address, parsed_netmask] do
[_address_result, :error] ->
:error
[{:ok, address}, {netmask, ""}] ->
{:ok, %Postgrex.INET{address: address, netmask: netmask}}
_ ->
:error
end
end
def cast(_), do: :error
@doc "Load from the native Ecto representation"
def load(%Postgrex.INET{} = inet) do
inet =
cond do
address_netmask(inet.address, inet.netmask) -> inet
address_netmask(inet.address) -> %{inet | netmask: address_netmask(inet.address)}
true -> %{inet | netmask: nil}
end
{:ok, inet}
end
def load(_), do: :error
@doc "Convert to the native Ecto representation"
def dump(%Postgrex.INET{} = inet) do
inet =
if inet.netmask do
inet
else
%{inet | netmask: address_netmask(inet.address)}
end
{:ok, inet}
end
def dump(_), do: :error
@doc "Convert from native Ecto representation to a binary"
def decode(%Postgrex.INET{address: address, netmask: netmask}) do
netmask = address_netmask(address, netmask)
address
|> :inet.ntoa()
|> case do
{:error, _} ->
:error
address ->
address = List.to_string(address)
if netmask, do: "#{address}/#{netmask}", else: address
end
end
defp address_netmask(address) do
if(tuple_size(address) == 4, do: 32, else: 128)
end
defp address_netmask(address, netmask) do
cond do
tuple_size(address) == 4 && netmask == 32 -> nil
tuple_size(address) == 8 && netmask == 128 -> nil
true -> netmask
end
end
defp cast_netmask(mask, _address) when is_binary(mask) do
mask
|> String.trim()
|> Integer.parse()
end
# For addresses without a netmask, use the default (full) mask.
# Returns same structure as a successful Integer.parse()
defp cast_netmask(nil, {:ok, address}) do
{address_netmask(address), ""}
end
defp cast_netmask(_mask, _address), do: :error
end
defimpl String.Chars, for: Postgrex.INET do
def to_string(%Postgrex.INET{} = address), do: EctoNetwork.INET.decode(address)
end
if Code.ensure_loaded?(Phoenix.HTML) do
defimpl Phoenix.HTML.Safe, for: Postgrex.INET do
def to_iodata(%Postgrex.INET{} = address), do: EctoNetwork.INET.decode(address)
end
end