Skip to content

Commit b5af5e0

Browse files
authored
Merge pull request #13 from GhostGroup/fix_casting_formats
Fix casting formats based on issue #90
2 parents d422742 + ac4928b commit b5af5e0

File tree

2 files changed

+71
-37
lines changed

2 files changed

+71
-37
lines changed

lib/open_api_spex/cast/string.ex

+62-35
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,87 @@
11
defmodule OpenApiSpex.Cast.String do
2-
@moduledoc false
3-
alias OpenApiSpex.Cast
2+
@moduledoc """
43
5-
def cast(%{value: value} = ctx) when is_binary(value) do
6-
case cast_binary(ctx) do
7-
{:cast, ctx} -> cast(ctx)
8-
result -> result
9-
end
10-
end
4+
This module will cast a binary to either an Elixir DateTime or Date. Otherwise it will
5+
validate a binary based on maxLength, minLength, or a Regex pattern passed through the
6+
schema struct.
117
12-
def cast(ctx) do
13-
Cast.error(ctx, {:invalid_type, :string})
14-
end
8+
"""
9+
alias OpenApiSpex.{Cast, Cast.Error}
1510

16-
## Private functions
11+
@schema_fields [:maxLength, :minLength, :pattern]
1712

18-
defp cast_binary(%{value: value, schema: %{format: :"date-time"}} = ctx)
19-
when is_binary(value) do
20-
case DateTime.from_iso8601(value) do
21-
{:ok, %DateTime{}, _offset} -> Cast.success(ctx, :format)
22-
_ -> Cast.error(ctx, {:invalid_format, :"date-time"})
13+
def cast(%{value: value, schema: %{format: :date}} = ctx) when is_binary(value) do
14+
case Date.from_iso8601(value) do
15+
{:ok, %Date{} = date} ->
16+
{:ok, date}
17+
18+
_ ->
19+
Cast.error(ctx, {:invalid_format, :date})
2320
end
2421
end
2522

26-
defp cast_binary(%{value: value, schema: %{format: :date}} = ctx) do
27-
case Date.from_iso8601(value) do
28-
{:ok, %Date{}} -> Cast.success(ctx, :format)
29-
_ -> Cast.error(ctx, {:invalid_format, :date})
23+
def cast(%{value: value, schema: %{format: :"date-time"}} = ctx) when is_binary(value) do
24+
case DateTime.from_iso8601(value) do
25+
{:ok, %DateTime{} = datetime, _offset} ->
26+
{:ok, datetime}
27+
28+
_ ->
29+
Cast.error(ctx, {:invalid_format, :"date-time"})
3030
end
3131
end
3232

33-
defp cast_binary(%{value: value, schema: %{pattern: pattern}} = ctx) when not is_nil(pattern) do
34-
if Regex.match?(pattern, value) do
35-
Cast.success(ctx, :pattern)
33+
def cast(%{value: value} = ctx) when is_binary(value) do
34+
apply_validation(ctx, @schema_fields)
35+
end
36+
37+
def cast(ctx) do
38+
Cast.error(ctx, {:invalid_type, :string})
39+
end
40+
41+
## Private functions
42+
43+
defp apply_validation(%{value: value, schema: %{maxLength: max_length}} = ctx, [
44+
:maxLength | fields
45+
])
46+
when is_integer(max_length) do
47+
if String.length(value) > max_length do
48+
ctx
49+
|> apply_error({:max_length, max_length})
50+
|> apply_validation(fields)
3651
else
37-
Cast.error(ctx, {:invalid_format, pattern})
52+
apply_validation(ctx, fields)
3853
end
3954
end
4055

41-
defp cast_binary(%{value: value, schema: %{minLength: min_length}} = ctx)
42-
when is_integer(min_length) do
56+
defp apply_validation(%{value: value, schema: %{minLength: min_length}} = ctx, [
57+
:minLength | fields
58+
])
59+
when is_integer(min_length) do
4360
if String.length(value) < min_length do
44-
Cast.error(ctx, {:min_length, min_length})
61+
ctx
62+
|> apply_error({:min_length, min_length})
63+
|> apply_validation(fields)
4564
else
46-
Cast.success(ctx, :minLength)
65+
apply_validation(ctx, fields)
4766
end
4867
end
4968

50-
defp cast_binary(%{value: value, schema: %{maxLength: max_length}} = ctx)
51-
when is_integer(max_length) do
52-
if String.length(value) > max_length do
53-
Cast.error(ctx, {:max_length, max_length})
69+
defp apply_validation(%{value: value, schema: %{pattern: pattern}} = ctx, [:pattern | fields])
70+
when not is_nil(pattern) do
71+
if Regex.match?(pattern, value) do
72+
apply_validation(ctx, fields)
5473
else
55-
Cast.success(ctx, :maxLength)
74+
ctx
75+
|> apply_error({:invalid_format, pattern})
76+
|> apply_validation(fields)
5677
end
5778
end
5879

59-
defp cast_binary(ctx), do: Cast.ok(ctx)
80+
defp apply_validation(ctx, [_field | fields]), do: apply_validation(ctx, fields)
81+
defp apply_validation(%{value: value, errors: []}, []), do: {:ok, value}
82+
defp apply_validation(%{errors: errors}, []) when length(errors) > 0, do: {:error, errors}
83+
84+
defp apply_error(%{errors: errors} = ctx, error_args) do
85+
Map.put(ctx, :errors, [Error.new(ctx, error_args) | errors])
86+
end
6087
end

test/cast/string_test.exs

+9-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ defmodule OpenApiSpex.CastStringTest do
2727
test "string with format (date time)" do
2828
schema = %Schema{type: :string, format: :"date-time"}
2929
time_string = DateTime.utc_now() |> DateTime.to_string()
30-
assert cast(value: time_string, schema: schema) == {:ok, time_string}
30+
assert {:ok, %DateTime{}} = cast(value: time_string, schema: schema)
3131
assert {:error, [error]} = cast(value: "hello", schema: schema)
3232
assert error.reason == :invalid_format
3333
assert error.value == "hello"
@@ -37,7 +37,7 @@ defmodule OpenApiSpex.CastStringTest do
3737
test "string with format (date)" do
3838
schema = %Schema{type: :string, format: :date}
3939
date_string = DateTime.utc_now() |> DateTime.to_date() |> Date.to_string()
40-
assert cast(value: date_string, schema: schema) == {:ok, date_string}
40+
assert {:ok, %Date{}} = cast(value: date_string, schema: schema)
4141
assert {:error, [error]} = cast(value: "hello", schema: schema)
4242
assert error.reason == :invalid_format
4343
assert error.value == "hello"
@@ -71,5 +71,12 @@ defmodule OpenApiSpex.CastStringTest do
7171
assert %Error{} = error
7272
assert error.reason == :max_length
7373
end
74+
75+
test "minLength and pattern" do
76+
schema = %Schema{type: :string, minLength: 1, pattern: ~r/\d-\d/ }
77+
assert {:error, errors} = cast(value: "", schema: schema)
78+
assert length(errors) == 2
79+
assert Enum.map(errors, &(&1.reason)) == [:invalid_format, :min_length]
80+
end
7481
end
7582
end

0 commit comments

Comments
 (0)