Current section
Files
Jump to
Current section
Files
lib/web/router/reverse.ex
# Copyright(c) 2015-2018 ACCESS CO., LTD. All rights reserved.
use Croma
defmodule Antikythera.Router.Reverse do
@moduledoc """
Internal helper functions for macros in `Antikythera.Router`, to generate/implement reverse routing functions.
"""
@typep placeholder_filler :: String.t | [String.t]
defun define_path_helper(path_name :: atom, path :: String.t) :: Macro.t do
quote bind_quoted: [path_name: path_name, path: path] do
{placeholder_vars, placeholder_types} =
String.split(path, "/", trim: true)
|> Enum.map(fn
":" <> name -> {Macro.var(String.to_atom(name), __MODULE__), quote do: String.t}
"*" <> name -> {Macro.var(String.to_atom(name), __MODULE__), quote do: [String.t]}
_ -> nil
end)
|> Enum.reject(&is_nil/1)
|> Enum.unzip()
fun_name = :"#{path_name}_path"
@spec unquote(fun_name)(unquote_splicing(placeholder_types), %{String.t => String.t}) :: String.t
def unquote(fun_name)(unquote_splicing(placeholder_vars), query_params \\ %{}) do
Antikythera.Router.Reverse.make_path(unquote(path), [unquote_splicing(placeholder_vars)], query_params)
end
end
end
defun make_path(path_pattern :: v[String.t], fillers :: v[[placeholder_filler]], query_params :: v[%{String.t => String.t}]) :: String.t do
replaced_path = fill_path(String.split(path_pattern, "/"), fillers, [])
query = Enum.map_join(query_params, "&", fn
{"", _} -> raise "empty query parameter name is not allowed"
{k, s} when is_binary(s) -> URI.encode_www_form(k) <> "=" <> URI.encode_www_form(s)
{k, i} when is_integer(i) -> URI.encode_www_form(k) <> "=" <> Integer.to_string(i)
end)
case query do
"" -> replaced_path
_ -> replaced_path <> "?" <> query
end
end
defunp fill_path(patterns :: [String.t], fillers :: [placeholder_filler], acc :: [String.t]) :: String.t do
([] , [] , acc) -> Enum.reverse(acc) |> Enum.join("/")
([":" <> _ | patterns], [filler | fillers], acc) -> fill_path(patterns, fillers, [encode_segment(filler) | acc])
(["*" <> _ | patterns], [filler | fillers], acc) -> fill_path(patterns, fillers, [encode_segment_list(filler) | acc])
([fixed | patterns] , fillers , acc) -> fill_path(patterns, fillers, [fixed | acc])
end
defunp encode_segment(segment :: String.t) :: String.t do
if segment == "", do: raise "empty segment is not allowed"
URI.encode_www_form(segment)
end
defunp encode_segment_list(segments :: [String.t]) :: String.t do
Enum.map_join(segments, "/", &encode_segment/1)
end
end