Skip to content
Closed
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
33 changes: 30 additions & 3 deletions lib/open_api_spex/cast/object.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
defmodule OpenApiSpex.Cast.Object do
@moduledoc false
alias OpenApiSpex.Cast
alias OpenApiSpex.Cast.Utils
alias OpenApiSpex.Cast.{Error, Utils}
alias OpenApiSpex.Reference

def cast(%{value: value} = ctx) when not is_map(value) do
Cast.error(ctx, {:invalid_type, :object})
end

def cast(%{value: value, schema: %{properties: nil, additionalProperties: nil}}) do
{:ok, value}
def cast(%{value: value, schema: %{properties: nil, additionalProperties: nil}} = ctx) do
with :ok <- check_required_by_name(ctx, value) do
{:ok, value}
end
end

def cast(ctx) do
Expand Down Expand Up @@ -50,6 +52,31 @@ defmodule OpenApiSpex.Cast.Object do
defp resolve_property_if_reference(_not_a_reference, properties, _schemas), do: properties

# When additionalProperties is not false, extra properties are allowed in input
# `required` on a schema without `properties` cannot rely on key
# atomization, so a required name counts as present when the input carries
# the atom key or its string form.
defp check_required_by_name(ctx, input_map) do
required = Map.get(ctx.schema, :required) || []

missing =
Enum.reject(required, fn key ->
Map.has_key?(input_map, key) or Map.has_key?(input_map, to_string(key))
end)

case missing do
[] ->
:ok

_ ->
errors =
Enum.map(missing, fn key ->
Error.new(%{ctx | path: [key | ctx.path]}, {:missing_field, key})
end)

{:error, ctx.errors ++ errors}
end
end

defp check_unrecognized_properties(%{schema: %{additionalProperties: ap}}) when ap != false do
:ok
end
Expand Down
11 changes: 11 additions & 0 deletions test/cast/object_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ defmodule OpenApiSpex.ObjectTest do
defp cast(ctx), do: Object.cast(struct(Cast, ctx))

describe "cast/3" do
test "required is enforced on a schema without properties" do
schema = %Schema{type: :object, required: [:one]}

assert {:error, [error]} = cast(value: %{}, schema: schema)
assert error.reason == :missing_field
assert error.name == :one

assert {:ok, %{"one" => 1}} = cast(value: %{"one" => 1}, schema: schema)
assert {:ok, %{one: 1}} = cast(value: %{one: 1}, schema: schema)
end

test "when input is not an object" do
schema = %Schema{type: :object}
assert {:error, [error]} = cast(value: ["hello"], schema: schema)
Expand Down