Page MenuHomePhorge

No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/one_of.ex b/lib/open_api_spex/cast/one_of.ex
index 92d89f5..25d3592 100644
--- a/lib/open_api_spex/cast/one_of.ex
+++ b/lib/open_api_spex/cast/one_of.ex
@@ -1,51 +1,51 @@
defmodule OpenApiSpex.Cast.OneOf do
@moduledoc false
alias OpenApiSpex.Cast
alias OpenApiSpex.Schema
def cast(%_{schema: %{type: _, oneOf: []}} = ctx) do
error(ctx, [])
end
def cast(%{schema: %{type: _, oneOf: schemas}} = ctx) do
castable_schemas =
Enum.reduce(schemas, {[], 0}, fn schema, {results, count} ->
schema = OpenApiSpex.resolve_schema(schema, ctx.schemas)
- case Cast.cast(%{ctx | schema: %{schema | anyOf: nil}}) do
+ case Cast.cast(%{ctx | schema: %{schema | anyOf: nil, additionalProperties: false}}) do
{:ok, value} -> {[{:ok, value, schema} | results], count + 1}
_ -> {results, count}
end
end)
case castable_schemas do
{[{:ok, %_{} = value, _}], 1} -> {:ok, value}
{[{:ok, value, %Schema{"x-struct": nil}}], 1} -> {:ok, value}
{[{:ok, value, %Schema{"x-struct": module}}], 1} -> {:ok, struct(module, value)}
{failed_schemas, _count} -> error(ctx, failed_schemas)
end
end
## Private functions
defp error(ctx, failed_schemas) do
Cast.error(ctx, {:one_of, error_message(failed_schemas)})
end
defp error_message([]) do
"[] (no schemas provided)"
end
defp error_message(failed_schemas) do
for {:ok, _value, schema} <- failed_schemas do
case schema do
%{title: title, type: type} when not is_nil(title) ->
"Schema(title: #{inspect(title)}, type: #{inspect(type)})"
%{type: type} ->
"Schema(type: #{inspect(type)})"
end
end
|> Enum.join(", ")
end
end
diff --git a/test/cast/one_of_test.exs b/test/cast/one_of_test.exs
index 5883c85..8d06081 100644
--- a/test/cast/one_of_test.exs
+++ b/test/cast/one_of_test.exs
@@ -1,37 +1,60 @@
defmodule OpenApiSpex.CastOneOfTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema}
alias OpenApiSpex.Cast.{Error, OneOf}
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
+ test "objects, using discriminator" do
dog = %{"bark" => "woof", "pet_type" => "Dog"}
TestAssertions.assert_schema(dog, "CatOrDog", OpenApiSpexTest.ApiSpec.spec())
end
end
+
+ describe "oneOf multiple objects" do
+ alias OpenApiSpexTest.{OneOfSchemas, OneOfSchemas.Dog}
+ @api_spec OneOfSchemas.spec()
+ @cat_or_dog_schema @api_spec.components.schemas["CatOrDog"]
+
+ test "matches perfectly with one schema" do
+ input = %{"bark" => true, "breed" => "Dingo"}
+
+ assert {:ok, %Dog{bark: true, breed: "Dingo"}} =
+ OpenApiSpex.cast_value(input, @cat_or_dog_schema, @api_spec)
+ end
+
+ test "should be invalid (not valid against both schemas)" do
+ input = %{"bark" => true, "meow" => true}
+ assert {:error, _} = OpenApiSpex.cast_value(input, @cat_or_dog_schema, @api_spec)
+ end
+
+ test "should be invalid (valid against both)" do
+ input = %{"bark" => true, "meow" => true, "breed" => "Husky", "age" => 3}
+ assert {:error, _} = OpenApiSpex.cast_value(input, @cat_or_dog_schema, @api_spec)
+ end
+ end
end
diff --git a/test/support/one_of_schemas.ex b/test/support/one_of_schemas.ex
new file mode 100644
index 0000000..4a9dd9d
--- /dev/null
+++ b/test/support/one_of_schemas.ex
@@ -0,0 +1,37 @@
+defmodule OpenApiSpexTest.OneOfSchemas do
+ alias OpenApiSpex.Schema
+ require OpenApiSpex
+
+ defmodule Cat do
+ OpenApiSpex.schema(%{
+ title: "Cat",
+ type: :object,
+ properties: %{
+ meow: %Schema{type: :boolean},
+ age: %Schema{type: :integer}
+ }
+ })
+ end
+
+ defmodule Dog do
+ OpenApiSpex.schema(%{
+ title: "Dog",
+ type: :object,
+ properties: %{
+ bark: %Schema{type: :boolean},
+ breed: %Schema{type: :string, enum: ["Dingo", "Husky", "Retriever", "Shepherd"]}
+ }
+ })
+ end
+
+ defmodule CatOrDog do
+ OpenApiSpex.schema(%{
+ title: "CatOrDog",
+ oneOf: [Cat, Dog]
+ })
+ end
+
+ def spec() do
+ OpenApiSpexTest.OpenApi.build([Cat, Dog, CatOrDog])
+ end
+end
diff --git a/test/support/open_api.ex b/test/support/open_api.ex
new file mode 100644
index 0000000..687555e
--- /dev/null
+++ b/test/support/open_api.ex
@@ -0,0 +1,22 @@
+defmodule OpenApiSpexTest.OpenApi do
+ alias OpenApiSpex.{Components, Info, OpenApi}
+
+ @doc """
+ Build an %OpenApi{} struct from a list of schema modules.
+ """
+ @spec build(schemas :: [module]) :: OpenApi.t()
+ def build(schemas) do
+ schemas_map =
+ for module <- schemas, into: %{} do
+ {module.schema().title, module.schema()}
+ end
+
+ info = %Info{
+ title: "Test schema",
+ version: "1.0.0"
+ }
+
+ %OpenApi{info: info, paths: %{}, components: %Components{schemas: schemas_map}}
+ |> OpenApiSpex.resolve_schema_modules()
+ end
+end
diff --git a/test/support/pet_controller.ex b/test/support/pet_controller.ex
index 2e01a52..f6991ac 100644
--- a/test/support/pet_controller.ex
+++ b/test/support/pet_controller.ex
@@ -1,90 +1,90 @@
defmodule OpenApiSpexTest.PetController do
use Phoenix.Controller
alias OpenApiSpex.Operation
alias OpenApiSpexTest.Schemas
plug OpenApiSpex.Plug.CastAndValidate
def open_api_operation(action) do
apply(__MODULE__, :"#{action}_operation", [])
end
@doc """
API Spec for :show action
"""
def show_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "Show pet",
description: "Show a pet by ID",
operationId: "PetController.show",
parameters: [
parameter(:id, :path, :integer, "Pet ID", example: 123, minimum: 1)
],
responses: %{
200 => response("Pet", "application/json", Schemas.PetResponse)
}
}
end
- def show(conn, %{id: id}) do
+ def show(conn, %{id: _id}) do
json(conn, %Schemas.PetResponse{
data: %Schemas.Dog{
pet_type: "Dog",
bark: "woof"
}
})
end
def index_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "List pets",
description: "List all petes",
operationId: "PetController.index",
parameters: [
parameter(:validParam, :query, :boolean, "Valid Param", example: true)
],
responses: %{
200 => response("Pet List Response", "application/json", Schemas.PetsResponse)
}
}
end
def index(conn, _params) do
json(conn, %Schemas.PetsResponse{
data: [
%Schemas.Dog{
pet_type: "Dog",
bark: "joe@gmail.com"
}
]
})
end
def create_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "Create pet",
description: "Create a pet",
operationId: "PetController.create",
parameters: [],
requestBody: request_body("The pet attributes", "application/json", Schemas.PetRequest),
responses: %{
201 => response("Pet", "application/json", Schemas.PetRequest)
}
}
end
def create(conn = %{body_params: %Schemas.PetRequest{pet: pet}}, _) do
json(conn, %Schemas.PetResponse{
data: pet
})
end
end

File Metadata

Mime Type
text/x-diff
Expires
Wed, Nov 27, 4:32 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40654
Default Alt Text
(8 KB)

Event Timeline