Closed
Description
Example implementation that I have used in my project (still lacks a little bit options, but overall idea can be seen):
defmodule KokonWeb.Rest do
alias OpenApiSpex.Operation
defmacro __using__(_opts) do
quote do
alias KokonWeb.Rest.Schema
alias OpenApiSpex.Operation
plug(OpenApiSpex.Plug.Cast)
plug(OpenApiSpex.Plug.Validate)
@on_definition KokonWeb.Rest
@before_compile KokonWeb.Rest
Module.register_attribute(__MODULE__, :parameter, accumulate: true)
Module.register_attribute(__MODULE__, :response, accumulate: true)
Module.register_attribute(__MODULE__, :open_api_operations, accumulate: true)
end
end
def __on_definition__(_env, _type, :open_api_operation, _args, _guards, _body), do: nil
def __on_definition__(_env, _type, :action, _args, _guards, _body), do: nil
def __on_definition__(%Macro.Env{module: mod}, :def, name, _args, _guards, _body) do
parameters = Module.delete_attribute(mod, :parameter)
response = Module.delete_attribute(mod, :response)
{summary, doc} = docs(Module.get_attribute(mod, :doc))
operation =
%Operation{
summary: summary || "TODO",
description: doc,
operationId: module_name(mod) <> ".#{name}",
parameters: parameters,
responses: Map.new(response)
}
Module.put_attribute(mod, :open_api_operations, {name, operation})
end
def __on_definition__(_env, _type, _name, _args, _guards, _body), do: nil
defmacro __before_compile__(_env) do
quote unquote: false do
for {name, operation} <- Module.delete_attribute(__MODULE__, :open_api_operations) do
def open_api_operation(unquote(name)), do: unquote(Macro.escape(operation))
end
end
end
defp docs(nil), do: {nil, nil}
defp docs({_, doc}) do
[summary | _] = String.split(doc, ~r/\n\s*\n/, parts: 2)
{summary, doc}
end
defp module_name(mod) when is_atom(mod), do: module_name(Atom.to_string(mod))
defp module_name("Elixir." <> name), do: name
defp module_name(name) when is_binary(name), do: name
end
This causes controller to look like this:
defmodule KokonWeb.Rest.Controllers.Schedule do
use KokonWeb.Rest
@doc "List schedule"
@response {200, Operation.response(
"Submissions",
"application/json",
KokonWeb.Rest.Schema.Submissions
)}
def index(conn, _params) do
{:ok, submissions} = Kokon.Submissions.all()
json(conn, submissions)
end
@doc "Create new submission"
@parameter Operation.parameter(:title, :query, :string, "Submission title",
required: true
)
@parameter Operation.parameter(
:abstract,
:query,
:string,
"Submission description",
required: true
)
@response {200, Operation.response(
"Submissions",
"application/json",
KokonWeb.Rest.Schema.Submission
)}
def create(conn, %{title: title, abstract: abstract}) do
with {:ok, submission} <-
Kokon.Submissions.create(%{title: title, abstract: abstract}) do
json(conn, submission)
end
end
end
I am opening this as an issue instead of PR as I would like to know opinions about this beforehand.