Page MenuHomePhorge

No OneTemporary

Size
15 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 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

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)

Event Timeline