Packages
jsv
0.17.1
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/schema/helper_compiler.ex
defmodule JSV.Schema.HelperCompiler do
alias JSV.Schema
@moduledoc false
defguard is_literal(v) when is_atom(v) or is_integer(v) or (is_tuple(v) and is_list(elem(v, 2)))
defp extract_guard(args_or_guarded) do
case args_or_guarded do
{:when, _meta, [args, guard]} -> {guard, args}
args -> {true, args}
end
end
defp expand_args(args, env) do
Enum.map(args, fn {prop, value} ->
value = Macro.expand(value, env)
if Macro.quoted_literal?(value) do
{:const, prop, value}
else
expand_expression(prop, value)
end
end)
end
defp expand_expression(prop, value) do
case value do
{:<-, _, [expr, value]} ->
{bind, typespec} = extract_typespec(value)
{:var, prop, bind, typespec, expr}
{{:., _, _}, _, _} = remote_call ->
{:call, prop, remote_call}
value ->
{bind, typespec} = extract_typespec(value)
{:var, prop, bind, typespec, bind}
end
end
defp extract_typespec(value) do
case value do
{:"::", _, [bind, typespec]} -> {bind, typespec}
bind -> {bind, {:term, [], nil}}
end
end
defp prepare_fun(args) do
schema_props =
Enum.map(args, fn
{:const, prop, const} -> {prop, const}
{:var, prop, _bind, _typespec, expr} -> {prop, expr}
{:call, prop, call} -> {prop, call}
end)
bindings =
Enum.flat_map(args, fn
{:const, _prop, _const} -> []
{:var, _prop, bind, _typespec, _expr} -> [bind]
{:call, _, _} -> []
end)
typespecs =
Enum.flat_map(args, fn
{:const, _prop, _const} -> []
{:var, _prop, _bind, typespec, _expr} -> [typespec]
{:call, _, _} -> []
end)
doc_bindings =
Enum.map(args, fn
{:const, prop, const} -> {prop, inspect(const)}
{:var, prop, bind, _typespec, _expr} -> {:var, {prop, Macro.to_string(bind)}}
{:call, prop, call} -> {prop, Macro.to_string(call)}
end)
{schema_props, bindings, typespecs, doc_bindings}
end
defmacro defcompose(fun, args_or_guarded) do
{guard, args} = extract_guard(args_or_guarded)
args = expand_args(args, __CALLER__)
{schema_props, bindings, typespecs, doc_bindings} = prepare_fun(args)
# Start of quote
quote location: :keep do
doc_custom =
case Module.get_attribute(__MODULE__, :doc) do
{_, text} when is_binary(text) -> ["\n\n", text]
_ -> ""
end
doc_schema_props =
unquote(doc_bindings)
|> Enum.map(fn
{:var, {prop, varname}} -> "`#{prop}: #{varname}`"
{prop, value_or_call} -> "`#{prop}: #{value_or_call}`"
end)
|> :lists.reverse()
|> case do
[last | [_ | _] = prev] ->
prev
|> Enum.intersperse(", ")
|> :lists.reverse([" and ", last])
[single] ->
[single]
end
@doc """
Defines or merges onto a JSON Schema with #{doc_schema_props}.#{doc_custom}
"""
@spec unquote(fun)(Schema.merge_base(), unquote_splicing(typespecs)) :: Schema.schema()
def unquote(fun)(merge_base \\ nil, unquote_splicing(bindings)) when unquote(guard) do
Schema.merge(merge_base, unquote(schema_props))
end
end
end
defmacro defcompose_deprecated(fun, arity) do
args = Macro.generate_arguments(arity, __MODULE__)
quote do
if Mix.env() != :test do
@deprecated "Use `JSV.Schema.Composer.#{unquote(fun)}/#{unquote(arity) + 1}`. " <>
"More information: https://hexdocs.pm/jsv/api-changes-v0-9.html"
end
@doc false
def unquote(fun)(base \\ nil, unquote_splicing(args)) do
JSV.Schema.Composer.unquote(fun)(base, unquote_splicing(args))
end
end
end
defmacro defpreset(fun, args_or_guarded) do
{guard, args} = extract_guard(args_or_guarded)
args = expand_args(args, __CALLER__)
{schema_props, bindings, typespecs, doc_bindings} = prepare_fun(args)
# Start of quote
quote location: :keep do
doc_custom =
case Module.get_attribute(__MODULE__, :doc) do
{_, text} when is_binary(text) -> ["\n\n", text]
_ -> ""
end
doc_schema_props =
unquote(doc_bindings)
|> Enum.map(fn
{:var, {prop, varname}} -> "`#{prop}: #{varname}`"
{prop, value_or_call} -> "`#{prop}: #{value_or_call}`"
end)
|> :lists.reverse()
|> case do
[last | [_ | _] = prev] ->
prev
|> Enum.intersperse(", ")
|> :lists.reverse([" and ", last])
[single] ->
[single]
end
@doc """
Returns a JSON Schema with #{doc_schema_props}.#{doc_custom}
"""
@doc group: "Schema Presets"
@spec unquote(fun)(unquote_splicing(typespecs), Schema.attributes() | nil) :: Schema.schema()
def unquote(fun)(unquote_splicing(bindings), extra \\ nil) when unquote(guard) do
case extra do
nil -> Map.new(unquote(schema_props))
_ -> Schema.combine(extra, unquote(schema_props))
end
end
end
end
end