Packages
maru
0.9.5
0.14.0-pre.1
0.13.2
0.13.1
0.13.0
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
REST-like API micro-framework for elixir inspired by grape.
Current section
Files
Jump to
Current section
Files
lib/maru/router/endpoint.ex
defmodule Maru.Router.Endpoint do
@moduledoc """
Generate endpoint of maru router.
"""
alias Maru.Router.Param
alias Maru.Router.Validator
[ method: nil, path: [], version: nil, desc: nil, param_context: [], block: nil, helpers: []
] |> defstruct
@doc """
Generate general endpoint defined by user within method block.
"""
def dispatch(ep) do
path = ep.path |> Enum.map(fn x when is_atom(x) -> Macro.var(:_, nil); x -> x end)
version =
case ep.version do
nil -> Macro.var(:_, nil)
v -> v
end
params_block = quote do
var!(conn) = var!(conn) |> Plug.Conn.put_private(:maru_params,
Maru.Router.Endpoint.validate_params(
Enum.concat(var!(conn).private.maru_param_context, unquote(ep.param_context |> Macro.escape)),
var!(conn).params,
Maru.Router.Path.parse_params(
var!(conn).path_info,
var!(conn).private.maru_route_path ++ unquote(ep.path)
)
)
)
end
quote do
defp endpoint(%Plug.Conn{
method: unquote(ep.method),
private: %{
maru_resource_path: unquote(path),
maru_version: unquote(version),
}
}=var!(conn), []) do
unquote(params_block)
unquote(ep.block)
end
end
end
@doc """
Generate MethodNotAllow endpoint for all path without `match` method.
"""
def dispatch_405(version, path) do
method = Macro.var(:_, nil)
path = path |> Enum.map(fn x when is_atom(x) -> Macro.var(:_, nil); x -> x end)
version =
case version do
nil -> Macro.var(:_, nil)
v -> v
end
quote do
defp endpoint(%Plug.Conn{
method: unquote(method),
private: %{
maru_resource_path: unquote(path),
maru_version: unquote(version),
}
}=var!(conn), []) do
Maru.Exceptions.MethodNotAllow |> raise([method: var!(conn).method, request_path: var!(conn).request_path])
end
end
end
@doc """
Parse and validate params.
"""
def validate_params([], _params, result), do: result
def validate_params([%Validator{action: action, attr_names: attr_names}|t], params, result) do
validator =
try do
[ Maru.Validations,
action |> Atom.to_string |> Maru.Utils.upper_camel_case
] |> Module.safe_concat
rescue
ArgumentError ->
Maru.Exceptions.UndefinedValidator |> raise([param: attr_names, validator: action])
end
validator.validate!(attr_names, result)
validate_params(t, params, result)
end
def validate_params([%Param{attr_name: attr_name, source: source, coerce_with: coercer, children: []}=p|t], params, result) do
attr_value =
pickup([
result |> Map.get(attr_name),
params |> Map.get(source || attr_name |> to_string) |> Maru.Coercer.parse(coercer),
p.default
])
case {attr_value, p.required} do
{nil, false} -> validate_params(t, params, result)
{nil, true} -> Maru.Exceptions.InvalidFormatter
|> raise([reason: :required, param: attr_name, option: p])
{value, _} ->
value = check_param(attr_name, value, p)
validate_params(t, params, put_in(result, [attr_name], value))
end
end
def validate_params([%Param{attr_name: attr_name, source: source, coerce_with: coercer, children: children, parser: :map}=p|t], params, result) do
nested_params = params |> Map.get(source || attr_name |> to_string) |> Maru.Coercer.parse(coercer)
case {is_nil(nested_params), p.required} do
{true, true} ->
Maru.Exceptions.InvalidFormatter |> raise([reason: :required, param: attr_name, option: p])
{true, false} ->
validate_params(t, params, result)
{false, _} ->
value = validate_params(children, nested_params, %{})
validate_params(t, params, put_in(result, [attr_name], value))
end
end
def validate_params([%Param{attr_name: attr_name, source: source, coerce_with: coercer, children: children, parser: :list}=p|t], params, result) do
nested_params = params |> Map.get(source || attr_name |> to_string) |> Maru.Coercer.parse(coercer)
case {is_nil(nested_params), p.required} do
{true, true} ->
Maru.Exceptions.InvalidFormatter |> raise([reason: :required, param: attr_name, option: p])
{true, false} ->
validate_params(t, params, result)
{false, _} ->
value = nested_params |> Enum.map(&validate_params(children, &1, %{}))
validate_params(t, params, put_in(result, [attr_name], value))
end
end
defp pickup([e]), do: e
defp pickup([h | t]) do
if is_nil(h) do
pickup(t)
else
h
end
end
defp check_param(attr_name, value, param_context) do
validators = param_context.validators
value =
try do
Maru.ParamType.parse(value, param_context.parser)
rescue
ArgumentError ->
Maru.Exceptions.InvalidFormatter |> raise([reason: :illegal, param: attr_name, value: value, option: validators])
end
do_check_param(validators, attr_name, value)
end
defp do_check_param([], _attr_name, value), do: value
defp do_check_param([{validator, option}|t], attr_name, value) do
validator = try do
[ Maru.Validations,
validator |> Atom.to_string |> Maru.Utils.upper_camel_case
] |> Module.safe_concat
rescue
ArgumentError ->
Maru.Exceptions.UndefinedValidator |> raise([param: attr_name, validator: validator])
end
validator.validate_param!(attr_name, value, option)
do_check_param(t, attr_name, value)
end
end