From 3c5f964a9ea21eca6d25e8b5157d8ee6c2194d2b Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 25 Oct 2017 16:20:39 +1300 Subject: [PATCH] Improve typespecs and run dialyzer. --- lib/ip/address.ex | 43 +++++++++++++++++++++++++---------------- lib/ip/prefix.ex | 38 ++++++++++++++++++++++-------------- lib/ip/prefix/parser.ex | 4 ++-- mix.exs | 3 ++- mix.lock | 1 + 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/lib/ip/address.ex b/lib/ip/address.ex index 4ef3cde..c3ff2b4 100644 --- a/lib/ip/address.ex +++ b/lib/ip/address.ex @@ -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 """ - @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), diff --git a/lib/ip/prefix.ex b/lib/ip/prefix.ex index b486beb..bfdda0e 100644 --- a/lib/ip/prefix.ex +++ b/lib/ip/prefix.ex @@ -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 diff --git a/lib/ip/prefix/parser.ex b/lib/ip/prefix/parser.ex index 27a21dd..400f494 100644 --- a/lib/ip/prefix/parser.ex +++ b/lib/ip/prefix/parser.ex @@ -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), diff --git a/mix.exs b/mix.exs index 6f56f40..4ecd500 100644 --- a/mix.exs +++ b/mix.exs @@ -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 diff --git a/mix.lock b/mix.lock index 36df49a..1a33fb8 100644 --- a/mix.lock +++ b/mix.lock @@ -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]}]},