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