Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F116588
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment