Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep discriminator errors relevant #533

Merged
merged 1 commit into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions lib/open_api_spex/cast/discriminator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,15 @@ defmodule OpenApiSpex.Cast.Discriminator do
end
end

defp cast_composition(composite_ctx, ctx, discriminator_value, mappings) do
with {composite_schemas, cast_composition_result} <- cast_composition(composite_ctx),
# When the composite key `allOf` is set we have to cast all the schemas even when the discriminator is set
defp cast_composition(
%_{schema: %{allOf: [_ | _]}} = composite_ctx,
ctx,
discriminator_value,
mappings
) do
with {composite_schemas, cast_composition_result} <-
{locate_composition_schemas(composite_ctx), Cast.cast(composite_ctx)},
{:ok, _} <- cast_composition_result,
%{} = schema <-
find_discriminator_schema(
Expand All @@ -69,17 +76,15 @@ defmodule OpenApiSpex.Cast.Discriminator do
end
end

defp cast_composition(%_{schema: %{anyOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}

defp cast_composition(%_{schema: %{allOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}

defp cast_composition(%_{schema: %{oneOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}
defp cast_composition(composite_ctx, ctx, discriminator_value, mappings) do
with composite_schemas <- locate_composition_schemas(composite_ctx),
%{} = schema <- find_discriminator_schema(discriminator_value, mappings, composite_schemas) do
Cast.cast(%{composite_ctx | schema: schema})
else
nil -> error(:invalid_discriminator_value, ctx)
{:error, _} = error -> error
end
end

defp find_discriminator_schema(discriminator, mappings = %{}, schemas) do
case Map.fetch(mappings, discriminator) do
Expand Down Expand Up @@ -109,6 +114,15 @@ defmodule OpenApiSpex.Cast.Discriminator do
defp error(message, %{schema: %{discriminator: %{propertyName: property}}} = ctx),
do: Cast.error(ctx, {message, property})

defp locate_composition_schemas(%_{schema: %{anyOf: [_ | _] = schemas}} = ctx),
do: locate_schemas(schemas, ctx.schemas)

defp locate_composition_schemas(%_{schema: %{allOf: [_ | _] = schemas}} = ctx),
do: locate_schemas(schemas, ctx.schemas)

defp locate_composition_schemas(%_{schema: %{oneOf: [_ | _] = schemas}} = ctx),
do: locate_schemas(schemas, ctx.schemas)

defp locate_schemas(schemas, ctx_schemas) do
Enum.map(schemas, fn
%Schema{} = schema ->
Expand Down
25 changes: 25 additions & 0 deletions test/cast/discriminator_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,31 @@ defmodule OpenApiSpex.CastDiscriminatorTest do
assert cast(value: input_value, schema: discriminator_schema) == expected
end

test "valid discriminator mapping but schema does not match", %{
schemas: %{dog: dog, wolf: wolf, cat: cat}
} do
# Wolf has a constraint on its "breed attribute" requiring the breed to have
# a minimum length of 5.
input_value = %{@discriminator => "Wolf", "breed" => "pug", "age" => 1}

discriminator_schema =
build_discriminator_schema([wolf, dog, cat], :oneOf, String.to_atom(@discriminator), nil)

assert {:error,
[
%OpenApiSpex.Cast.Error{
format: nil,
length: 5,
meta: %{},
name: nil,
path: [:breed],
reason: :min_length,
type: nil,
value: "pug"
}
]} = cast(value: input_value, schema: discriminator_schema)
end

test "invalid property on discriminator schema", %{
schemas: %{dog: dog, wolf: wolf}
} do
Expand Down
65 changes: 45 additions & 20 deletions test/cast/one_of_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,54 @@ defmodule OpenApiSpex.CastOneOfTest do
TestAssertions.assert_schema(dog, "Pet", OpenApiSpexTest.ApiSpec.spec())
end

test "error when value invalid against all schemas, using discriminator" do
dog = %{"fur" => "grey", "pet_type" => "Wolf"}
test "returns only relative errors when the discriminator value points to a known but invalid schema" do
dog = %{"fur" => "grey", "pet_type" => "Cat"}
api_spec = OpenApiSpexTest.ApiSpec.spec()
pet_schema = api_spec.components.schemas["Pet"]
assert {:error, [error | _]} = OpenApiSpex.cast_value(dog, pet_schema, api_spec)

assert error == %OpenApiSpex.Cast.Error{
format: nil,
length: 0,
meta: %{
failed_schemas: [
"Schema(title: \"Dog\", type: :object)",
"Schema(title: \"Cat\", type: :object)"
],
message: "no schemas validate",
valid_schemas: []
},
name: nil,
path: [],
reason: :one_of,
type: nil,
value: %{"fur" => "grey", "pet_type" => "Wolf"}
}
assert {:error,
[
%OpenApiSpex.Cast.Error{
format: nil,
length: 0,
meta: %{invalid_schema: "object"},
name: nil,
path: [],
reason: :all_of,
type: nil,
value: %{"fur" => "grey", "pet_type" => "Cat"}
},
%OpenApiSpex.Cast.Error{
format: nil,
length: 0,
meta: %{},
name: :meow,
path: [:meow],
reason: :missing_field,
type: nil,
value: %{"fur" => "grey", "pet_type" => "Cat"}
}
]} = OpenApiSpex.cast_value(dog, pet_schema, api_spec)
end

test "error when the discriminator value points to a schema which does not exist" do
dog = %{"fur" => "grey", "pet_type" => "Pangolin"}
api_spec = OpenApiSpexTest.ApiSpec.spec()
pet_schema = api_spec.components.schemas["Pet"]

assert {:error,
[
%OpenApiSpex.Cast.Error{
format: nil,
length: 0,
meta: %{},
name: "pet_type",
path: [],
reason: :invalid_discriminator_value,
type: nil,
value: %{"fur" => "grey", "pet_type" => "Pangolin"}
}
]} = OpenApiSpex.cast_value(dog, pet_schema, api_spec)
end
end

Expand Down