defmodule OpenAPI.Processor.Operation do
@moduledoc """
Default plugin for formatting operations
This module also provides the Operation struct that is used by the renderer.
"""
alias OpenAPI.Processor.Operation.Param
alias OpenAPI.Processor.State
alias OpenAPI.Processor.Type
alias OpenAPI.Spec.Path.Operation, as: OperationSpec
alias OpenAPI.Spec.RequestBody, as: RequestBodySpec
alias OpenAPI.Spec.Response, as: ResponseSpec
alias OpenAPI.Spec.Schema, as: SchemaSpec
alias OpenAPI.Spec.Schema.Media, as: MediaSpec
@typedoc "HTTP method"
@type method :: :get | :put | :post | :delete | :options | :head | :patch | :trace
@typedoc "Request content types and their associated schema specs"
@type request_body_unprocessed :: [{content_type :: String.t(), schema :: SchemaSpec.t()}]
@typedoc "Request content types and their associated schemas"
@type request_body :: [{content_type :: String.t(), schema :: Type.t()}]
@typedoc "Response status codes and their associated schema specs"
@type response_body_unprocessed :: [
{status :: integer | :default, schemas :: %{String.t() => SchemaSpec.t()}}
]
@typedoc "Response status codes and their associated schemas"
@type response_body :: [{status :: integer | :default, schemas :: %{String.t() => Type.t()}}]
@typedoc "Processed operation data used by the renderer"
@type t :: %__MODULE__{
docstring: String.t(),
function_name: atom,
module_name: atom,
request_body: request_body,
request_method: atom,
request_path: String.t(),
request_path_parameters: [Param.t()],
request_query_parameters: [Param.t()],
responses: response_body
}
defstruct [
:docstring,
:function_name,
:module_name,
:request_body,
:request_method,
:request_path,
:request_path_parameters,
:request_query_parameters,
:responses
]
@doc """
Create the contents of an `@doc` string for the given operation
Default implementation of `c:OpenAPI.Processor.operation_docstring/3`.
The docstring constructed by this function will contain a summary line provided by the operation
summary (if available) or the request method and path otherwise. It will incorporate the
operation description (if available) and link to any included external documentation. Finally,
all query parameters (which are part of the `opts` argument) are documented.
@doc \"\"\"
Summary of the operation or method and path
Description of the operation, which generally provides more information.
## Options
* `param`: query parameter description
## Resources
* [External Doc Description](link to external documentation)
\"\"\"
"""
@doc default_implementation: true
@spec docstring(State.t(), OperationSpec.t(), [Param.t()]) :: String.t()
def docstring(_state, operation, query_params) do
%OperationSpec{
"$oag_path": request_path,
"$oag_path_method": request_method,
description: description,
external_docs: external_docs,
summary: summary
} = operation
summary =
if summary do
"#{summary}\n"
else
"#{request_method} `#{request_path}`\n"
end
description = if description not in [nil, ""], do: "\n#{description}\n"
options =
if length(query_params) > 0 do
for %Param{description: description, name: name} <- query_params,
into: "\n## Options\n\n" do
if description do
description = String.replace(description, "\n", "\n ")
" * `#{name}`: #{description}\n"
else
" * `#{name}`\n"
end
end
end
resources =
if external_docs && external_docs.url do
if external_docs.description do
"""
## Resources
* [#{external_docs.description}](#{external_docs.url})
"""
else
"""
## Resources
* [Documentation](#{external_docs.url})
"""
end
end
if options || resources do
"#{summary}#{description}#{options}#{resources}\n"
else
"#{summary}#{description}"
end
end
@doc """
Collect request content types and their associated schemas
Default implementation of `c:OpenAPI.Processor.operation_request_body/2`.
"""
@doc default_implementation: true
@spec request_body(State.t(), OperationSpec.t()) :: request_body_unprocessed
def request_body(_state, %OperationSpec{request_body: %RequestBodySpec{content: content}})
when is_map(content) do
Enum.map(content, fn {content_type, %MediaSpec{schema: schema}} -> {content_type, schema} end)
end
def request_body(_state, _operation_spec), do: []
@doc """
Cast the HTTP method to an atom
Default implementation of `c:OpenAPI.Processor.operation_request_method/2`.
"""
@doc default_implementation: true
@spec request_method(State.t(), OperationSpec.t()) :: method
def request_method(_state, %OperationSpec{"$oag_path_method": "get"}), do: :get
def request_method(_state, %OperationSpec{"$oag_path_method": "put"}), do: :put
def request_method(_state, %OperationSpec{"$oag_path_method": "post"}), do: :post
def request_method(_state, %OperationSpec{"$oag_path_method": "delete"}), do: :delete
def request_method(_state, %OperationSpec{"$oag_path_method": "options"}), do: :options
def request_method(_state, %OperationSpec{"$oag_path_method": "head"}), do: :head
def request_method(_state, %OperationSpec{"$oag_path_method": "patch"}), do: :patch
def request_method(_state, %OperationSpec{"$oag_path_method": "trace"}), do: :trace
@doc """
Collect response status codes and their associated schemas
Default implementation of `c:OpenAPI.Processor.operation_response_body/2`.
In this implementation, all schemas are returned regardless of content type. It is possible for
the same status code to have multiple schemas, in which case the renderer should compose a
union type for the response.
"""
@doc default_implementation: true
@spec response_body(State.t(), OperationSpec.t()) :: response_body_unprocessed
def response_body(_state, %OperationSpec{responses: responses}) when is_map(responses) do
Enum.map(responses, fn {status_or_default, %ResponseSpec{content: content}} ->
schemas =
Map.new(content, fn {content_type, %MediaSpec{schema: schema}} ->
{content_type, schema}
end)
{status_or_default, schemas}
end)
end
end