Improve typespecs and run dialyzer.

This commit is contained in:
James Harton 2017-10-25 16:20:39 +13:00
parent a0381549a4
commit 3c5f964a9e
5 changed files with 55 additions and 34 deletions

View file

@ -9,10 +9,20 @@ defmodule IP.Address do
Simple representations of IP Addresses.
"""
@type t :: %Address{}
@typedoc "Valid IPv4 address - integer between zero and 32 ones."
@type ipv4 :: 0..0xffffffff
@typedoc "Valid IPv6 address - integer between zero and 128 ones."
@type ipv6 :: 0..0xffffffffffffffffffffffffffffffff
@type ip_version :: 4 | 6
@typedoc "Valid IP address"
@type ip :: ipv4 | ipv6
@typedoc "Valid IP version (currently only 4 and 6 are deployed in the wild)."
@type version :: 4 | 6
@typedoc "IP address struct type, contains a valid address and version."
@type t :: %Address{address: ip, version: version}
@doc """
Convert from (packed) binary representations (either 32 or 128 bits long) into an address.
@ -71,7 +81,7 @@ defmodule IP.Address do
...> |> IP.Address.from_integer(6)
{:ok, %IP.Address{address: 42540766411282592856903984951653826561, version: 6}}
"""
@spec from_integer(ipv4 | ipv6, ip_version) :: {:ok, t} | {:error, term}
@spec from_integer(ip, version) :: {:ok, t} | {:error, term}
def from_integer(address, 4) when valid_ipv4_integer?(address) do
{:ok, %Address{address: address, version: 4}}
end
@ -98,7 +108,7 @@ defmodule IP.Address do
...> |> IP.Address.from_integer!(6)
%IP.Address{address: 42540766411282592856903984951653826561, version: 6}
"""
@spec from_integer!(ipv4 | ipv6, ip_version) :: t
@spec from_integer!(ip, version) :: t
def from_integer!(address, version) do
case from_integer(address, version) do
{:ok, address} -> address
@ -169,7 +179,7 @@ defmodule IP.Address do
...> |> IP.Address.from_string(6)
{:ok, %IP.Address{address: 42540766411282592856903984951653826561, version: 6}}
"""
@spec from_string(binary, ip_version) :: {:ok, t} | {:error, term}
@spec from_string(binary, version) :: {:ok, t} | {:error, term}
def from_string(address, 4) when is_binary(address) do
case :inet.parse_ipv4strict_address(String.to_charlist(address)) do
{:ok, addr} ->
@ -210,7 +220,7 @@ defmodule IP.Address do
...> |> IP.Address.from_string!(6)
%IP.Address{address: 42540766411282592856903984951653826561, version: 6}
"""
@spec from_string!(binary, ip_version) :: t
@spec from_string!(binary, version) :: t
def from_string!(address, version) do
case from_string(address, version) do
{:ok, address} -> address
@ -265,8 +275,7 @@ defmodule IP.Address do
...> |> IP.Address.to_prefix(32)
#IP.Prefix<192.0.2.1/32 DOCUMENTATION>
"""
@spec to_prefix(t, Prefix.ipv4_prefix_length | Prefix.ipv6_prefix_length) \
:: Prefix.t
@spec to_prefix(t, Prefix.prefix_length) :: Prefix.t
def to_prefix(%Address{} = address, length), do: Prefix.new(address, length)
@doc """
@ -282,7 +291,7 @@ defmodule IP.Address do
...> |> IP.Address.version()
6
"""
@spec version(t) :: 4 | 6
@spec version(t) :: version
def version(%Address{version: version}), do: version
@doc """
@ -298,7 +307,7 @@ defmodule IP.Address do
...> |> IP.Address.to_integer()
42540766411282592856903984951653826561
"""
@spec to_integer(t) :: ipv4 | ipv6
@spec to_integer(t) :: ip
def to_integer(%Address{address: address}), do: address
@doc """
@ -314,7 +323,7 @@ defmodule IP.Address do
...> |> IP.Address.v6?
true
"""
@spec v6?(t) :: true | false
@spec v6?(t) :: boolean
def v6?(%Address{version: 6} = _address), do: true
def v6?(_address), do: false
@ -331,7 +340,7 @@ defmodule IP.Address do
...> |> IP.Address.v4?
false
"""
@spec v4?(t) :: true | false
@spec v4?(t) :: boolean
def v4?(%Address{version: 4} = _address), do: true
def v4?(_address), do: false
@ -344,7 +353,7 @@ defmodule IP.Address do
...> |> IP.Address.eui_64?()
true
"""
@spec eui_64?(t) :: true | false
@spec eui_64?(t) :: boolean
def eui_64?(%Address{address: address, version: 6})
when (address &&& 0x20000fffe000000) == 0x20000fffe000000,
do: true
@ -360,7 +369,7 @@ defmodule IP.Address do
...> |> IP.Address.eui_64_mac()
{:ok, "60f8.1dad.d890"}
"""
@spec eui_64_mac(t) :: binary
@spec eui_64_mac(t) :: {:ok, binary} | {:error, term}
def eui_64_mac(%Address{address: address, version: 6})
when (address &&& 0x20000fffe000000) == 0x20000fffe000000
do
@ -407,7 +416,7 @@ defmodule IP.Address do
...> |> IP.Address.is_6to4?()
false
"""
@spec is_6to4?(t) :: true | false
@spec is_6to4?(t) :: boolean
def is_6to4?(%Address{address: address, version: 6})
when (address >>> 112) == 0x2002, do: true
@ -446,7 +455,7 @@ defmodule IP.Address do
...> |> IP.Address.is_teredo?()
true
"""
@spec is_teredo?(t) :: true | false
@spec is_teredo?(t) :: boolean
def is_teredo?(%Address{address: address, version: 6})
when (address >>> 96) == 0x20010000, do: true
@ -497,7 +506,7 @@ defmodule IP.Address do
iex> IP.Address.generate_ula("60:f8:1d:ad:d8:90")
#IP.Address<fd29:f1ef:86a1::>
"""
@spec generate_ula(binary, non_neg_integer, true | false) :: \
@spec generate_ula(binary, non_neg_integer, boolean) :: \
{:ok, t} | {:error, term}
def generate_ula(mac, subnet_id \\ 0, locally_assigned \\ true) do
with {:ok, address} <- ULA.generate(mac, subnet_id, locally_assigned),

View file

@ -9,13 +9,21 @@ defmodule IP.Prefix do
Defines an IP prefix, otherwise known as a subnet.
"""
@type t :: %Prefix{}
@type ipv4_prefix_length :: 0..32
@type ipv6_prefix_length :: 0..128
@ipv4_mask 0xffffffff
@ipv6_mask 0xffffffffffffffffffffffffffffffff
@typedoc "Valid IPv4 prefix lengths from 0 to 32."
@type ipv4_prefix_length :: 0..32
@typedoc "Valid IPv6 prefix lengths from 0 to 128."
@type ipv6_prefix_length :: 0..128
@typedoc "Valid IP prefix length."
@type prefix_length :: ipv4_prefix_length | ipv6_prefix_length
@typedoc "The main prefix type, contains an address and a mask value."
@type t :: %Prefix{address: Address.t, mask: Address.ip}
@doc """
Create an IP prefix from an `IP.Address` and `length`.
@ -27,7 +35,7 @@ defmodule IP.Prefix do
iex> IP.Prefix.new(~i(2001:db8::1), 64)
#IP.Prefix<2001:db8::/64 DOCUMENTATION>
"""
@spec new(Address.t, ipv4_prefix_length | ipv6_prefix_length) :: t
@spec new(Address.t, prefix_length) :: t
def new(%Address{address: address, version: 4}, length) when length >= 0 and length <= 32 do
mask = calculate_mask_from_length(length, 32)
%Prefix{address: Address.from_integer!(address, 4), mask: mask}
@ -82,7 +90,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.from_string(4)
{:error, "Error parsing IPv4 prefix"}
"""
@spec from_string(binary, 4 | 6) :: {:ok, t} | {:error, term}
@spec from_string(binary, Address.version) :: {:ok, t} | {:error, term}
def from_string(prefix, version), do: Parser.parse(prefix, version)
@doc """
@ -125,7 +133,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.from_string!(4)
#IP.Prefix<192.0.2.0/24 DOCUMENTATION>
"""
@spec from_string!(binary, 4 | 6) :: t
@spec from_string!(binary, Address.version) :: t
def from_string!(prefix, version) do
case from_string(prefix, version) do
{:ok, prefix} -> prefix
@ -142,7 +150,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.length()
24
"""
@spec length(t) :: ipv4_prefix_length | ipv6_prefix_length
@spec length(t) :: prefix_length
def length(%Prefix{mask: mask}), do: calculate_length_from_mask(mask)
@doc """
@ -154,7 +162,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.length(25)
#IP.Prefix<192.0.2.0/25 DOCUMENTATION>
"""
@spec length(t, ipv4_prefix_length | ipv6_prefix_length) :: t
@spec length(t, prefix_length) :: t
def length(%Prefix{address: %Address{version: 4}} = prefix, length)
when is_number(length) and length >= 0 and length <= 32
do
@ -176,7 +184,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.mask()
0b11111111111111111111111100000000
"""
@spec mask(t) :: non_neg_integer
@spec mask(t) :: Address.ip
def mask(%Prefix{mask: mask}), do: mask
@doc """
@ -188,7 +196,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.subnet_mask()
#IP.Address<255.255.255.0 RESERVED>
"""
@spec subnet_mask(t) :: binary
@spec subnet_mask(t) :: Address.t
def subnet_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
mask
|> Address.from_integer!(4)
@ -203,7 +211,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.wildcard_mask()
#IP.Address<0.0.0.255 CURRENT NETWORK>
"""
@spec wildcard_mask(t) :: binary
@spec wildcard_mask(t) :: Address.t
def wildcard_mask(%Prefix{mask: mask, address: %Address{version: 4}}) do
mask
|> bnot()
@ -242,6 +250,7 @@ defmodule IP.Prefix do
...> |> IP.Prefix.last()
#IP.Address<2001:db8::ffff:ffff:ffff:ffff DOCUMENTATION>
"""
@spec last(t) :: Address.t
def last(%Prefix{address: %Address{address: address, version: 4}, mask: mask}) do
Address.from_integer!(highest_address(address, mask, 4), 4)
end
@ -278,6 +287,7 @@ defmodule IP.Prefix do
true
"""
@spec contains_prefix?(t, Address.t) :: boolean
def contains_address?(%Prefix{address: %Address{address: addr0, version: 4}, mask: mask} = _prefix,
%Address{address: addr1, version: 4} = _address)
when lowest_address(addr0, mask) <= addr1
@ -323,7 +333,7 @@ defmodule IP.Prefix do
false
"""
@spec contains_prefix?(t, t) :: true | false
@spec contains_prefix?(t, t) :: boolean
def contains_prefix?(%Prefix{address: %Address{address: oaddr, version: 4}, mask: omask} = _outside,
%Prefix{address: %Address{address: iaddr, version: 4}, mask: imask} = _inside)
when lowest_address(oaddr, omask) <= lowest_address(iaddr, imask)
@ -354,7 +364,7 @@ defmodule IP.Prefix do
...> |> inspect()
"{:ok, #IP.Address<2001:db8::62f8:1dff:fead:d890 DOCUMENTATION>}"
"""
@spec eui_64(t, binary) :: Address.t
@spec eui_64(t, binary) :: {:ok, Address.t} | {:error, term}
def eui_64(%Prefix{address: %Address{version: 6},
mask: 0xffffffffffffffff0000000000000000} = prefix, mac)
do

