defmodule OpenAPI.Spec.Path.Operation do
@moduledoc "Raw operation from the OpenAPI spec"
import OpenAPI.Reader.State
alias OpenAPI.Spec
alias OpenAPI.Spec.Path.Parameter
alias OpenAPI.Spec.ExternalDocumentation
alias OpenAPI.Spec.RequestBody
alias OpenAPI.Spec.Response
alias OpenAPI.Spec.Server
@type t :: %__MODULE__{
"$oag_base_file": String.t(),
"$oag_base_file_path": [Spec.path_segment()],
"$oag_last_ref_file": String.t() | nil,
"$oag_last_ref_path": [Spec.path_segment()],
"$oag_path": String.t(),
"$oag_path_method": String.t(),
"$oag_path_parameters": [Parameter.t()],
tags: [String.t()],
summary: String.t() | nil,
description: String.t() | nil,
external_docs: ExternalDocumentation.t() | nil,
operation_id: String.t() | nil,
parameters: [Parameter.t()],
request_body: RequestBody.t() | nil,
responses: %{optional(pos_integer | :default) => Response.t()},
callbacks: nil,
deprecated: boolean,
security: nil,
servers: [Server.t()]
}
defstruct [
:"$oag_base_file",
:"$oag_base_file_path",
:"$oag_last_ref_file",
:"$oag_last_ref_path",
:"$oag_path",
:"$oag_path_method",
:"$oag_path_parameters",
:tags,
:summary,
:description,
:external_docs,
:operation_id,
:parameters,
:request_body,
:responses,
:callbacks,
:deprecated,
:security,
:servers
]
@doc false
@spec decode(map, map) :: {map, t}
def decode(state, yaml) do
{state, external_docs} = decode_external_docs(state, yaml)
{state, parameters} = decode_parameters(state, yaml)
{state, request_body} = decode_request_body(state, yaml)
{state, responses} = decode_responses(state, yaml)
{state, servers} = decode_servers(state, yaml)
reverse_base_file_path = Map.fetch!(state, :base_file_path)
[method, path | _] = reverse_base_file_path
operation = %__MODULE__{
"$oag_base_file": Map.fetch!(state, :base_file),
"$oag_base_file_path": reverse_base_file_path |> Enum.reverse(),
"$oag_last_ref_file": Map.fetch!(state, :last_ref_file),
"$oag_last_ref_path": Map.fetch!(state, :last_ref_path) |> Enum.reverse(),
"$oag_path": path,
"$oag_path_method": method,
"$oag_path_parameters": Map.fetch!(state, :path_parameters),
tags: Map.get(yaml, "tags", []),
summary: Map.get(yaml, "summary"),
description: Map.get(yaml, "description"),
external_docs: external_docs,
operation_id: Map.get(yaml, "operationId"),
parameters: parameters,
request_body: request_body,
responses: responses,
callbacks: nil,
deprecated: Map.get(yaml, "deprecated", false),
security: nil,
servers: servers
}
{state, operation}
end
@spec decode_external_docs(map, map) :: {map, ExternalDocumentation.t()}
defp decode_external_docs(state, %{"externalDocs" => docs}) do
with_path(state, docs, "externalDocs", &ExternalDocumentation.decode/2)
end
defp decode_external_docs(state, _docs), do: {state, nil}
@spec decode_parameters(map, map) :: {map, [Parameter.t()]}
def decode_parameters(state, %{"parameters" => parameters}) do
with_path(state, parameters, "parameters", fn state, parameters ->
parameters
|> Enum.with_index()
|> Enum.reverse()
|> Enum.reduce({state, []}, fn {parameter, index}, {state, list} ->
{state, element} =
with_path(state, parameter, index, fn state, parameter ->
with_ref(state, parameter, &Parameter.decode/2)
end)
{state, [element | list]}
end)
end)
end
def decode_parameters(state, _yaml), do: {state, []}
@spec decode_request_body(map, map) :: {map, RequestBody.t() | nil}
def decode_request_body(state, %{"requestBody" => request_body}) do
with_path(state, request_body, "requestBody", &RequestBody.decode/2)
end
def decode_request_body(state, _yaml), do: {state, nil}
@spec decode_responses(map, map) :: {map, %{optional(String.t()) => t}}
defp decode_responses(state, %{"responses" => responses}) do
with_path(state, responses, "responses", fn state, responses ->
Enum.reduce(responses, {state, %{}}, fn {key, response}, {state, responses} ->
{state, property} =
with_path(state, response, key, fn state, response ->
with_ref(state, response, &Response.decode/2)
end)
key =
cond do
key == "default" -> :default
String.match?(key, ~r/^[1-5]XX$/) -> key
:else -> String.to_integer(key)
end
{state, Map.put(responses, key, property)}
end)
end)
end
defp decode_responses(state, _schema), do: {state, %{}}
@spec decode_servers(map, map) :: {map, [Server.t()]}
defp decode_servers(state, %{"servers" => servers}) when is_list(servers) do
with_path(state, servers, "servers", fn state, servers ->
{state, servers} =
servers
|> Enum.with_index()
|> Enum.reduce({state, []}, fn {server, index}, {state, servers} ->
{state, server} = with_path(state, server, index, &Server.decode/2)
{state, [server | servers]}
end)
{state, Enum.reverse(servers)}
end)
end
defp decode_servers(state, _yaml), do: {state, [%Server{url: "/"}]}
end