Page MenuHomePhorge

No OneTemporary

Size
37 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex.ex b/lib/open_api_spex.ex
index 3e4522c..88d583a 100644
--- a/lib/open_api_spex.ex
+++ b/lib/open_api_spex.ex
@@ -1,139 +1,140 @@
defmodule OpenApiSpex do
@moduledoc """
Provides the entry-points for defining schemas, validating and casting.
"""
- alias OpenApiSpex.{CastError, OpenApi, Operation, Operation2, Reference, Schema, SchemaResolver}
+ alias OpenApiSpex.{OpenApi, Operation, Operation2, Reference, Schema, SchemaResolver}
+ alias OpenApiSpex.Cast.Error
@doc """
Adds schemas to the api spec from the modules specified in the Operations.
Eg, if the response schema for an operation is defined with:
responses: %{
200 => Operation.response("User", "application/json", UserResponse)
}
Then the `UserResponse.schema()` function will be called to load the schema, and
a `Reference` to the loaded schema will be used in the operation response.
See `OpenApiSpex.schema` macro for a convenient syntax for defining schema modules.
"""
@spec resolve_schema_modules(OpenApi.t) :: OpenApi.t
def resolve_schema_modules(spec = %OpenApi{}) do
SchemaResolver.resolve_schema_modules(spec)
end
def cast_and_validate(
spec = %OpenApi{},
operation = %Operation{},
conn = %Plug.Conn{},
content_type \\ nil
) do
Operation2.cast(operation, conn, content_type, spec.components.schemas)
end
@doc """
Cast params to conform to a `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.cast/3` for additional examples and details.
"""
@spec cast(OpenApi.t, Schema.t | Reference.t, any) :: {:ok, any} | {:error, String.t}
def cast(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
def cast(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
@doc """
Cast all params in `Plug.Conn` to conform to the schemas for `OpenApiSpex.Operation`.
Returns `{:ok, Plug.Conn.t}` with `params` and `body_params` fields updated if successful,
or `{:error, reason}` if casting fails.
`content_type` may optionally be supplied to select the `requestBody` schema.
"""
@spec cast(OpenApi.t, Operation.t, Plug.Conn.t, content_type | nil) :: {:ok, Plug.Conn.t} | {:error, String.t}
when content_type: String.t
def cast(spec = %OpenApi{}, operation = %Operation{}, conn = %Plug.Conn{}, content_type \\ nil) do
Operation.cast(operation, conn, content_type, spec.components.schemas)
end
@doc """
Validate params against `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.validate/3` for examples of error messages.
"""
@spec validate(OpenApi.t, Schema.t | Reference.t, any) :: :ok | {:error, String.t}
def validate(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
def validate(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
@doc """
Validate all params in `Plug.Conn` against `OpenApiSpex.Operation` `parameter` and `requestBody` schemas.
`content_type` may be optionally supplied to select the `requestBody` schema.
"""
@spec validate(OpenApi.t, Operation.t, Plug.Conn.t, content_type | nil) :: :ok | {:error, String.t}
when content_type: String.t
def validate(spec = %OpenApi{}, operation = %Operation{}, conn = %Plug.Conn{}, content_type \\ nil) do
Operation.validate(operation, conn, content_type, spec.components.schemas)
end
- def path_to_string(%CastError{} = error) do
- CastError.path_to_string(error)
+ def path_to_string(%Error{} = error) do
+ Error.path_to_string(error)
end
@doc """
Declares a struct based `OpenApiSpex.Schema`
- defines the schema/0 callback
- ensures the schema is linked to the module by "x-struct" extension property
- defines a struct with keys matching the schema properties
- defines a @type `t` for the struct
- derives a `Poison.Encoder` for the struct
See `OpenApiSpex.Schema` for additional examples and details.
## Example
require OpenApiSpex
defmodule User do
OpenApiSpex.schema %{
title: "User",
description: "A user of the app",
type: :object,
properties: %{
id: %Schema{type: :integer, description: "User ID"},
name: %Schema{type: :string, description: "User name", pattern: ~r/[a-zA-Z][a-zA-Z0-9_]+/},
email: %Schema{type: :string, description: "Email address", format: :email},
inserted_at: %Schema{type: :string, description: "Creation timestamp", format: :'date-time'},
updated_at: %Schema{type: :string, description: "Update timestamp", format: :'date-time'}
},
required: [:name, :email],
example: %{
"id" => 123,
"name" => "Joe User",
"email" => "joe@gmail.com",
"inserted_at" => "2017-09-12T12:34:55Z",
"updated_at" => "2017-09-13T10:11:12Z"
}
}
end
"""
defmacro schema(body) do
quote do
@behaviour OpenApiSpex.Schema
@schema struct(OpenApiSpex.Schema, Map.put(unquote(body), :"x-struct", __MODULE__))
def schema, do: @schema
@derive [Poison.Encoder]
defstruct Schema.properties(@schema)
@type t :: %__MODULE__{}
end
end
end
diff --git a/lib/open_api_spex/cast.ex b/lib/open_api_spex/cast.ex
index 9af2f38..c68fa7b 100644
--- a/lib/open_api_spex/cast.ex
+++ b/lib/open_api_spex/cast.ex
@@ -1,57 +1,58 @@
defmodule OpenApiSpex.Cast do
- alias OpenApiSpex.{CastArray, CastContext, CastObject, CastPrimitive, Reference}
+ alias OpenApiSpex.Reference
+ alias OpenApiSpex.Cast.{Array, Context, Object, Primitive}
@primitives [:boolean, :integer, :number, :string]
def cast(schema, value, schemas) do
- ctx = %CastContext{schema: schema, value: value, schemas: schemas}
+ ctx = %Context{schema: schema, value: value, schemas: schemas}
cast(ctx)
end
# nil schema
- def cast(%CastContext{value: value, schema: nil}),
+ def cast(%Context{value: value, schema: nil}),
do: {:ok, value}
- def cast(%CastContext{schema: %Reference{}} = ctx) do
+ def cast(%Context{schema: %Reference{}} = ctx) do
schema = Reference.resolve_schema(ctx.schema, ctx.schemas)
cast(%{ctx | schema: schema})
end
# nullable: true
- def cast(%CastContext{value: nil, schema: %{nullable: true}}) do
+ def cast(%Context{value: nil, schema: %{nullable: true}}) do
{:ok, nil}
end
# nullable: false
- def cast(%CastContext{value: nil} = ctx) do
- CastContext.error(ctx, {:null_value})
+ def cast(%Context{value: nil} = ctx) do
+ Context.error(ctx, {:null_value})
end
# Enum
- def cast(%CastContext{schema: %{enum: []}} = ctx) do
+ def cast(%Context{schema: %{enum: []}} = ctx) do
cast(%{ctx | enum: nil})
end
# Enum
- def cast(%CastContext{schema: %{enum: enum}} = ctx) when is_list(enum) do
+ def cast(%Context{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
- CastContext.error(ctx, {:invalid_enum})
+ Context.error(ctx, {:invalid_enum})
end
end
end
## Specific types
- def cast(%CastContext{schema: %{type: type}} = ctx) when type in @primitives,
- do: CastPrimitive.cast(ctx)
+ def cast(%Context{schema: %{type: type}} = ctx) when type in @primitives,
+ do: Primitive.cast(ctx)
- def cast(%CastContext{schema: %{type: :array}} = ctx),
- do: CastArray.cast(ctx)
+ def cast(%Context{schema: %{type: :array}} = ctx),
+ do: Array.cast(ctx)
- def cast(%CastContext{schema: %{type: :object}} = ctx),
- do: CastObject.cast(ctx)
+ def cast(%Context{schema: %{type: :object}} = ctx),
+ do: Object.cast(ctx)
- def cast(%{} = ctx), do: cast(struct(CastContext, ctx))
+ def cast(%{} = ctx), do: cast(struct(Context, ctx))
end
diff --git a/lib/open_api_spex/cast_array.ex b/lib/open_api_spex/cast/array.ex
similarity index 73%
rename from lib/open_api_spex/cast_array.ex
rename to lib/open_api_spex/cast/array.ex
index 18b0727..0bfc2d7 100644
--- a/lib/open_api_spex/cast_array.ex
+++ b/lib/open_api_spex/cast/array.ex
@@ -1,36 +1,39 @@
-defmodule OpenApiSpex.CastArray do
+defmodule OpenApiSpex.Cast.Array do
@moduledoc false
- alias OpenApiSpex.{Cast, CastContext}
+ alias OpenApiSpex.Cast
+ alias OpenApiSpex.Cast.Context
+ @spec cast(atom() | %{value: any()}) ::
+ {:error, nonempty_maybe_improper_list()} | {:ok, [any()]}
def cast(%{value: []}), do: {:ok, []}
def cast(%{value: items} = ctx) when is_list(items) do
case cast_items(ctx) do
{items, []} -> {:ok, items}
{_, errors} -> {:error, errors}
end
end
def cast(ctx),
- do: CastContext.error(ctx, {:invalid_type, :array})
+ do: Context.error(ctx, {:invalid_type, :array})
## Private functions
defp cast_items(%{value: items} = ctx) do
cast_results =
items
|> Enum.with_index()
|> Enum.map(fn {item, index} ->
path = [index | ctx.path]
Cast.cast(%{ctx | value: item, schema: ctx.schema.items, path: path})
end)
errors =
for({:error, errors} <- cast_results, do: errors)
|> Enum.concat()
items = for {:ok, item} <- cast_results, do: item
{items, errors}
end
end
diff --git a/lib/open_api_spex/cast_context.ex b/lib/open_api_spex/cast/context.ex
similarity index 68%
rename from lib/open_api_spex/cast_context.ex
rename to lib/open_api_spex/cast/context.ex
index d463292..21dcf1e 100644
--- a/lib/open_api_spex/cast_context.ex
+++ b/lib/open_api_spex/cast/context.ex
@@ -1,16 +1,16 @@
-defmodule OpenApiSpex.CastContext do
- alias OpenApiSpex.CastError
+defmodule OpenApiSpex.Cast.Context do
+ alias OpenApiSpex.Cast.Error
defstruct value: nil,
schema: nil,
schemas: %{},
path: [],
key: nil,
index: 0,
errors: []
def error(ctx, error_args) do
- error = CastError.new(ctx, error_args)
+ error = Error.new(ctx, error_args)
{:error, [error | ctx.errors]}
end
end
diff --git a/lib/open_api_spex/cast_error.ex b/lib/open_api_spex/cast/error.ex
similarity index 95%
rename from lib/open_api_spex/cast_error.ex
rename to lib/open_api_spex/cast/error.ex
index 7d809eb..cabb69c 100644
--- a/lib/open_api_spex/cast_error.ex
+++ b/lib/open_api_spex/cast/error.ex
@@ -1,123 +1,123 @@
-defmodule OpenApiSpex.CastError do
+defmodule OpenApiSpex.Cast.Error do
alias OpenApiSpex.TermType
defstruct reason: nil,
value: nil,
format: nil,
type: nil,
name: nil,
path: [],
length: 0
def new(ctx, {:null_value}) do
type = ctx.schema && ctx.schema.type
%__MODULE__{reason: :null_value, type: type}
|> add_context_fields(ctx)
end
def new(ctx, {:min_length, length}) do
%__MODULE__{reason: :min_length, length: length}
|> add_context_fields(ctx)
end
def new(ctx, {:invalid_type, type}) do
%__MODULE__{reason: :invalid_type, type: type}
|> add_context_fields(ctx)
end
def new(ctx, {:invalid_format, format}) do
%__MODULE__{reason: :invalid_format, format: format}
|> add_context_fields(ctx)
end
def new(ctx, {:invalid_enum}) do
%__MODULE__{reason: :invalid_enum}
|> add_context_fields(ctx)
end
def new(ctx, {:unexpected_field, name}) do
%__MODULE__{reason: :unexpected_field, name: name}
|> add_context_fields(ctx)
end
def new(ctx, {:missing_field, name}) do
%__MODULE__{reason: :missing_field, name: name}
|> add_context_fields(ctx)
end
def message(%{reason: :null_value} = error) do
case error.type do
nil -> "null value"
type -> "null value where #{type} expected"
end
end
def message(%{reason: :min_length, length: length}) do
"String length is smaller than minLength: #{length}"
end
def message(%{reason: :invalid_type, type: type, value: value}) do
"Invalid #{type}. Got: #{TermType.type(value)}"
end
def message(%{reason: :invalid_format, format: format}) do
"Invalid format. Expected #{inspect(format)}"
end
def message(%{reason: :invalid_enum}) do
"Invalid value for enum"
end
def message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
"Failed to cast to any schema in #{polymorphic_type}"
end
def message(%{reason: :unexpected_field, name: name}) do
"Unexpected field: #{safe_string(name)}"
end
def message(%{reason: :no_value_required_for_discriminator, name: field}) do
"No value for required disciminator property: #{field}"
end
def message(%{reason: :unknown_schema, name: name}) do
"Unknown schema: #{name}"
end
def message(%{reason: :missing_field, name: name}) do
"Missing field: #{name}"
end
def message_with_path(error) do
prepend_path(error, message(error))
end
def path_to_string(%{path: path} = _error) do
"/" <> (path |> Enum.map(&to_string/1) |> Path.join())
end
defp add_context_fields(error, ctx) do
%{error | path: Enum.reverse(ctx.path), value: ctx.value}
end
defp prepend_path(error, message) do
path =
case error.path do
[] -> "#"
_ -> "#" <> path_to_string(error)
end
path <> ": " <> message
end
defp safe_string(string) do
to_string(string) |> String.slice(0..39)
end
end
-defimpl String.Chars, for: OpenApiSpex.CastError do
+defimpl String.Chars, for: OpenApiSpex.Cast.Error do
def to_string(error) do
- OpenApiSpex.CastError.message(error)
+ OpenApiSpex.Cast.Error.message(error)
end
end
diff --git a/lib/open_api_spex/cast_object.ex b/lib/open_api_spex/cast/object.ex
similarity index 90%
rename from lib/open_api_spex/cast_object.ex
rename to lib/open_api_spex/cast/object.ex
index f7726aa..98432d1 100644
--- a/lib/open_api_spex/cast_object.ex
+++ b/lib/open_api_spex/cast/object.ex
@@ -1,93 +1,94 @@
-defmodule OpenApiSpex.CastObject do
+defmodule OpenApiSpex.Cast.Object do
@moduledoc false
- alias OpenApiSpex.{Cast, CastError, CastContext}
+ alias OpenApiSpex.Cast
+ alias OpenApiSpex.Cast.{Error, Context}
def cast(%{value: value} = ctx) when not is_map(value) do
- CastContext.error(ctx, {:invalid_type, :object})
+ Context.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, value} <- cast_properties(%{ctx | schema: schema_properties}) do
ctx = to_struct(%{ctx | value: value})
{:ok, ctx}
end
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
- CastContext.error(ctx, {:unexpected_field, name})
+ Context.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]}
- CastError.new(ctx, {:missing_field, key})
+ Error.new(ctx, {:missing_field, key})
end)
{:error, ctx.errors ++ errors}
end
end
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 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_primitive.ex b/lib/open_api_spex/cast/primitive.ex
similarity index 76%
rename from lib/open_api_spex/cast_primitive.ex
rename to lib/open_api_spex/cast/primitive.ex
index eb24180..3afa442 100644
--- a/lib/open_api_spex/cast_primitive.ex
+++ b/lib/open_api_spex/cast/primitive.ex
@@ -1,67 +1,67 @@
-defmodule OpenApiSpex.CastPrimitive do
+defmodule OpenApiSpex.Cast.Primitive do
@moduledoc false
- alias OpenApiSpex.{CastContext, CastString}
+ alias OpenApiSpex.Cast.{Context, String}
def cast(%{schema: %{type: :boolean}} = ctx),
do: cast_boolean(ctx)
def cast(%{schema: %{type: :integer}} = ctx),
do: cast_integer(ctx)
def cast(%{schema: %{type: :number}} = ctx),
do: cast_number(ctx)
def cast(%{schema: %{type: :string}} = ctx),
- do: CastString.cast(ctx)
+ do: String.cast(ctx)
## Private functions
defp cast_boolean(%{value: value}) when is_boolean(value) do
{:ok, value}
end
defp cast_boolean(%{value: "true"}), do: {:ok, true}
defp cast_boolean(%{value: "false"}), do: {:ok, false}
defp cast_boolean(ctx) do
- CastContext.error(ctx, {:invalid_type, :boolean})
+ Context.error(ctx, {:invalid_type, :boolean})
end
defp cast_integer(%{value: value}) when is_integer(value) do
{:ok, value}
end
defp cast_integer(%{value: value}) when is_number(value) do
{:ok, round(value)}
end
defp cast_integer(%{value: value} = ctx) when is_binary(value) do
case Float.parse(value) do
{value, ""} -> cast_integer(%{ctx | value: value})
- _ -> CastContext.error(ctx, {:invalid_type, :integer})
+ _ -> Context.error(ctx, {:invalid_type, :integer})
end
end
defp cast_integer(ctx) do
- CastContext.error(ctx, {:invalid_type, :integer})
+ Context.error(ctx, {:invalid_type, :integer})
end
defp cast_number(%{value: value}) when is_number(value) do
{:ok, value}
end
defp cast_number(%{value: value}) when is_integer(value) do
{:ok, value / 1}
end
defp cast_number(%{value: value} = ctx) when is_binary(value) do
case Float.parse(value) do
{value, ""} -> {:ok, value}
- _ -> CastContext.error(ctx, {:invalid_type, :number})
+ _ -> Context.error(ctx, {:invalid_type, :number})
end
end
defp cast_number(ctx) do
- CastContext.error(ctx, {:invalid_type, :number})
+ Context.error(ctx, {:invalid_type, :number})
end
end
diff --git a/lib/open_api_spex/cast_string.ex b/lib/open_api_spex/cast/string.ex
similarity index 77%
rename from lib/open_api_spex/cast_string.ex
rename to lib/open_api_spex/cast/string.ex
index 9e9d634..6f43b6b 100644
--- a/lib/open_api_spex/cast_string.ex
+++ b/lib/open_api_spex/cast/string.ex
@@ -1,40 +1,40 @@
-defmodule OpenApiSpex.CastString do
+defmodule OpenApiSpex.Cast.String do
@moduledoc false
- alias OpenApiSpex.CastContext
+ alias OpenApiSpex.Cast.Context
def cast(%{value: value} = ctx) when is_binary(value) do
cast_binary(ctx)
end
def cast(ctx) do
- CastContext.error(ctx, {:invalid_type, :string})
+ Context.error(ctx, {:invalid_type, :string})
end
## Private functions
defp cast_binary(%{value: value, schema: %{pattern: pattern}} = ctx) when not is_nil(pattern) do
if Regex.match?(pattern, value) do
{:ok, value}
else
- CastContext.error(ctx, {:invalid_format, pattern})
+ Context.error(ctx, {:invalid_format, pattern})
end
end
defp cast_binary(%{value: value, schema: %{minLength: min_length}} = ctx)
when is_integer(min_length) do
# Note: This is not part of the JSON Shema spec: trim string before measuring length
# It's just too important to miss
trimmed = String.trim(value)
length = String.length(trimmed)
if length < min_length do
- CastContext.error(ctx, {:min_length, length})
+ Context.error(ctx, {:min_length, length})
else
{:ok, value}
end
end
defp cast_binary(%{value: value}) do
{:ok, value}
end
end
diff --git a/test/cast_array_test.exs b/test/cast/array_test.exs
similarity index 81%
rename from test/cast_array_test.exs
rename to test/cast/array_test.exs
index c4ade67..1a2463f 100644
--- a/test/cast_array_test.exs
+++ b/test/cast/array_test.exs
@@ -1,34 +1,35 @@
-defmodule OpenApiSpec.CastArrayTest do
+defmodule OpenApiSpec.Cast.ArrayTest do
use ExUnit.Case
- alias OpenApiSpex.{CastArray, CastContext, CastError, Schema}
+ alias OpenApiSpex.Cast.{Array, Context, Error}
+ alias OpenApiSpex.Schema
- defp cast(map), do: CastArray.cast(struct(CastContext, map))
+ defp cast(map), do: Array.cast(struct(Context, map))
describe "cast/4" do
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 %CastError{} = error
+ 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, [error]} = cast(value: [1, "two"], schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
assert error.reason == :invalid_type
assert error.value == "two"
assert error.path == [1]
end
end
end
diff --git a/test/cast_object_test.exs b/test/cast/object_test.exs
similarity index 88%
rename from test/cast_object_test.exs
rename to test/cast/object_test.exs
index 6f0e8cc..bdac17e 100644
--- a/test/cast_object_test.exs
+++ b/test/cast/object_test.exs
@@ -1,89 +1,90 @@
-defmodule OpenApiSpex.CastObjectTest do
+defmodule OpenApiSpex.ObjectTest do
use ExUnit.Case
- alias OpenApiSpex.{CastContext, CastObject, CastError, Schema}
+ alias OpenApiSpex.Cast.{Context, Object, Error}
+ alias OpenApiSpex.Schema
- defp cast(ctx), do: CastObject.cast(struct(CastContext, ctx))
+ defp cast(ctx), do: Object.cast(struct(Context, ctx))
describe "cast/3" do
test "not an object" do
schema = %Schema{type: :object}
assert {:error, [error]} = cast(value: ["hello"], schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
assert error.reason == :invalid_type
assert error.value == ["hello"]
end
test "properties:nil, given unknown input property" do
schema = %Schema{type: :object}
assert cast(value: %{}, schema: schema) == {:ok, %{}}
assert cast(value: %{"unknown" => "hello"}, schema: schema) ==
{:ok, %{"unknown" => "hello"}}
end
test "with empty schema properties, given unknown input property" do
schema = %Schema{type: :object, properties: %{}}
assert cast(value: %{}, schema: schema) == {:ok, %{}}
assert {:error, [error]} = cast(value: %{"unknown" => "hello"}, schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
end
test "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 "required fields" do
schema = %Schema{
type: :object,
properties: %{age: nil, name: nil},
required: [:age, :name]
}
assert {:error, [error, error2]} = cast(value: %{}, schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
assert error.reason == :missing_field
assert error.name == :age
assert error.path == [:age]
assert error2.reason == :missing_field
assert error2.name == :name
assert error2.path == [:name]
end
test "cast property against schema" do
schema = %Schema{
type: :object,
properties: %{age: %Schema{type: :integer}}
}
assert cast(value: %{}, schema: schema) == {:ok, %{}}
assert {:error, [error]} = cast(value: %{"age" => "hello"}, schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
assert error.reason == :invalid_type
assert error.path == [:age]
end
defmodule User do
defstruct [:name]
end
test "optionally casts to struct" do
schema = %Schema{
type: :object,
"x-struct": User,
properties: %{
name: %Schema{type: :string}
}
}
assert {:ok, user} = cast(value: %{"name" => "Name"}, schema: schema)
assert user == %User{name: "Name"}
end
end
end
diff --git a/test/cast_primitive_test.exs b/test/cast/primitive_test.exs
similarity index 80%
rename from test/cast_primitive_test.exs
rename to test/cast/primitive_test.exs
index b30a964..b89e38f 100644
--- a/test/cast_primitive_test.exs
+++ b/test/cast/primitive_test.exs
@@ -1,51 +1,52 @@
-defmodule OpenApiSpex.CastPrimitiveTest do
+defmodule OpenApiSpex.PrimitiveTest do
use ExUnit.Case
- alias OpenApiSpex.{CastContext, CastPrimitive, CastError, Schema}
+ alias OpenApiSpex.Cast.{Context, Primitive, Error}
+ alias OpenApiSpex.Schema
- defp cast(ctx), do: CastPrimitive.cast(struct(CastContext, ctx))
+ defp cast(ctx), do: Primitive.cast(struct(Context, ctx))
describe "cast/3" do
test "boolean" do
schema = %Schema{type: :boolean}
assert cast(value: true, schema: schema) == {:ok, true}
assert cast(value: false, schema: schema) == {:ok, false}
assert cast(value: "true", schema: schema) == {:ok, true}
assert cast(value: "false", schema: schema) == {:ok, false}
assert {:error, [error]} = cast(value: "other", schema: schema)
- assert %CastError{reason: :invalid_type} = error
+ assert %Error{reason: :invalid_type} = error
assert error.value == "other"
end
test "integer" do
schema = %Schema{type: :integer}
assert cast(value: 1, schema: schema) == {:ok, 1}
assert cast(value: 1.5, schema: schema) == {:ok, 2}
assert cast(value: "1", schema: schema) == {:ok, 1}
assert cast(value: "1.5", schema: schema) == {:ok, 2}
assert {:error, [error]} = cast(value: "other", schema: schema)
- assert %CastError{reason: :invalid_type} = error
+ assert %Error{reason: :invalid_type} = error
assert error.value == "other"
end
test "number" do
schema = %Schema{type: :number}
assert cast(value: 1, schema: schema) == {:ok, 1.0}
assert cast(value: 1.5, schema: schema) == {:ok, 1.5}
assert cast(value: "1", schema: schema) == {:ok, 1.0}
assert cast(value: "1.5", schema: schema) == {:ok, 1.5}
assert {:error, [error]} = cast(value: "other", schema: schema)
- assert %CastError{reason: :invalid_type} = error
+ assert %Error{reason: :invalid_type} = error
assert error.value == "other"
end
# Additional string tests are covered in CastStringTest
test "string" do
schema = %Schema{type: :string}
assert cast(value: "hello", schema: schema) == {:ok, "hello"}
assert cast(value: "", schema: schema) == {:ok, ""}
assert {:error, [error]} = cast(value: %{}, schema: schema)
- assert %CastError{reason: :invalid_type} = error
+ assert %Error{reason: :invalid_type} = error
assert error.value == %{}
end
end
end
diff --git a/test/cast_string_test.exs b/test/cast/string_test.exs
similarity index 82%
rename from test/cast_string_test.exs
rename to test/cast/string_test.exs
index abb2584..cabd59a 100644
--- a/test/cast_string_test.exs
+++ b/test/cast/string_test.exs
@@ -1,34 +1,35 @@
defmodule OpenApiSpex.CastStringTest do
use ExUnit.Case
- alias OpenApiSpex.{CastContext, CastError, CastString, Schema}
+ alias OpenApiSpex.Cast.{Context, Error, String}
+ alias OpenApiSpex.Schema
- defp cast(ctx), do: CastString.cast(struct(CastContext, ctx))
+ defp cast(ctx), do: String.cast(struct(Context, ctx))
describe "cast/1" do
test "basics" do
schema = %Schema{type: :string}
assert cast(value: "hello", schema: schema) == {:ok, "hello"}
assert cast(value: "", schema: schema) == {:ok, ""}
assert {:error, [error]} = cast(value: %{}, schema: schema)
- assert %CastError{reason: :invalid_type} = error
+ assert %Error{reason: :invalid_type} = error
assert error.value == %{}
end
test "string with pattern" do
schema = %Schema{type: :string, pattern: ~r/\d-\d/}
assert cast(value: "1-2", schema: schema) == {:ok, "1-2"}
assert {:error, [error]} = cast(value: "hello", schema: schema)
assert error.reason == :invalid_format
assert error.value == "hello"
assert error.format == ~r/\d-\d/
end
# Note: we measure length of string after trimming leading and trailing whitespace
test "minLength" do
schema = %Schema{type: :string, minLength: 1}
assert {:error, [error]} = cast(value: " ", schema: schema)
- assert %CastError{} = error
+ assert %Error{} = error
assert error.reason == :min_length
end
end
end
diff --git a/test/cast_test.exs b/test/cast_test.exs
index 0336dc1..c9bc1c4 100644
--- a/test/cast_test.exs
+++ b/test/cast_test.exs
@@ -1,196 +1,197 @@
defmodule OpenApiSpec.CastTest do
use ExUnit.Case
- alias OpenApiSpex.{Cast, CastContext, CastError, Reference, Schema}
+ alias OpenApiSpex.Cast.{Context, Error}
+ alias OpenApiSpex.{Cast, Schema, Reference}
- def cast(ctx), do: Cast.cast(struct(CastContext, ctx))
+ def cast(ctx), do: Cast.cast(struct(Context, ctx))
describe "cast/3" do
# Note: full tests for primitives are covered in CastPrimitiveTest
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 CastError.message_with_path(error) == "#: null value where array expected"
+ 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 %CastError{} = error
+ 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 [%CastError{} = error] = errors
+ assert [%Error{} = error] = errors
assert error.reason == :invalid_type
assert error.value == "two"
assert error.path == [1]
end
# Additional object tests found in CastObjectTest
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 %CastError{} = error
+ 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 %CastError{} = error
+ assert %Error{} = error
assert error.path == [:data, :age]
- assert CastError.message_with_path(error) == "#/data/age: Invalid integer. Got: string"
+ 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 %CastError{} = error
+ assert %Error{} = error
assert error.path == [:data, 1, :age]
- assert CastError.message_with_path(error) == "#/data/1/age: Invalid integer. Got: string"
+ 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 %CastError{} = error
+ assert %Error{} = error
assert error.reason == :invalid_type
assert error.path == [1]
- assert CastError.message_with_path(error) == "#/1: Invalid integer. Got: string"
+ assert Error.message_with_path(error) == "#/1: Invalid integer. Got: string"
- assert CastError.message_with_path(error2) == "#/3: 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 %CastError{} = error
+ 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
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 30, 2:42 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41465
Default Alt Text
(37 KB)

Event Timeline