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