Current section

Files

Jump to
oaskit lib oaskit spec request_body.ex
Raw

lib/oaskit/spec/request_body.ex

defmodule Oaskit.Spec.RequestBody do
alias Oaskit.Spec.MediaType
alias Oaskit.Spec.Reference
import Oaskit.Internal.ControllerBuilder
use Oaskit.Internal.SpecObject
defschema %{
title: "RequestBody",
type: :object,
description: "Describes a single request body.",
properties: %{
description: %{type: :string, description: "A brief description of the request body."},
content: %{
type: :object,
additionalProperties: Oaskit.Spec.MediaType,
description: "A map containing the content of the request body by media type. Required."
},
required: %{
type: :boolean,
default: false,
description: "Determines if the request body is required in the request."
}
},
required: [:content]
}
@doc false
def from_controller(spec) do
# The debang version is made to work with the cast system, there is no need
# to return {:error, _}
{:ok, from_controller!(spec)}
end
def from_controller!(%Reference{} = ref) do
ref
end
def from_controller!(schema)
when is_atom(schema)
when is_boolean(schema) do
from_controller!({schema, []})
end
def from_controller!({schema, spec}) do
spec = Keyword.put_new(spec, :required, true)
case Keyword.fetch(spec, :content) do
:error ->
spec = Keyword.put(spec, :content, %{"application/json" => %{schema: schema}})
from_controller!(spec)
_ ->
raise ArgumentError,
"cannot use a tuple definition for request body with the :content option"
end
end
def from_controller!(spec) when is_list(spec) do
spec
|> make(__MODULE__)
|> take_required(:content, &cast_content/1)
|> take_default(:required, false)
|> into()
end
defp cast_content(content) when is_map(content) when is_list(content) do
content
|> Enum.to_list()
|> tap(fn
[] -> raise ArgumentError, ":content cannot by empty"
_ -> :ok
end)
|> Enum.reduce_while({:ok, %{}}, fn
{mime_type, _media_spec}, _ when not is_binary(mime_type) ->
{:halt, {:error, "media mime types must be strings, got: #{inspect(mime_type)}"}}
{mime_type, media_spec}, {:ok, acc} ->
case Plug.Conn.Utils.media_type(mime_type) do
{:ok, _, _, _} ->
media = MediaType.from_controller!(media_spec)
{:cont, {:ok, Map.put(acc, mime_type, media)}}
:error ->
{:halt, {:error, "cannot parse media type #{inspect(mime_type)}"}}
end
end)
end
defp cast_content(content) do
raise ArgumentError,
"invalid :content given in request body definition, expected map or keyword list,got: #{inspect(content)}"
end
@impl true
def normalize!(data, ctx) do
data
|> from(__MODULE__, ctx)
|> normalize_default([:description, :required])
|> normalize_subs(content: {:map, Oaskit.Spec.MediaType})
|> collect()
end
end