Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/plug/cast.ex b/lib/open_api_spex/plug/cast.ex
index 0a7f8a5..f58728c 100644
--- a/lib/open_api_spex/plug/cast.ex
+++ b/lib/open_api_spex/plug/cast.ex
@@ -1,58 +1,86 @@
defmodule OpenApiSpex.Plug.Cast do
@moduledoc """
Module plug that will cast the `Conn.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.Cast, 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.Cast
...
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.Cast,
+ render_error: MyApp.RenderError
+
+ ...
+ end
+
+ defmodule MyApp.RenderError do
+ def init(opts), do: opts
+
+ def call(conn, reason) do
+ msg = %{error: reason} |> Posion.encode!()
+
+ 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
+ def init(opts), do: Keyword.put_new(opts, :render_error, OpenApiSpex.Plug.DefaultRenderError)
@impl Plug
- def call(conn = %{private: %{open_api_spex: private_data}}, operation_id: operation_id) do
+ 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)
case OpenApiSpex.cast(spec, operation, conn, content_type) do
{:ok, params} -> %{conn | params: params}
{:error, reason} ->
+ opts = render_error.init(reason)
+
conn
- |> Plug.Conn.send_resp(422, "#{reason}")
+ |> 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
+ 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, operation_id: operation_id)
+ call(conn, Keyword.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/default_render_error.ex b/lib/open_api_spex/plug/default_render_error.ex
new file mode 100644
index 0000000..82222a2
--- /dev/null
+++ b/lib/open_api_spex/plug/default_render_error.ex
@@ -0,0 +1,14 @@
+defmodule OpenApiSpex.Plug.DefaultRenderError do
+
+ @behaviour Plug
+
+ alias Plug.Conn
+
+ @impl Plug
+ def init(opts), do: opts
+
+ @impl Plug
+ def call(conn, reason) do
+ conn |> Conn.send_resp(422, "#{reason}")
+ end
+end
diff --git a/lib/open_api_spex/plug/validate.ex b/lib/open_api_spex/plug/validate.ex
index 72455b7..7dd5c5a 100644
--- a/lib/open_api_spex/plug/validate.ex
+++ b/lib/open_api_spex/plug/validate.ex
@@ -1,45 +1,84 @@
defmodule OpenApiSpex.Plug.Validate do
@moduledoc """
Module plug that validates params against the schema defined for an operation.
If validation fails, the plug will send a 422 response with the reason as the body.
This plug should always be run after `OpenApiSpex.Plug.Cast`, as it expects the params map to
have atom keys and query params converted from strings to the appropriate types.
## Example
defmodule MyApp.UserController do
use Phoenix.Controller
plug OpenApiSpex.Plug.Cast
plug OpenApiSpex.Plug.Validate
...
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 MyApp.UserController do
+ use Phoenix.Controller
+ plug OpenApiSpex.Plug.Cast
+ plug OpenApiSpex.Plug.Validate,
+ render_error: MyApp.RenderError
+
+ def render_error(conn, reason) do
+ msg = %{error: reason} |> Posion.encode!()
+
+ conn
+ |> Conn.put_resp_content_type("application/json")
+ |> Conn.send_resp(400, msg)
+ end
+ ...
+ end
+
+ defmodule MyApp.RenderError do
+ def init(opts), do: opts
+
+ def call(conn, reason) do
+ msg = %{error: reason} |> Posion.encode!()
+
+ 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
+ def init(opts), do: Keyword.put_new(opts, :render_error, OpenApiSpex.Plug.DefaultRenderError)
@impl Plug
- def call(conn, _opts) do
+ def call(conn, render_error: render_error) do
spec = conn.private.open_api_spex.spec
operation_id = conn.private.open_api_spex.operation_id
operation_lookup = conn.private.open_api_spex.operation_lookup
operation = operation_lookup[operation_id]
content_type = Conn.get_req_header(conn, "content-type")
|> Enum.at(0)
|> String.split(";")
|> Enum.at(0)
with :ok <- OpenApiSpex.validate(spec, operation, conn, content_type) do
conn
else
{:error, reason} ->
+ opts = render_error.init(reason)
+
conn
- |> Conn.send_resp(422, "#{reason}")
- |> Conn.halt()
+ |> render_error.call(opts)
+ |> Plug.Conn.halt()
end
end
+
+ def render_error(conn, reason) do
+ conn |> Conn.send_resp(422, "#{reason}")
+ end
end
\ No newline at end of file
diff --git a/test/param_test.exs b/test/param_test.exs
index 136b0d1..7580d7b 100644
--- a/test/param_test.exs
+++ b/test/param_test.exs
@@ -1,36 +1,70 @@
defmodule ParamTest do
use ExUnit.Case
describe "Param" do
test "Valid Param" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=true")
|> Plug.Conn.put_req_header("content-type", "application/json")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 200
end
test "Invalid value" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=123")
|> Plug.Conn.put_req_header("content-type", "application/json")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 422
end
test "Invalid Param" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=123&inValidParam=123&inValid2=hi")
|> Plug.Conn.put_req_header("content-type", "application/json")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 422
assert conn.resp_body == "Undefined query parameter: \"inValid2\""
end
end
+
+ describe "Param with custom error handling" do
+ test "Valid Param" do
+ conn =
+ :get
+ |> Plug.Test.conn("/api/custom_error_users?validParam=true")
+ |> Plug.Conn.put_req_header("content-type", "application/json")
+ |> OpenApiSpexTest.Router.call([])
+
+ assert conn.status == 200
+ end
+
+ test "Invalid value" do
+ conn =
+ :get
+ |> Plug.Test.conn("/api/custom_error_users?validParam=123")
+ |> Plug.Conn.put_req_header("content-type", "application/json")
+ |> OpenApiSpexTest.Router.call([])
+
+ assert conn.status == 400
+ end
+
+ test "Invalid Param" do
+ conn =
+ :get
+ |> Plug.Test.conn("/api/custom_error_users?validParam=123&inValidParam=123&inValid2=hi")
+ |> Plug.Conn.put_req_header("content-type", "application/json")
+ |> OpenApiSpexTest.Router.call([])
+
+ assert conn.status == 400
+ assert conn.resp_body == "Undefined query parameter: \"inValid2\""
+ end
+ end
+
end
diff --git a/test/support/custom_error_user_controller.ex b/test/support/custom_error_user_controller.ex
new file mode 100644
index 0000000..2acd746
--- /dev/null
+++ b/test/support/custom_error_user_controller.ex
@@ -0,0 +1,56 @@
+defmodule OpenApiSpexTest.CustomErrorUserController do
+ use Phoenix.Controller
+ alias OpenApiSpex.Operation
+ alias OpenApiSpexTest.Schemas
+ alias Plug.Conn
+
+ defmodule CustomRenderErrorPlug do
+
+ @behaviour Plug
+
+ alias Plug.Conn
+
+ @impl Plug
+ def init(opts), do: opts
+
+ @impl Plug
+ def call(conn, reason) do
+ conn |> Conn.send_resp(400, "#{reason}")
+ end
+ end
+
+ plug OpenApiSpex.Plug.Cast, render_error: CustomRenderErrorPlug
+ plug OpenApiSpex.Plug.Validate, render_error: CustomRenderErrorPlug
+
+ def open_api_operation(action) do
+ apply(__MODULE__, :"#{action}_operation", [])
+ end
+
+ def index_operation() do
+ import Operation
+ %Operation{
+ tags: ["users"],
+ summary: "List users",
+ description: "List all useres",
+ operationId: "UserController.index",
+ parameters: [
+ parameter(:validParam, :query, :boolean, "Valid Param", example: true)
+ ],
+ responses: %{
+ 200 => response("User List Response", "application/json", Schemas.UsersResponse)
+ }
+ }
+ end
+ def index(conn, _params) do
+ json(conn, %Schemas.UsersResponse{
+ data: [
+ %Schemas.User{
+ id: 123,
+ name: "joe user",
+ email: "joe@gmail.com"
+ }
+ ]
+ })
+ end
+
+end
diff --git a/test/support/router.ex b/test/support/router.ex
index 84aa71f..48f0328 100644
--- a/test/support/router.ex
+++ b/test/support/router.ex
@@ -1,20 +1,22 @@
defmodule OpenApiSpexTest.Router do
use Phoenix.Router
alias Plug.Parsers
alias OpenApiSpexTest.UserController
+ alias OpenApiSpexTest.CustomErrorUserController
alias OpenApiSpex.Plug.{PutApiSpec, RenderSpec}
pipeline :api do
plug :accepts, ["json"]
plug PutApiSpec, module: OpenApiSpexTest.ApiSpec
plug Parsers, parsers: [:json], pass: ["text/*"], json_decoder: Poison
end
scope "/api" do
pipe_through :api
resources "/users", UserController, only: [:create, :index, :show]
+ resources "/custom_error_users", CustomErrorUserController, only: [:index]
get "/users/:id/payment_details", UserController, :payment_details
post "/users/create_entity", UserController, :create_entity
get "/openapi", RenderSpec, []
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 1, 8:43 PM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41840
Default Alt Text
(11 KB)

Event Timeline