Page MenuHomePhorge

No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/all_of.ex b/lib/open_api_spex/cast/all_of.ex
index 169d031..a536f65 100644
--- a/lib/open_api_spex/cast/all_of.ex
+++ b/lib/open_api_spex/cast/all_of.ex
@@ -1,23 +1,84 @@
defmodule OpenApiSpex.Cast.AllOf do
@moduledoc false
alias OpenApiSpex.Cast
+ alias OpenApiSpex.Schema
- def cast(ctx),
- do: cast_all_of(ctx, nil)
+ def cast(ctx), do: cast_all_of(ctx, nil)
- defp cast_all_of(%{schema: %{type: _, allOf: [schema | remaining]}} = ctx, result) do
+ defp cast_all_of(%{schema: %{allOf: [%Schema{type: :array} = schema | remaining]}} = ctx, acc)
+ when is_list(acc) or acc == nil do
+ # Since we parse a multi-type array, acc has to be a list or nil
+ acc = acc || []
+
+ case Cast.cast(%{ctx | schema: schema}) do
+ {:ok, value} when is_list(value) ->
+ # Since the cast for the list didn't result in a cast error,
+ # we do not proceed the values through the remaining schemas
+ {:ok, Enum.concat(acc, value)}
+
+ {:error, errors} ->
+ with {:ok, cleaned_ctx} <- reject_error_values(ctx, errors) do
+ case Cast.cast(cleaned_ctx) do
+ {:ok, cleaned_values} ->
+ new_ctx = put_in(ctx.schema.allOf, remaining)
+ new_ctx = update_in(new_ctx.value, fn values -> values -- cleaned_ctx.value end)
+ cast_all_of(new_ctx, Enum.concat(acc, cleaned_values))
+ end
+ else
+ _ -> Cast.error(ctx, {:all_of, to_string(schema.title || schema.type)})
+ end
+ end
+ end
+
+ defp cast_all_of(%{schema: %{allOf: [%Schema{} = schema | remaining]}} = ctx, acc) do
schema = OpenApiSpex.resolve_schema(schema, ctx.schemas)
relaxed_schema = %{schema | additionalProperties: true}
+ new_ctx = put_in(ctx.schema.allOf, remaining)
- with {:ok, value} <- Cast.cast(%{ctx | schema: relaxed_schema}) do
- new_schema = %{ctx.schema | allOf: remaining}
- cast_all_of(%{ctx | schema: new_schema}, result || {:ok, value})
- else
- _ -> Cast.error(ctx, {:all_of, to_string(schema.title || schema.type)})
+ case Cast.cast(%{ctx | schema: relaxed_schema}) do
+ {:ok, value} when is_map(value) ->
+ cast_all_of(new_ctx, Map.merge(acc || %{}, value))
+
+ {:ok, value} when acc == nil ->
+ # primitive value with no previous (valid) casts -> return
+ {:ok, value}
+
+ {:error, _} when remaining == [] ->
+ # Since no schema is left to parse the remaining values, we return a error
+ Cast.error(ctx, {:all_of, to_string(relaxed_schema.title || relaxed_schema.type)})
+
+ {:error, _} ->
+ # in case the cast results in a error, we just skip this schema
+ cast_all_of(new_ctx, acc)
end
end
- defp cast_all_of(_, {:ok, result}) do
- {:ok, result}
+ defp cast_all_of(%{schema: %{allOf: [schema | remaining]}} = ctx, result) do
+ schema = OpenApiSpex.resolve_schema(schema, ctx.schemas)
+ cast_all_of(%{ctx | schema: %{allOf: [schema | remaining]}}, result)
+ end
+
+ defp cast_all_of(%{schema: schema} = ctx, nil) do
+ Cast.error(ctx, {:all_of, to_string(schema.title || schema.type)})
+ end
+
+ defp cast_all_of(%{schema: %{allOf: []}}, acc) do
+ # All values have been casted against the allOf schemas - return accumulator
+ {:ok, acc}
+ end
+
+ defp reject_error_values(%{value: values} = ctx, [%{reason: :invalid_type} = error | tail]) do
+ new_values = List.delete(values, error.value)
+ reject_error_values(%{ctx | value: new_values}, tail)
+ end
+
+ defp reject_error_values(ctx, []) do
+ # All errors should now be resolved for the current schema
+ {:ok, ctx}
+ end
+
+ defp reject_error_values(_ctx, errors) do
+ # Some errors couldn't be resolved, we break and return the remaining errors
+ errors
end
end
diff --git a/test/cast/all_of_test.exs b/test/cast/all_of_test.exs
index 236a4ab..9bb9217 100644
--- a/test/cast/all_of_test.exs
+++ b/test/cast/all_of_test.exs
@@ -1,36 +1,79 @@
defmodule OpenApiSpex.CastAllOfTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema}
alias OpenApiSpex.Cast.{Error, AllOf}
alias OpenApiSpex.Test.Assertions2
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."
+ "Failed to cast value as string. 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())
end
+
+ test "allOf, for inheritance schema" do
+ schema = %Schema{
+ allOf: [
+ %Schema{
+ type: :object,
+ additionalProperties: true,
+ properties: %{
+ id: %Schema{
+ type: :string
+ }
+ }
+ },
+ %Schema{
+ type: :object,
+ additionalProperties: true,
+ properties: %{
+ bar: %Schema{
+ type: :string
+ }
+ }
+ }
+ ]
+ }
+
+ value = %{id: "e30aee0f-dbda-40bd-9198-6cf609b8b640", bar: "foo"}
+
+ assert {:ok, %{id: "e30aee0f-dbda-40bd-9198-6cf609b8b640", bar: "foo"}} =
+ cast(value: value, schema: schema)
+ end
+ end
+
+ test "allOf, for multi-type array" do
+ schema = %Schema{
+ allOf: [
+ %Schema{type: :array, items: %Schema{type: :integer}},
+ %Schema{type: :array, items: %Schema{type: :boolean}},
+ %Schema{type: :array, items: %Schema{type: :string}}
+ ]
+ }
+
+ value = ["Test #1", "2", "3", "4", "true", "Five!"]
+ assert {:ok, [2, 3, 4, true, "Test #1", "Five!"]} = cast(value: value, schema: schema)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Wed, Nov 27, 8:40 PM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40746
Default Alt Text
(6 KB)

Event Timeline