Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115207
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Nov 27, 2:26 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40654
Default Alt Text
(8 KB)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment