Current section

Files

Jump to
api_doc lib documenter plug router.ex
Raw

lib/documenter/plug/router.ex

defmodule APIDoc.PlugRouterDocumenter do
@moduledoc ~S"""
"""
require Logger
alias APIDoc.{Doc.Router, Documenter.Helper}
@http_methods ~W(GET POST PUT PATCH DELETE)
defmacro __using__(_) do
quote do
unquote(Helper.register())
@on_definition {APIDoc.PlugRouterDocumenter, :on_def}
@before_compile APIDoc.PlugRouterDocumenter
end
end
@doc false
@spec on_def(term, :def | :defp, atom, term, term, term) :: term
# credo:disable-for-next-line
def on_def(env, :defp, :do_match, [{:conn, _, Plug.Router}, method, path, _], _guards, _body) do
cond do
(api = Module.delete_attribute(env.module, :api)) && method in @http_methods ->
atom_method = method |> String.downcase() |> String.to_existing_atom()
Helper.generate_api_doc(env.module, api, atom_method, path)
(forward = Module.get_attribute(env.module, :plug_forward_target)) && is_list(path) ->
generate_forward(env.module, forward, path)
Module.delete_attribute(env.module, :api) == false ->
:ignore
:undocumented ->
Logger.warn(fn -> warn_undocumented(env.module, method, path) end)
end
end
# credo:disable-for-next-line
def on_def(_env, _type, _name, _args, _guards, _body), do: :ignore
defp warn_undocumented(module, method, path) do
safe_module = module |> Module.split() |> Enum.join(".")
safe_method = if is_binary(method), do: method, else: "*"
# credo:disable-for-next-line
safe_path =
if is_tuple(path) do
""
else
path
|> Enum.map(&if(is_tuple(&1), do: elem(&1, 0), else: &1))
|> Enum.map(&to_string/1)
|> Enum.join("/")
end
"APIDoc: #{safe_method} /#{safe_path} in #{safe_module} is undocumented, please document or use `@api false`."
end
defp generate_forward(module, forward, path) do
cond do
Module.delete_attribute(module, :api) == false ->
:ignore
{:__api_doc__, 0} in forward.__info__(:functions) ->
last = List.last(path)
if is_tuple(last) && elem(last, 0) == :| do
Module.put_attribute(module, :generated_api_doc, %{
to: forward,
description:
if(doc = Module.delete_attribute(module, :doc), do: elem(doc, 1) || nil, else: nil),
path:
Enum.map(path, fn
{field, _, nil} -> field
{:|, _, [{field, _, nil} | _]} -> field
{:|, _, [step | _]} -> step
step -> step
end),
security: Module.delete_attribute(module, :security) || []
})
end
:undocumented ->
Logger.warn(fn ->
safe_module = module |> Module.split() |> Enum.join(".")
safe_forward = forward |> Module.split() |> Enum.join(".")
"APIDoc: #{safe_module} forwards to #{safe_forward}, but #{safe_forward} is not documented."
end)
end
end
defmacro __before_compile__(_env) do
quote do
@doc false
@spec __api_doc__ :: [APIDoc.Doc.Endpoint.t()]
def __api_doc__ do
Enum.map(@generated_api_doc, fn
endpoint = %{to: to} ->
struct(Router, endpoint |> Map.delete(:to) |> Map.put(:endpoints, to.__api_doc__()))
endpoint ->
endpoint
end)
end
end
end
end