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