Skip to content
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
6 changes: 1 addition & 5 deletions lib/open_api_spex/cast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,7 @@ defmodule OpenApiSpex.Cast do
# Enum
def cast(%__MODULE__{schema: %{enum: enum}} = ctx) when is_list(enum) do
with {:ok, value} <- cast(%{ctx | schema: %{ctx.schema | enum: nil}}) do
if value in enum do
{:ok, value}
else
error(ctx, {:invalid_enum})
end
OpenApiSpex.Cast.Enum.cast(%{ctx | value: value})
end
end

Expand Down
30 changes: 30 additions & 0 deletions lib/open_api_spex/cast/enum.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule OpenApiSpex.Cast.Enum do
@moduledoc false
alias OpenApiSpex.Cast

def cast(ctx = %Cast{schema: %{enum: enum}, value: value}) do
case Enum.find(enum, {:error, :invalid_enum}, &equivalent?(&1, value)) do
{:error, :invalid_enum} -> Cast.error(ctx, {:invalid_enum})
found -> {:ok, found}
end
end

defp equivalent?(x, x), do: true

# Special case: atoms are equivalent to their stringified representation
defp equivalent?(left, right) when is_atom(left) and is_binary(right) do
to_string(left) == right
end

# an explicit schema should be used to cast to enum of structs
defp equivalent?(_x, %_struct{}), do: false

# Special case: Atom-keyed maps are equivalent to their string-keyed representation
defp equivalent?(left, right) when is_map(left) and is_map(right) do
Enum.all?(left, fn {k, v} ->
equivalent?(v, Map.get(right, to_string(k)))
end)
end

defp equivalent?(_left, _right), do: false
end
90 changes: 90 additions & 0 deletions test/cast/enum_test.exs
Original file line number Diff line number Diff line change
@@ -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
13 changes: 0 additions & 13 deletions test/cast_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,6 @@ defmodule OpenApiSpec.CastTest do

assert Error.message_with_path(error2) == "#/3: Invalid integer. Got: string"
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests moved into the new test/cast/enum_test.exs file

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
end

describe "ok/1" do
Expand Down