Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115395
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment