Packages
open_api_spex
3.20.0
3.22.3
3.22.2
3.22.1
3.22.0
3.21.5
3.21.4
3.21.3
3.21.2
3.21.1
3.21.0
3.20.1
3.20.0
3.19.1
3.19.0
3.18.3
3.18.2
3.18.1
3.18.0
3.17.3
3.17.2
3.17.1
3.17.0
3.16.4
3.16.3
3.16.2
3.16.1
3.16.0
3.15.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.0
3.8.0
3.7.0
3.6.0
3.5.2
3.5.1
3.5.0
3.4.0
3.3.0
3.2.1
3.2.0
3.1.0
3.0.0
2.3.1
2.3.0
2.2.0
2.1.1
2.1.0
2.0.0
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
Leverage Open Api Specification 3 (swagger) to document, test, validate and explore your Plug and Phoenix APIs.
Current section
Files
Jump to
Current section
Files
lib/open_api_spex/controller.ex
defmodule OpenApiSpex.Controller do
@moduledoc ~S'''
Generation of OpenAPI documentation via ExDoc documentation and tags.
Note: For projects using Elixir releases, [there is an issue](https://github.com/open-api-spex/open_api_spex/issues/242) that
potentially breaks OpenApiSpex's integration with your application. Please use `OpenApiSpex.ControllerSpecs` instead.
## Supported OpenAPI fields
### `description` and `summary`
Description of endpoint will be filled with documentation string in the same
manner as ExDocs, so first line will be used as a `summary` and the rest of it
will be used as `description` field.
### `operation_id`
The action's `operation_id` can be set explicitly using a `@doc` tag.
If no `operation_id` is specified, it will default to the action's module path: `Module.Name.function_name`
### `parameters`
Parameters of the endpoint are defined by `:parameters` tag which should be
map or list that is formed as:
```elixir
[
param_name: definition
]
# or
[
structure_definition
]
```
Where `definition` is map or keyword list that accepts the same arguments.
Where `structure_definition` is `OpenApiSpex.Parameter.t()` or `OpenApiSpex.Reference.t()` structure
that accepts the same arguments.
Example:
```elixir
@doc parameters: [
group_id: [in: :path, type: :integer, description: "Group ID", example: 1]
]
```
or
```elixir
@doc parameters: [
%OpenApiSpex.Parameter{
in: :path,
name: :group_id,
description: "Group ID",
schema: %Schema{type: :integer},
required: true,
example: 1
}
]
```
or
```elixir
@doc parameters: [
"$ref": "#/components/parameters/group_id"
# or
%OpenApiSpex.Reference{"$ref": "#/components/parameters/group_id"}
]
```
### `responses`
Responses are controlled by `:responses` tag. Responses must be defined as
a map or keyword list in form of:
```
%{
200 => {"Response name", "application/json", schema},
:not_found => {"Response name", "application/json", schema}
}
```
Or:
```
[
ok: {"Response name", "application/json", schema},
not_found: {"Response name", "application/json", schema}
]
```
If a response has no body, the definition may be simplified further:
```
[
no_content: "Empty response"
]
```
For each key in the key-value list of map, either an HTTP status code can be used or its atom equivalent.
The full set of atom keys are defined in `Plug.Conn.Status.code/1`.
### `requestBody`
Controlled by `:request_body` parameter and is defined as a tuple in form
`{description, mime, schema}` or `{description, mime, schema, opts}` that
matches the arguments of `OpenApiSpex.Operation.request_body/3` or
`OpenApiSpex.Operation.request_body/4`, respectively.
```
@doc request_body: {
"CartUpdateRequest",
"application/vnd.api+json",
CartUpdateRequest,
required: true
}
```
### `security`
Allows specifying the security scheme(s) required for this operation. See
`OpenApiSpex.Operation` and `OpenApiSpex.SecurityRequirement`.
### `tags`
Tags are controlled by `:tags` attribute. In contrast to other attributes, this
one will also inherit all tags defined as a module documentation attributes.
## Example
```
defmodule UserController do
@moduledoc tags: ["Users"]
use MyAppWeb, :controller
use OpenApiSpex.Controller
@doc """
Endpoint summary
Endpoint description...
"""
@doc parameters: [
id: [in: :path, type: :string, required: true]
],
request_body: {"Request body to update User", "application/json", UserUpdateBody, required: true},
responses: [
ok: {"User document", "application/json", UserSchema},
{302, "Redirect", "text/html", EmptyResponse, headers: %{"Location" => %Header{description: "Redirect Location"}}}
]
def update(conn, %{id: id}) do
user_params = conn.body_params
# …
end
end
```
'''
alias OpenApiSpex.{Operation, OperationBuilder}
defmacro __using__(_opts) do
quote do
@doc false
@spec open_api_operation(atom()) :: OpenApiSpex.Operation.t()
def open_api_operation(name),
do: unquote(__MODULE__).__api_operation__(__MODULE__, name)
defoverridable open_api_operation: 1
end
end
@doc false
@spec __api_operation__(module(), atom()) :: Operation.t() | nil
def __api_operation__(mod, name) do
case get_docs(mod, name) do
{:ok, {mod_meta, summary, description, meta}} ->
%Operation{
summary: summary,
externalDocs: OperationBuilder.build_external_docs(meta, mod_meta),
description: description || "",
operationId: OperationBuilder.build_operation_id(meta, mod, name),
parameters: OperationBuilder.build_parameters(meta),
requestBody: OperationBuilder.build_request_body(meta),
responses: OperationBuilder.build_responses(meta),
security: OperationBuilder.build_security(meta, mod_meta),
tags: OperationBuilder.build_tags(meta, mod_meta)
}
_ ->
nil
end
end
defp get_docs(module, name) do
{:docs_v1, _anno, _lang, _format, _module_doc, mod_meta, mod_docs} = Code.fetch_docs(module)
mod_docs
|> Enum.find(mod_docs, fn
{{:function, ^name, _}, _, _, _, _} -> true
_ -> false
end)
|> doc_for_function(module, name, mod_meta)
end
defp doc_for_function({_, _, _, :hidden, _}, _module, _name, _mod_meta), do: nil
defp doc_for_function({_, _, _, docs, meta}, module, name, mod_meta) when is_map(meta) do
cond do
Enum.empty?(meta) ->
IO.warn("No docs found for function #{module}.#{name}/2")
nil
not Map.has_key?(meta, :responses) ->
IO.warn("No responses declaration found for function #{module}.#{name}/2")
nil
true ->
{summary, description} =
case docs do
%{"en" => contents} ->
[summary | maybe_description] = String.split(contents, ~r/\n\s*\n/, parts: 2)
{summary, List.first(maybe_description) || ""}
_ ->
{"", ""}
end
{:ok, {mod_meta, summary, description, meta}}
end
end
defp doc_for_function(_doc_for_function, module, name, _mod_meta) do
IO.warn("Invalid docs declaration found for function #{module}.#{name}/2")
nil
end
end