Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/string.ex b/lib/open_api_spex/cast/string.ex
index aea0776..723cf9c 100644
--- a/lib/open_api_spex/cast/string.ex
+++ b/lib/open_api_spex/cast/string.ex
@@ -1,60 +1,87 @@
defmodule OpenApiSpex.Cast.String do
- @moduledoc false
- alias OpenApiSpex.Cast
+ @moduledoc """
- def cast(%{value: value} = ctx) when is_binary(value) do
- case cast_binary(ctx) do
- {:cast, ctx} -> cast(ctx)
- result -> result
- end
- end
+ This module will cast a binary to either an Elixir DateTime or Date. Otherwise it will
+ validate a binary based on maxLength, minLength, or a Regex pattern passed through the
+ schema struct.
- def cast(ctx) do
- Cast.error(ctx, {:invalid_type, :string})
- end
+ """
+ alias OpenApiSpex.{Cast, Cast.Error}
- ## Private functions
+ @schema_fields [:maxLength, :minLength, :pattern]
- defp cast_binary(%{value: value, schema: %{format: :"date-time"}} = ctx)
- when is_binary(value) do
- case DateTime.from_iso8601(value) do
- {:ok, %DateTime{}, _offset} -> Cast.success(ctx, :format)
- _ -> Cast.error(ctx, {:invalid_format, :"date-time"})
+ def cast(%{value: value, schema: %{format: :date}} = ctx) when is_binary(value) do
+ case Date.from_iso8601(value) do
+ {:ok, %Date{} = date} ->
+ {:ok, date}
+
+ _ ->
+ Cast.error(ctx, {:invalid_format, :date})
end
end
- defp cast_binary(%{value: value, schema: %{format: :date}} = ctx) do
- case Date.from_iso8601(value) do
- {:ok, %Date{}} -> Cast.success(ctx, :format)
- _ -> Cast.error(ctx, {:invalid_format, :date})
+ def cast(%{value: value, schema: %{format: :"date-time"}} = ctx) when is_binary(value) do
+ case DateTime.from_iso8601(value) do
+ {:ok, %DateTime{} = datetime, _offset} ->
+ {:ok, datetime}
+
+ _ ->
+ Cast.error(ctx, {:invalid_format, :"date-time"})
end
end
- defp cast_binary(%{value: value, schema: %{pattern: pattern}} = ctx) when not is_nil(pattern) do
- if Regex.match?(pattern, value) do
- Cast.success(ctx, :pattern)
+ def cast(%{value: value} = ctx) when is_binary(value) do
+ apply_validation(ctx, @schema_fields)
+ end
+
+ def cast(ctx) do
+ Cast.error(ctx, {:invalid_type, :string})
+ end
+
+ ## Private functions
+
+ defp apply_validation(%{value: value, schema: %{maxLength: max_length}} = ctx, [
+ :maxLength | fields
+ ])
+ when is_integer(max_length) do
+ if String.length(value) > max_length do
+ ctx
+ |> apply_error({:max_length, max_length})
+ |> apply_validation(fields)
else
- Cast.error(ctx, {:invalid_format, pattern})
+ apply_validation(ctx, fields)
end
end
- defp cast_binary(%{value: value, schema: %{minLength: min_length}} = ctx)
- when is_integer(min_length) do
+ defp apply_validation(%{value: value, schema: %{minLength: min_length}} = ctx, [
+ :minLength | fields
+ ])
+ when is_integer(min_length) do
if String.length(value) < min_length do
- Cast.error(ctx, {:min_length, min_length})
+ ctx
+ |> apply_error({:min_length, min_length})
+ |> apply_validation(fields)
else
- Cast.success(ctx, :minLength)
+ apply_validation(ctx, fields)
end
end
- defp cast_binary(%{value: value, schema: %{maxLength: max_length}} = ctx)
- when is_integer(max_length) do
- if String.length(value) > max_length do
- Cast.error(ctx, {:max_length, max_length})
+ defp apply_validation(%{value: value, schema: %{pattern: pattern}} = ctx, [:pattern | fields])
+ when not is_nil(pattern) do
+ if Regex.match?(pattern, value) do
+ apply_validation(ctx, fields)
else
- Cast.success(ctx, :maxLength)
+ ctx
+ |> apply_error({:invalid_format, pattern})
+ |> apply_validation(fields)
end
end
- defp cast_binary(ctx), do: Cast.ok(ctx)
+ defp apply_validation(ctx, [_field | fields]), do: apply_validation(ctx, fields)
+ defp apply_validation(%{value: value, errors: []}, []), do: {:ok, value}
+ defp apply_validation(%{errors: errors}, []) when length(errors) > 0, do: {:error, errors}
+
+ defp apply_error(%{errors: errors} = ctx, error_args) do
+ Map.put(ctx, :errors, [Error.new(ctx, error_args) | errors])
+ end
end
diff --git a/test/cast/string_test.exs b/test/cast/string_test.exs
index d21c772..ebf1d9e 100644
--- a/test/cast/string_test.exs
+++ b/test/cast/string_test.exs
@@ -1,75 +1,82 @@
defmodule OpenApiSpex.CastStringTest do
use ExUnit.Case
alias OpenApiSpex.{Cast, Schema}
alias OpenApiSpex.Cast.{Error, String}
defp cast(ctx), do: String.cast(struct(Cast, 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 %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
test "string with format (date time)" do
schema = %Schema{type: :string, format: :"date-time"}
time_string = DateTime.utc_now() |> DateTime.to_string()
- assert cast(value: time_string, schema: schema) == {:ok, time_string}
+ assert {:ok, %DateTime{}} = cast(value: time_string, schema: schema)
assert {:error, [error]} = cast(value: "hello", schema: schema)
assert error.reason == :invalid_format
assert error.value == "hello"
assert error.format == :"date-time"
end
test "string with format (date)" do
schema = %Schema{type: :string, format: :date}
date_string = DateTime.utc_now() |> DateTime.to_date() |> Date.to_string()
- assert cast(value: date_string, schema: schema) == {:ok, date_string}
+ assert {:ok, %Date{}} = cast(value: date_string, schema: schema)
assert {:error, [error]} = cast(value: "hello", schema: schema)
assert error.reason == :invalid_format
assert error.value == "hello"
assert error.format == :date
end
# Note: we measure length of string after trimming leading and trailing whitespace
test "minLength" do
schema = %Schema{type: :string, minLength: 1}
assert {:ok, "a"} = cast(value: "a", schema: schema)
assert {:error, [error]} = cast(value: "", schema: schema)
assert %Error{} = error
assert error.reason == :min_length
end
# Note: we measure length of string after trimming leading and trailing whitespace
test "maxLength" do
schema = %Schema{type: :string, maxLength: 1}
assert {:ok, "a"} = cast(value: "a", schema: schema)
assert {:error, [error]} = cast(value: "aa", schema: schema)
assert %Error{} = error
assert error.reason == :max_length
end
test "maxLength and minLength" do
schema = %Schema{type: :string, minLength: 1, maxLength: 2}
assert {:error, [error]} = cast(value: "", schema: schema)
assert %Error{} = error
assert error.reason == :min_length
assert {:error, [error]} = cast(value: "aaa", schema: schema)
assert %Error{} = error
assert error.reason == :max_length
end
+
+ test "minLength and pattern" do
+ schema = %Schema{type: :string, minLength: 1, pattern: ~r/\d-\d/ }
+ assert {:error, errors} = cast(value: "", schema: schema)
+ assert length(errors) == 2
+ assert Enum.map(errors, &(&1.reason)) == [:invalid_format, :min_length]
+ end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 29, 6:51 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41279
Default Alt Text
(7 KB)

Event Timeline