Packages
jsv
0.13.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
retired
0.11.2
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
A JSON Schema Validator with complete support for the latest specifications.
Current section
Files
Jump to
Current section
Files
lib/jsv/debanger.ex
defmodule JSV.Debanger do
@moduledoc false
defmacro __using__(opts) do
quote do
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
@__debang_nowrap_records unquote(List.wrap(opts[:records]))
Module.register_attribute(__MODULE__, :__debang_bang_funs, accumulate: true)
end
end
defmacro debang(call) do
{:def, meta, [{fun, _, args}]} = call
quote do
@__debang_bang_funs {unquote(fun), unquote(meta), unquote(Macro.escape(args)),
Module.get_attribute(__MODULE__, :doc_group)}
unquote(call)
end
end
defmacro __before_compile__(env) do
bang_funs = Module.get_attribute(env.module, :__debang_bang_funs)
specs = Module.get_attribute(env.module, :spec)
quoted_unbanged =
Enum.map(bang_funs, fn {bang_fun, bang_meta, args, doc_group} ->
{:spec, {:"::", _, [{^bang_fun, _, arg_types}, return_type]}, _} = find_spec!(specs, bang_fun)
args_no_defaults = args_no_defaults(args)
tuple_return_type = return_type_to_tuple_type(return_type)
nobang_fun = debang_atom(bang_fun)
quoted =
quote do
@doc """
Same as `#{unquote(bang_fun)}/#{unquote(length(args))}` but rescues
errors and returns a result tuple.
"""
@spec unquote(nobang_fun)(unquote_splicing(arg_types)) :: unquote(tuple_return_type)
if doc_group = unquote(doc_group) do
@doc group: doc_group
end
def unquote(nobang_fun)(unquote_splicing(args)) do
__debang_wrap__(unquote(bang_fun)(unquote_splicing(args_no_defaults)))
rescue
e -> {:error, e}
end
end
Macro.postwalk(quoted, fn
node -> Macro.update_meta(node, &Keyword.merge(bang_meta, &1))
end)
end)
quote generated: true do
unquote(quoted_unbanged)
if @__debang_nowrap_records != [] do
defp __debang_wrap__(value) when is_tuple(value) and elem(value, 0) in @__debang_nowrap_records do
{:ok, value}
end
end
defp __debang_wrap__(value) do
unquote(__MODULE__).__wrap__(value)
end
end
end
defp find_spec!(specs, bang_fun) do
spec =
Enum.find_value(specs, fn
{:spec, {:"::", _, [{^bang_fun, _, _}, _]}, _} = spec -> spec
_ -> nil
end)
if spec == nil do
raise "could not debang fun #{inspect(bang_fun)}, no spec found"
end
spec
end
defp return_type_to_tuple_type(return_type) do
case return_type do
{:{}, meta, tuple_vals} ->
{:|, meta,
[
{:{}, meta, [:ok | tuple_vals]},
{:error,
quote do
Exception.t()
end}
]}
# 2-tuples are not quoted
{t1, t2} ->
{:|, [],
[
{:{}, [], [:ok, t1, t2]},
{:error, {{:., [], [:"Elixir.Exception", :t]}, [], []}}
]}
raw_type ->
{:|, [],
[
{:ok, raw_type},
{:error, {{:., [], [:"Elixir.Exception", :t]}, [], []}}
]}
end
end
defp debang_atom(atom) do
atom
|> Atom.to_string()
|> tap(&(true = String.ends_with?(&1, "!")))
|> String.trim_trailing("!")
|> String.to_atom()
end
defp args_no_defaults(args) do
Macro.prewalk(args, fn
{:\\, _, [var, _default]} -> var
ast -> ast
end)
end
@spec __wrap__(term) :: tuple
def __wrap__(value) do
case value do
{a, b} -> {:ok, a, b}
{a, b, c} -> {:ok, a, b, c}
{a, b, c, d} -> {:ok, a, b, c, d}
{a, b, c, d, e} -> {:ok, a, b, c, d, e}
v when not is_tuple(v) -> {:ok, v}
end
end
end