Page MenuHomePhorge

No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/open_api.ex b/lib/open_api_spex/open_api.ex
index 8bf48b7..a9c15ba 100644
--- a/lib/open_api_spex/open_api.ex
+++ b/lib/open_api_spex/open_api.ex
@@ -1,70 +1,74 @@
defmodule OpenApiSpex.OpenApi do
@moduledoc """
Defines the `OpenApiSpex.OpenApi.t` type.
"""
alias OpenApiSpex.{
Info, Server, Paths, Components,
SecurityRequirement, Tag, ExternalDocumentation,
OpenApi
}
@enforce_keys [:info, :paths]
defstruct [
openapi: "3.0.0",
info: nil,
servers: [],
paths: nil,
components: nil,
security: [],
tags: [],
externalDocs: nil
]
@typedoc """
[OpenAPI Object](https://swagger.io/specification/#oasObject)
This is the root document object of the OpenAPI document.
"""
@type t :: %OpenApi{
openapi: String.t,
info: Info.t,
servers: [Server.t] | nil,
paths: Paths.t,
components: Components.t | nil,
security: [SecurityRequirement.t] | nil,
tags: [Tag.t] | nil,
externalDocs: ExternalDocumentation.t | nil
}
+ @json_encoder Enum.find([Jason, Poison], &Code.ensure_loaded?/1)
+
+ def json_encoder, do: @json_encoder
+
for encoder <- [Poison.Encoder, Jason.Encoder] do
if Code.ensure_loaded?(encoder) do
defimpl encoder do
def encode(api_spec = %OpenApi{}, options) do
api_spec
|> to_json()
|> unquote(encoder).encode(options)
end
defp to_json(%Regex{source: source}), do: source
defp to_json(value = %{__struct__: _}) do
value
|> Map.from_struct()
|> to_json()
end
defp to_json(value) when is_map(value) do
value
|> Stream.map(fn {k,v} -> {to_string(k), to_json(v)} end)
|> Stream.filter(fn {_, nil} -> false; _ -> true end)
|> Enum.into(%{})
end
defp to_json(value) when is_list(value) do
Enum.map(value, &to_json/1)
end
defp to_json(nil), do: nil
defp to_json(true), do: true
defp to_json(false), do: false
defp to_json(value) when is_atom(value), do: to_string(value)
defp to_json(value), do: value
end
end
end
end
diff --git a/lib/open_api_spex/plug/cast_and_validate.ex b/lib/open_api_spex/plug/cast_and_validate.ex
index b250e92..1043094 100644
--- a/lib/open_api_spex/plug/cast_and_validate.ex
+++ b/lib/open_api_spex/plug/cast_and_validate.ex
@@ -1,103 +1,103 @@
defmodule OpenApiSpex.Plug.CastAndValidate do
@moduledoc """
Module plug that will cast and validate the `Conn.params` and `Conn.body_params` according to the schemas defined for the operation.
The operation_id can be given at compile time as an argument to `init`:
plug OpenApiSpex.Plug.CastAndValidate, operation_id: "MyApp.ShowUser"
For phoenix applications, the operation_id can be obtained at runtime automatically.
defmodule MyAppWeb.UserController do
use Phoenix.Controller
plug OpenApiSpex.Plug.CastAndValidate
...
end
If you want customize the error response, you can provide the `:render_error` option to register a plug which creates
a custom response in the case of a validation error.
## Example
defmodule MyAppWeb.UserController do
use Phoenix.Controller
plug OpenApiSpex.Plug.CastAndValidate, render_error: MyApp.RenderError
...
end
defmodule MyApp.RenderError do
def init(opts), do: opts
def call(conn, reason) do
msg = Jason.encode!(%{error: reason})
conn
|> Conn.put_resp_content_type("application/json")
|> Conn.send_resp(400, msg)
end
end
"""
@behaviour Plug
alias Plug.Conn
@impl Plug
def init(opts) do
opts
|> Map.new()
- |> Map.put_new(:render_error, OpenApiSpex.Plug.DefaultRenderError)
+ |> Map.put_new(:render_error, OpenApiSpex.Plug.JsonRenderError)
end
@impl Plug
def call(conn = %{private: %{open_api_spex: private_data}}, %{
operation_id: operation_id,
render_error: render_error
}) do
spec = private_data.spec
operation = private_data.operation_lookup[operation_id]
content_type =
Conn.get_req_header(conn, "content-type")
|> Enum.at(0, "")
|> String.split(";")
|> Enum.at(0)
private_data = Map.put(private_data, :operation_id, operation_id)
conn = Conn.put_private(conn, :open_api_spex, private_data)
with {:ok, conn} <- OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
conn
else
{:error, reason} ->
opts = render_error.init(reason)
conn
|> render_error.call(opts)
|> Plug.Conn.halt()
end
end
def call(
conn = %{
private: %{phoenix_controller: controller, phoenix_action: action, open_api_spex: _pd}
},
opts
) do
operation_id = controller.open_api_operation(action).operationId
if operation_id do
call(conn, Map.put(opts, :operation_id, operation_id))
else
raise "operationId was not found in action API spec"
end
end
def call(_conn = %{private: %{open_api_spex: _pd}}, _opts) do
raise ":operation_id was neither provided nor inferred from conn. Consider putting plug OpenApiSpex.Plug.Cast rather into your phoenix controller."
end
def call(_conn, _opts) do
raise ":open_api_spex was not found under :private. Maybe OpenApiSpex.Plug.PutApiSpec was not called before?"
end
end
diff --git a/lib/open_api_spex/plug/json_render_error.ex b/lib/open_api_spex/plug/json_render_error.ex
new file mode 100644
index 0000000..c27b8b1
--- /dev/null
+++ b/lib/open_api_spex/plug/json_render_error.ex
@@ -0,0 +1,26 @@
+defmodule OpenApiSpex.Plug.JsonRenderError do
+ @behaviour Plug
+
+ alias Plug.Conn
+ alias OpenApiSpex.OpenApi
+
+ @impl Plug
+ def init(opts), do: opts
+
+ @impl Plug
+ def call(conn, reasons) when is_list(reasons) do
+ response = %{
+ errors: Enum.map(reasons, &to_string/1)
+ }
+
+ json = OpenApi.json_encoder().encode!(response)
+
+ conn
+ |> Conn.put_resp_content_type("application/json")
+ |> Conn.send_resp(422, json)
+ end
+
+ def call(conn, reason) do
+ call(conn, [reason])
+ end
+end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 30, 2:53 AM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41351
Default Alt Text
(6 KB)

Event Timeline