Current section

Files

Jump to
ham lib ham.ex
Raw

lib/ham.ex

defmodule Ham do
@moduledoc """
Ham is a library for rigorous typespec checking for module functions including
typespecs from implemented behaviours
"""
@doc """
Apply a module function validating arguments and return value against typespecs
iex> Ham.apply(URI, :decode, ["https%3A%2F%2Felixir-lang.org"])
"https://elixir-lang.org"
iex> Ham.apply(URI, :char_reserved?, ["a"])
** (Ham.TypeMatchError) 1st argument value "a" does not match 1st parameter's type byte().
Value "a" does not match type 0..255.
"""
@spec apply(module, atom, [any], Keyword.t()) :: any
def apply(module, function_name, args, opts \\ []) do
return_value = Kernel.apply(module, function_name, args)
validate!(module, function_name, args, return_value, opts)
return_value
end
@doc """
Handy macro to apply a module function validating arguments and return value against typespecs
iex> Ham.apply(URI.decode("https%3A%2F%2Felixir-lang.org"))
"https://elixir-lang.org"
iex> Ham.apply(URI.char_reserved?("a"))
** (Ham.TypeMatchError) 1st argument value "a" does not match 1st parameter's type byte().
Value "a" does not match type 0..255.
"""
defmacro apply(call, opts \\ []) do
case Macro.decompose_call(call) do
:error ->
raise ArgumentError, "Invalid call"
{module, function_name, args} ->
quote bind_quoted: [module: module, function_name: function_name, args: args, opts: opts] do
Ham.apply(module, function_name, args, opts)
end
end
end
@doc """
Validate arguments and return value against typespecs.
iex> Ham.validate(URI, :char_reserved?, [?a], true)
:ok
"""
@spec validate(module, atom, [any], any, Keyword.t()) :: :ok | {:error, Ham.TypeMatchError.t()}
def validate(module, function_name, args, return_value, opts \\ []) do
behaviours = Keyword.get(opts, :behaviours, [])
Ham.TypeChecker.validate(module, function_name, args, return_value, behaviours)
end
@doc """
Validate arguments and return value against typespecs
iex> Ham.validate!(URI, :char_reserved?, ["a"], true)
** (Ham.TypeMatchError) 1st argument value "a" does not match 1st parameter's type byte().
Value "a" does not match type 0..255.
"""
@spec validate!(module, atom, [any], any, Keyword.t()) :: :ok | no_return
def validate!(module, function_name, args, return_value, opts \\ []) do
case validate(module, function_name, args, return_value, opts) do
:ok -> :ok
{:error, error} -> raise error
end
end
end