Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115292
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
15 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 70b2a38..2e5b6be 100644
--- a/lib/open_api_spex/plug/cast.ex
+++ b/lib/open_api_spex/plug/cast.ex
@@ -1,92 +1,110 @@
defmodule OpenApiSpex.Plug.Cast do
@moduledoc """
Module plug that will cast the `Conn.params` and `Conn.body_params` according to the schemas defined for the operation.
Note that when using this plug, the body params are no longer merged into `Conn.params` and must be read from `Conn.body_params`
separately.
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
+ @deprecated "Use OpenApiSpex.Plug.CastAndValidate instead"
def init(opts) do
opts
|> Map.new()
|> Map.put_new(:render_error, OpenApiSpex.Plug.DefaultRenderError)
end
@impl Plug
- def call(conn = %{private: %{open_api_spex: private_data}}, %{operation_id: operation_id, render_error: render_error}) do
+ @deprecated "Use OpenApiSpex.Plug.CastAndValidate instead"
+ 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)
+
+ 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, conn} -> conn
+ {:ok, conn} ->
+ conn
+
{: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
+
+ 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
+
+ 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/validate.ex b/lib/open_api_spex/plug/validate.ex
index 54e9fd5..143e426 100644
--- a/lib/open_api_spex/plug/validate.ex
+++ b/lib/open_api_spex/plug/validate.ex
@@ -1,84 +1,88 @@
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
+ @deprecated "Use OpenApiSpex.Plug.CastAndValidate.init/1 instead"
def init(opts), do: Keyword.put_new(opts, :render_error, OpenApiSpex.Plug.DefaultRenderError)
@impl Plug
+ @deprecated "Use OpenApiSpex.Plug.CastAndValidate.call/2 instead"
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)
+
+ 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
|> 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
+end
diff --git a/lib/open_api_spex/test/assertions.ex b/lib/open_api_spex/test/assertions.ex
index ede6534..b0aeca8 100644
--- a/lib/open_api_spex/test/assertions.ex
+++ b/lib/open_api_spex/test/assertions.ex
@@ -1,42 +1,42 @@
defmodule OpenApiSpex.Test.Assertions do
@moduledoc """
Defines helpers for testing API responses and examples against API spec schemas.
"""
alias OpenApiSpex.OpenApi
import ExUnit.Assertions
@dialyzer {:no_match, assert_schema: 3}
@doc """
Asserts that `value` conforms to the schema with title `schema_title` in `api_spec`.
"""
@spec assert_schema(map, String.t(), OpenApi.t()) :: map | no_return
- @deprecated "use Elixir.OpenApiSpex.Test.Assertions2 instead"
+ @deprecated "Use OpenApiSpex.TestAssertions.assert_schema/3 instead"
def assert_schema(value = %{}, schema_title, api_spec = %OpenApi{}) do
schemas = api_spec.components.schemas
schema = schemas[schema_title]
if !schema do
flunk("Schema: #{schema_title} not found in #{inspect(Map.keys(schemas))}")
end
data =
case OpenApiSpex.cast(api_spec, schema, value) do
{:ok, data} ->
data
{:error, reason} ->
flunk("Value does not conform to schema #{schema_title}: #{reason}\n#{inspect(value)}")
end
case OpenApiSpex.validate(api_spec, schema, data) do
:ok ->
:ok
{:error, reason} ->
flunk("Value does not conform to schema #{schema_title}: #{reason}\n#{inspect(value)}")
end
data
end
end
diff --git a/lib/open_api_spex/test/assertions2.ex b/lib/open_api_spex/test/assertions2.ex
index 38e2b37..501ecb3 100644
--- a/lib/open_api_spex/test/assertions2.ex
+++ b/lib/open_api_spex/test/assertions2.ex
@@ -1,42 +1,43 @@
defmodule OpenApiSpex.Test.Assertions2 do
@moduledoc """
Defines helpers for testing API responses and examples against API spec schemas.
"""
import ExUnit.Assertions
alias OpenApiSpex.{Cast, OpenApi}
alias OpenApiSpex.Cast.Error
@dialyzer {:no_match, assert_schema: 3}
@doc """
Asserts that `value` conforms to the schema with title `schema_title` in `api_spec`.
"""
@spec assert_schema(map, String.t(), OpenApi.t()) :: map | no_return
+ @deprecated "Use OpenApiSpex.TestAssertions.assert_schema/3 instead"
def assert_schema(value = %{}, schema_title, api_spec = %OpenApi{}) do
schemas = api_spec.components.schemas
schema = schemas[schema_title]
if !schema do
flunk("Schema: #{schema_title} not found in #{inspect(Map.keys(schemas))}")
end
case Cast.cast(schema, value, api_spec.components.schemas) do
{:ok, data} ->
data
{:error, errors} ->
errors =
Enum.map(errors, fn error ->
message = Error.message(error)
path = Error.path_to_string(error)
"#{message} at #{path}"
end)
flunk(
"Value does not conform to schema #{schema_title}: #{Enum.join(errors, "\n")}\n#{
inspect(value)
}"
)
end
end
end
diff --git a/lib/open_api_spex/test/assertions2.ex b/lib/open_api_spex/test/test_assertions.ex
similarity index 88%
copy from lib/open_api_spex/test/assertions2.ex
copy to lib/open_api_spex/test/test_assertions.ex
index 38e2b37..cb728cb 100644
--- a/lib/open_api_spex/test/assertions2.ex
+++ b/lib/open_api_spex/test/test_assertions.ex
@@ -1,42 +1,42 @@
-defmodule OpenApiSpex.Test.Assertions2 do
+defmodule OpenApiSpex.TestAssertions do
@moduledoc """
Defines helpers for testing API responses and examples against API spec schemas.
"""
import ExUnit.Assertions
- alias OpenApiSpex.{Cast, OpenApi}
+ alias OpenApiSpex.OpenApi
alias OpenApiSpex.Cast.Error
@dialyzer {:no_match, assert_schema: 3}
@doc """
Asserts that `value` conforms to the schema with title `schema_title` in `api_spec`.
"""
@spec assert_schema(map, String.t(), OpenApi.t()) :: map | no_return
def assert_schema(value = %{}, schema_title, api_spec = %OpenApi{}) do
schemas = api_spec.components.schemas
schema = schemas[schema_title]
if !schema do
flunk("Schema: #{schema_title} not found in #{inspect(Map.keys(schemas))}")
end
- case Cast.cast(schema, value, api_spec.components.schemas) do
+ case OpenApiSpex.cast_value(value, schema, api_spec) do
{:ok, data} ->
data
{:error, errors} ->
errors =
Enum.map(errors, fn error ->
message = Error.message(error)
path = Error.path_to_string(error)
"#{message} at #{path}"
end)
flunk(
"Value does not conform to schema #{schema_title}: #{Enum.join(errors, "\n")}\n#{
inspect(value)
}"
)
end
end
end
diff --git a/test/cast/all_of_test.exs b/test/cast/all_of_test.exs
index 236a4ab..e1ced88 100644
--- a/test/cast/all_of_test.exs
+++ b/test/cast/all_of_test.exs
@@ -1,36 +1,36 @@
defmodule OpenApiSpex.CastAllOfTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema}
alias OpenApiSpex.Cast.{Error, AllOf}
- alias OpenApiSpex.Test.Assertions2
+ alias OpenApiSpex.TestAssertions
defp cast(ctx), do: AllOf.cast(struct(Cast, ctx))
describe "cast/1" do
test "allOf" do
schema = %Schema{allOf: [%Schema{type: :integer}, %Schema{type: :string}]}
assert {:ok, 1} = cast(value: "1", schema: schema)
end
test "allOf, uncastable schema" do
schema = %Schema{allOf: [%Schema{type: :integer}, %Schema{type: :string}]}
assert {:error, [error]} = cast(value: [:whoops], schema: schema)
assert Error.message(error) ==
"Failed to cast value as integer. Value must be castable using `allOf` schemas listed."
schema_with_title = %Schema{allOf: [%Schema{title: "Age", type: :integer}]}
assert {:error, [error_with_schema_title]} =
cast(value: [:nopes], schema: schema_with_title)
assert Error.message(error_with_schema_title) ==
"Failed to cast value as Age. Value must be castable using `allOf` schemas listed."
end
test "a more sophisticated example" do
dog = %{"bark" => "woof", "pet_type" => "Dog"}
- Assertions2.assert_schema(dog, "Dog", OpenApiSpexTest.ApiSpec.spec())
+ TestAssertions.assert_schema(dog, "Dog", OpenApiSpexTest.ApiSpec.spec())
end
end
end
diff --git a/test/cast/one_of_test.exs b/test/cast/one_of_test.exs
index 381e933..5883c85 100644
--- a/test/cast/one_of_test.exs
+++ b/test/cast/one_of_test.exs
@@ -1,37 +1,37 @@
defmodule OpenApiSpex.CastOneOfTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema}
alias OpenApiSpex.Cast.{Error, OneOf}
- alias OpenApiSpex.Test.Assertions2
+ alias OpenApiSpex.TestAssertions
defp cast(ctx), do: OneOf.cast(struct(Cast, ctx))
describe "cast/1" do
test "oneOf" do
schema = %Schema{oneOf: [%Schema{type: :integer}, %Schema{type: :string}]}
assert {:ok, "hello"} = cast(value: "hello", schema: schema)
end
test "oneOf, more than one matching schema" do
schema = %Schema{oneOf: [%Schema{type: :integer}, %Schema{type: :string}]}
assert {:error, [error]} = cast(value: "1", schema: schema)
assert error.reason == :one_of
assert Error.message(error) ==
"Failed to cast value to one of: Schema(type: :string), Schema(type: :integer)"
end
test "oneOf, no castable schema" do
schema = %Schema{oneOf: [%Schema{type: :string}]}
assert {:error, [error]} = cast(value: 1, schema: schema)
assert error.reason == :one_of
assert Error.message(error) == "Failed to cast value to one of: [] (no schemas provided)"
end
test "a more sophisticated case" do
dog = %{"bark" => "woof", "pet_type" => "Dog"}
- Assertions2.assert_schema(dog, "CatOrDog", OpenApiSpexTest.ApiSpec.spec())
+ TestAssertions.assert_schema(dog, "CatOrDog", OpenApiSpexTest.ApiSpec.spec())
end
end
end
diff --git a/test/server_test.exs b/test/server_test.exs
index d5555d6..bb611c0 100644
--- a/test/server_test.exs
+++ b/test/server_test.exs
@@ -1,41 +1,32 @@
defmodule OpenApiSpex.ServerTest do
use ExUnit.Case
alias OpenApiSpex.{Server}
alias OpenApiSpexTest.Endpoint
@otp_app :open_api_spex_test
describe "Server" do
test "from_endpoint/1" do
setup_endpoint()
server = Server.from_endpoint(Endpoint)
assert %{
- url: "https://example.com:1234/api/v1/"
- } = server
- end
-
- test "from_endpoint/2" do
- setup_endpoint()
-
- expected = Server.from_endpoint(Endpoint)
- actual = Server.from_endpoint(Endpoint, [opt_app: @otp_app])
-
- assert ^expected = actual
+ url: "https://example.com:1234/api/v1/"
+ } = server
end
end
defp setup_endpoint do
- Application.put_env(@otp_app, Endpoint, [
+ Application.put_env(@otp_app, Endpoint,
url: [
scheme: "https",
host: "example.com",
port: 1234,
path: "/api/v1/"
]
- ])
+ )
+
Endpoint.start_link()
end
end
-
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Nov 27, 7:37 PM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40722
Default Alt Text
(15 KB)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment