Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/enum.ex b/lib/open_api_spex/cast/enum.ex
index 5bd35fb..336ee6c 100644
--- a/lib/open_api_spex/cast/enum.ex
+++ b/lib/open_api_spex/cast/enum.ex
@@ -1,25 +1,48 @@
defmodule OpenApiSpex.Cast.Enum do
@moduledoc false
alias OpenApiSpex.Cast
def cast(%Cast{schema: %{enum: []}} = ctx) do
Cast.error(ctx, {:invalid_enum})
end
def cast(%Cast{schema: %{enum: [value | _]}, value: value}) do
{:ok, value}
end
+ # Special case: convert binary to atom enum
def cast(ctx = %Cast{schema: schema = %{enum: [atom_value | tail]}, value: value})
when is_binary(value) and is_atom(atom_value) do
if value == to_string(atom_value) do
{:ok, atom_value}
else
cast(%{ctx | schema: %{schema | enum: tail}})
end
end
+ # Special case: convert string-keyed map to atom-keyed map enum
+ def cast(ctx = %Cast{schema: schema = %{enum: [enum_map = %{} | tail]}, value: value = %{}}) do
+ if maps_equivalent?(value, enum_map) do
+ {:ok, enum_map}
+ else
+ cast(%{ctx | schema: %{schema | enum: tail}})
+ end
+ end
+
def cast(ctx = %Cast{schema: schema = %{enum: [_ | tail]}}) do
cast(%{ctx | schema: %{schema | enum: tail}})
end
+
+ defp maps_equivalent?(x, x), do: true
+
+ # an explicit schema should be used to cast to enum of structs
+ defp maps_equivalent?(_left, %_struct{}), do: false
+
+ defp maps_equivalent?(left = %{}, right = %{}) when map_size(left) == map_size(right) do
+ Enum.all?(right, fn {k, v} ->
+ maps_equivalent?(Map.get(left, to_string(k)), v)
+ end)
+ end
+
+ defp maps_equivalent?(_left, _right), do: false
end
diff --git a/test/cast/enum_test.exs b/test/cast/enum_test.exs
new file mode 100644
index 0000000..7f78df9
--- /dev/null
+++ b/test/cast/enum_test.exs
@@ -0,0 +1,90 @@
+defmodule OpenApiSpex.Cast.EnumTest do
+ use ExUnit.Case
+ alias OpenApiSpex.{Cast, Schema}
+ alias OpenApiSpex.Cast.Error
+
+ defp cast(ctx), do: Cast.cast(ctx)
+
+ defmodule User do
+ require OpenApiSpex
+ alias __MODULE__
+
+ defstruct [:age]
+
+ def schema() do
+ %OpenApiSpex.Schema{
+ type: :object,
+ required: [:age],
+ properties: %{
+ age: %Schema{type: :integer},
+ },
+ enum: [%User{age: 32}, %User{age: 45}],
+ "x-struct": __MODULE__
+ }
+ end
+ end
+
+ describe "Enum of strings" do
+ setup do
+ {:ok, %{schema: %Schema{type: :string, enum: ["one"]}}}
+ end
+
+ test "error on invalid string", %{schema: schema} do
+ assert {:error, [error]} = cast(schema: schema, value: "two")
+ assert %Error{} = error
+ assert error.reason == :invalid_enum
+ end
+
+ test "OK on valid string", %{schema: schema} do
+ assert {:ok, "one"} = cast(schema: schema, value: "one")
+ end
+ end
+
+ describe "Enum of atoms" do
+ setup do
+ {:ok, %{schema: %Schema{type: :string, enum: [:one, :two, :three]}}}
+ end
+
+ test "string will be converted to atom", %{schema: schema} do
+ assert {:ok, :three} = cast(schema: schema, value: "three")
+ end
+
+ test "error on invalid string", %{schema: schema} do
+ assert {:error, [error]} = cast(schema: schema, value: "four")
+ assert %Error{} = error
+ assert error.reason == :invalid_enum
+ end
+ end
+
+ describe "Enum with explicit schema" do
+ test "converts string keyed map to struct" do
+ assert {:ok, %User{age: 32}} = cast(schema: User.schema(), value: %{"age" => 32})
+ end
+
+ test "Must be a valid enum value" do
+ assert {:error, [error]} = cast(schema: User.schema(), value: %{"age" => 33})
+ assert %Error{} = error
+ assert error.reason == :invalid_enum
+ end
+ end
+
+ describe "Enum without explicit schema" do
+ setup do
+ schema = %Schema{
+ type: :object,
+ enum: [%{age: 55}, %{age: 66}, %{age: 77}]
+ }
+ {:ok, %{schema: schema}}
+ end
+
+ test "casts from string keyed map", %{schema: schema} do
+ assert {:ok, %{age: 55}} = cast(value: %{"age" => 55}, schema: schema)
+ end
+
+ test "value must be a valid enum value", %{schema: schema} do
+ assert {:error, [error]} = cast(value: %{"age" => 56}, schema: schema)
+ assert %Error{} = error
+ assert error.reason == :invalid_enum
+ end
+ end
+end
diff --git a/test/cast_test.exs b/test/cast_test.exs
index 9cc7689..5e41091 100644
--- a/test/cast_test.exs
+++ b/test/cast_test.exs
@@ -1,247 +1,219 @@
defmodule OpenApiSpec.CastTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema, Reference}
alias OpenApiSpex.Cast.Error
doctest OpenApiSpex.Cast
def cast(ctx), do: Cast.cast(ctx)
describe "cast/1" do
test "unknown schema type" do
assert {:error, [error]} = cast(value: "string", schema: %Schema{type: :nope})
assert error.reason == :invalid_schema_type
assert error.type == :nope
assert {:error, [error]} = cast(value: "string", schema: %Schema{type: nil})
assert error.reason == :invalid_schema_type
assert error.type == nil
end
# Note: full tests for primitives are covered in Cast.PrimitiveTest
test "primitives" do
tests = [
{:string, "1", :ok},
{:string, "", :ok},
{:string, true, :invalid},
{:string, nil, :invalid},
{:integer, 1, :ok},
{:integer, "1", :ok},
{:integer, %{}, :invalid},
{:integer, nil, :invalid},
{:array, nil, :invalid},
{:object, nil, :invalid}
]
for {type, input, expected} <- tests do
case expected do
:ok -> assert {:ok, _} = cast(value: input, schema: %Schema{type: type})
:invalid -> assert {:error, _} = cast(value: input, schema: %Schema{type: type})
end
end
end
test "array type, nullable, given nil" do
schema = %Schema{type: :array, nullable: true}
assert {:ok, nil} = cast(value: nil, schema: schema)
end
test "array type, given nil" do
schema = %Schema{type: :array}
assert {:error, [error]} = cast(value: nil, schema: schema)
assert error.reason == :null_value
assert Error.message_with_path(error) == "#: null value where array expected"
end
test "array" do
schema = %Schema{type: :array}
assert cast(value: [], schema: schema) == {:ok, []}
assert cast(value: [1, 2, 3], schema: schema) == {:ok, [1, 2, 3]}
assert cast(value: ["1", "2", "3"], schema: schema) == {:ok, ["1", "2", "3"]}
assert {:error, [error]} = cast(value: %{}, schema: schema)
assert %Error{} = error
assert error.reason == :invalid_type
assert error.value == %{}
end
test "array with items schema" do
items_schema = %Schema{type: :integer}
schema = %Schema{type: :array, items: items_schema}
assert cast(value: [], schema: schema) == {:ok, []}
assert cast(value: [1, 2, 3], schema: schema) == {:ok, [1, 2, 3]}
assert cast(value: ["1", "2", "3"], schema: schema) == {:ok, [1, 2, 3]}
assert {:error, errors} = cast(value: [1, "two"], schema: schema)
assert [%Error{} = error] = errors
assert error.reason == :invalid_type
assert error.value == "two"
assert error.path == [1]
end
# Additional object tests found in Cast.ObjectTest
test "object with schema properties set, given known input property" do
schema = %Schema{
type: :object,
properties: %{age: nil}
}
assert cast(value: %{}, schema: schema) == {:ok, %{}}
assert cast(value: %{"age" => "hello"}, schema: schema) == {:ok, %{age: "hello"}}
end
test "reference" do
age_schema = %Schema{type: :integer}
assert cast(
value: "20",
schema: %Reference{"$ref": "#/components/schemas/Age"},
schemas: %{"Age" => age_schema}
) == {:ok, 20}
end
test "reference nested in object" do
age_schema = %Schema{type: :integer}
schema = %Schema{
type: :object,
properties: %{
age: %Reference{"$ref": "#/components/schemas/Age"}
}
}
assert cast(
value: %{"age" => "20"},
schema: schema,
schemas: %{"Age" => age_schema}
) == {:ok, %{age: 20}}
end
test "paths" do
schema = %Schema{
type: :object,
properties: %{
age: %Schema{type: :integer}
}
}
assert {:error, errors} = cast(value: %{"age" => "twenty"}, schema: schema)
assert [error] = errors
assert %Error{} = error
assert error.path == [:age]
end
test "nested paths" do
schema = %Schema{
type: :object,
properties: %{
data: %Schema{
type: :object,
properties: %{
age: %Schema{type: :integer}
}
}
}
}
assert {:error, errors} = cast(value: %{"data" => %{"age" => "twenty"}}, schema: schema)
assert [error] = errors
assert %Error{} = error
assert error.path == [:data, :age]
assert Error.message_with_path(error) == "#/data/age: Invalid integer. Got: string"
end
test "paths involving arrays" do
schema = %Schema{
type: :object,
properties: %{
data: %Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
age: %Schema{type: :integer}
}
}
}
}
}
assert {:error, errors} =
cast(value: %{"data" => [%{"age" => "20"}, %{"age" => "twenty"}]}, schema: schema)
assert [error] = errors
assert %Error{} = error
assert error.path == [:data, 1, :age]
assert Error.message_with_path(error) == "#/data/1/age: Invalid integer. Got: string"
end
test "multiple errors" do
schema = %Schema{
type: :array,
items: %Schema{type: :integer}
}
value = [1, "two", 3, "four"]
assert {:error, errors} = cast(value: value, schema: schema)
assert [error, error2] = errors
assert %Error{} = error
assert error.reason == :invalid_type
assert error.path == [1]
assert Error.message_with_path(error) == "#/1: Invalid integer. Got: string"
assert Error.message_with_path(error2) == "#/3: Invalid integer. Got: string"
end
-
- test "enum - invalid" do
- schema = %Schema{type: :string, enum: ["one"]}
- assert {:error, [error]} = cast(value: "two", schema: schema)
-
- assert %Error{} = error
- assert error.reason == :invalid_enum
- end
-
- test "enum - valid" do
- schema = %Schema{type: :string, enum: ["one"]}
- assert {:ok, "one"} = cast(value: "one", schema: schema)
- end
-
- test "enum - atoms" do
- schema = %Schema{type: :string, enum: [:one, :two, :three]}
- assert {:ok, :three} = cast(value: "three", schema: schema)
- end
-
- test "enum - atom keyed map" do
- schema = %Schema{
- type: :object,
- properties: %{age: %Schema{type: :integer}},
- enum: [%{age: 10}, %{age: 12}, %{age: 18}]
- }
-
- assert {:ok, %{age: 12}} = cast(value: %{"age" => 12}, schema: schema)
- end
end
describe "ok/1" do
test "basics" do
assert {:ok, 1} = Cast.ok(%Cast{value: 1})
end
end
describe "success/2" do
test "nils out property" do
schema = %Schema{minimum: 1}
ctx = %Cast{schema: schema}
expected = {:cast, %Cast{schema: %Schema{minimum: nil}}}
assert expected == Cast.success(ctx, :minimum)
end
test "nils out properties" do
schema = %Schema{minimum: 1, exclusiveMinimum: true}
ctx = %Cast{schema: schema}
expected = {:cast, %Cast{schema: %Schema{minimum: nil, exclusiveMinimum: nil}}}
assert expected == Cast.success(ctx, [:minimum, :exclusiveMinimum])
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 29, 6:44 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41275
Default Alt Text
(11 KB)

Event Timeline