View file

@ -22,7 +22,7 @@ defmodule IP.Prefix.Parser do
...> |> inspect()
"{:ok, #IP.Prefix<2001:db8::/64 DOCUMENTATION>}"
"""
@spec parse(binary) :: Prefix.t
@spec parse(binary) :: {:ok, Prefix.t} | {:error, term}
def parse(prefix) do
case parse(prefix, 4) do
{:ok, prefix} -> {:ok, prefix}
@ -49,7 +49,7 @@ defmodule IP.Prefix.Parser do
...> |> inspect()
"{:ok, #IP.Prefix<2001:db8::/64 DOCUMENTATION>}"
"""
@spec parse(binary, 4 | 6) :: Prefix.t
@spec parse(binary, Address.version) :: {:ok, Prefix.t} | {:error, term}
def parse(prefix, 4 = _version) do
with {:ok, address, mask} <- ensure_contains_slash(prefix),
{:ok, address} <- Address.from_string(address, 4),

View file

@ -41,7 +41,8 @@ defmodule IP.Mixfile do
{:ex_doc, ">= 0.0.0", only: :dev},
{:earmark, ">= 0.0.0", only: :dev},
{:credo, "~> 0.6", only: ~w(dev test)a, runtime: false},
{:inch_ex, "~> 0.5", only: ~w(dev test)a, runtime: false}
{:inch_ex, "~> 0.5", only: ~w(dev test)a, runtime: false},
{:dialyxir, "~> 0.5", only: ~w(dev test)a, runtime: false}
]
end
end

View file

@ -1,5 +1,6 @@
%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"credo": {:hex, :credo, "0.8.7", "b1aad9cd3aa7acdbaea49765bfc9f1605dc4555023a037dc9ea7a70539615bc8", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.17.1", "39f777415e769992e6732d9589dc5846ea587f01412241f4a774664c746affbb", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},