Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/object.ex b/lib/open_api_spex/cast/object.ex
index 9c83a93..a87c067 100644
--- a/lib/open_api_spex/cast/object.ex
+++ b/lib/open_api_spex/cast/object.ex
@@ -1,128 +1,145 @@
defmodule OpenApiSpex.Cast.Object do
@moduledoc false
alias OpenApiSpex.Cast
alias OpenApiSpex.Cast.Error
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}}) do
{:ok, value}
end
def cast(%{value: value, schema: schema} = ctx) do
schema_properties = schema.properties || %{}
with :ok <- check_unrecognized_properties(ctx, schema_properties),
value = cast_atom_keys(value, schema_properties),
ctx = %{ctx | value: value},
:ok <- check_required_fields(ctx, schema),
:ok <- check_max_properties(ctx),
:ok <- check_min_properties(ctx),
{:ok, value} <- cast_properties(%{ctx | schema: schema_properties}) do
- ctx = to_struct(%{ctx | value: value})
+ value_with_defaults = apply_defaults(value, schema_properties)
+ ctx = to_struct(%{ctx | value: value_with_defaults})
{:ok, ctx}
end
end
# When additionalProperties is true, extra properties are allowed in input
defp check_unrecognized_properties(%{schema: %{additionalProperties: true}}, _expected_keys) do
:ok
end
defp check_unrecognized_properties(%{value: value} = ctx, expected_keys) do
input_keys = value |> Map.keys() |> Enum.map(&to_string/1)
schema_keys = expected_keys |> Map.keys() |> Enum.map(&to_string/1)
extra_keys = input_keys -- schema_keys
if extra_keys == [] do
:ok
else
[name | _] = extra_keys
ctx = %{ctx | path: [name | ctx.path]}
Cast.error(ctx, {:unexpected_field, name})
end
end
defp check_required_fields(%{value: input_map} = ctx, schema) do
required = schema.required || []
input_keys = Map.keys(input_map)
missing_keys = required -- input_keys
if missing_keys == [] do
:ok
else
errors =
Enum.map(missing_keys, fn key ->
ctx = %{ctx | path: [key | ctx.path]}
Error.new(ctx, {:missing_field, key})
end)
{:error, ctx.errors ++ errors}
end
end
defp check_max_properties(%{schema: %{maxProperties: max_properties}} = ctx)
when is_integer(max_properties) do
count = ctx.value |> Map.keys() |> length()
if count > max_properties do
Cast.error(ctx, {:max_properties, max_properties, count})
else
:ok
end
end
defp check_max_properties(_ctx), do: :ok
defp check_min_properties(%{schema: %{minProperties: min_properties}} = ctx)
when is_integer(min_properties) do
count = ctx.value |> Map.keys() |> length()
if count < min_properties do
Cast.error(ctx, {:min_properties, min_properties, count})
else
:ok
end
end
defp check_min_properties(_ctx), do: :ok
defp cast_atom_keys(input_map, properties) do
Enum.reduce(properties, %{}, fn {key, _}, output ->
string_key = to_string(key)
case input_map do
%{^key => value} -> Map.put(output, key, value)
%{^string_key => value} -> Map.put(output, key, value)
_ -> output
end
end)
end
defp cast_properties(%{value: object, schema: schema_properties} = ctx) do
Enum.reduce(object, {:ok, %{}}, fn
{key, value}, {:ok, output} ->
cast_property(%{ctx | key: key, value: value, schema: schema_properties}, output)
_, error ->
error
end)
end
defp cast_property(%{key: key, schema: schema_properties} = ctx, output) do
prop_schema = Map.get(schema_properties, key)
path = [key | ctx.path]
with {:ok, value} <- Cast.cast(%{ctx | path: path, schema: prop_schema}) do
{:ok, Map.put(output, key, value)}
end
end
+ defp apply_defaults(object_value, schema_properties) do
+ Enum.reduce(schema_properties, object_value, &apply_default/2)
+ end
+
+ defp apply_default({_key, %{default: nil}}, object_value), do: object_value
+
+ defp apply_default({key, %{default: default_value}}, object_value) do
+ if Map.has_key?(object_value, key) do
+ object_value
+ else
+ Map.put(object_value, key, default_value)
+ end
+ end
+
+ defp apply_default(_, object_value), do: object_value
+
defp to_struct(%{value: value = %_{}}), do: value
defp to_struct(%{value: value, schema: %{"x-struct": nil}}), do: value
defp to_struct(%{value: value, schema: %{"x-struct": module}}),
do: struct(module, value)
end
diff --git a/lib/open_api_spex/cast_parameters.ex b/lib/open_api_spex/cast_parameters.ex
index 19275c8..3890a78 100644
--- a/lib/open_api_spex/cast_parameters.ex
+++ b/lib/open_api_spex/cast_parameters.ex
@@ -1,66 +1,44 @@
defmodule OpenApiSpex.CastParameters do
@moduledoc false
alias OpenApiSpex.{Cast, Operation, Parameter, Schema, Reference, Components}
alias OpenApiSpex.Cast.{Error, Object}
alias Plug.Conn
@spec cast(Plug.Conn.t(), Operation.t(), Components.t()) ::
{:error, [Error.t()]} | {:ok, Conn.t()}
def cast(conn, operation, components) do
# Taken together as a set, operation parameters are similar to an object schema type.
# Convert parameters to an object schema, then delegate to `Cast.Object.cast/1`
# Operation's parameters list may include references - resolving here
resolved_parameters =
Enum.map(operation.parameters, fn
ref = %Reference{} -> Reference.resolve_parameter(ref, components.parameters)
param = %Parameter{} -> param
end)
properties =
resolved_parameters
|> Enum.map(fn parameter -> {parameter.name, Parameter.schema(parameter)} end)
|> Map.new()
required =
resolved_parameters
|> Enum.filter(& &1.required)
|> Enum.map(& &1.name)
object_schema = %Schema{
type: :object,
properties: properties,
required: required
}
params = Map.merge(conn.path_params, conn.query_params)
ctx = %Cast{value: params, schema: object_schema, schemas: components.schemas}
with {:ok, params} <- Object.cast(ctx) do
- {:ok, %{conn | params: add_defaults(params, object_schema)}}
- end
- end
-
- # Whenever an optional parameter is missing in the request (e.g. in a query) a default
- # can be applied from API schema. The implementation is naive because:
- # * it doesn't allow specifying nil as a default
- # * it doesn't support defaults in nested parameters (if that's possible)
- # * only tested with query string parameters (obvious place for improvements)
- #
- # QUESTION: should defaults be applied in schema.ex instead?
- #
- defp add_defaults(params, schema) do
- Enum.reduce(schema.properties, params, &apply_default/2)
- end
-
- defp apply_default({_key, %Schema{default: nil}}, params), do: params
-
- defp apply_default({key, %Schema{default: value}}, params) do
- if Map.has_key?(params, key) do
- params
- else
- Map.put(params, key, value)
+ {:ok, %{conn | params: params}}
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 28, 2:38 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40803
Default Alt Text
(7 KB)

Event Timeline