Skip to content

Commit

Permalink
wip: default values
Browse files Browse the repository at this point in the history
  • Loading branch information
zoedsoupe committed Jun 21, 2024
1 parent 68fa43f commit a569ecf
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 36 deletions.
42 changes: 22 additions & 20 deletions lib/peri.ex
Original file line number Diff line number Diff line change
Expand Up @@ -239,32 +239,29 @@ defmodule Peri do
defp validate_field(val, :boolean, _data) when is_boolean(val), do: :ok
defp validate_field(val, :list, _data) when is_list(val), do: :ok
defp validate_field(nil, {:required, _}, _data), do: {:error, "is required", []}

defp validate_field(m, {:required, :map}, _data) when m == %{},
do: {:error, "cannot be empty", []}

defp validate_field(m, {:required, s}, _data) when m == %{} and is_map(s),
do: {:error, "cannot be empty", []}

defp validate_field([], {:required, {:list, _}}, _data), do: {:error, "cannot be empty", []}
defp validate_field(val, {:required, type}, data), do: validate_field(val, type, data)
defp validate_field(nil, _, _data), do: :ok

defp validate_field(val, {:custom, callback}, _data) when is_function(callback, 1) do
case callback.(val) do
:ok -> :ok
{:ok, _} -> :ok
err -> err
end
callback.(val)
end

defp validate_field(val, {:custom, {mod, fun}}, _data) do
case apply(mod, fun, [val]) do
:ok -> :ok
{:ok, _} -> :ok
err -> err
end
defp validate_field(val, {:custom, {mod, fun}}, _data)
when is_atom(mod) and is_atom(fun) do
apply(mod, fun, [val])
end

defp validate_field(val, {:custom, {mod, fun, args}}, _data) do
case apply(mod, fun, [val | args]) do
:ok -> :ok
{:ok, _} -> :ok
err -> err
end
defp validate_field(val, {:custom, {mod, fun, args}}, _data)
when is_atom(mod) and is_atom(fun) and is_list(args) do
apply(mod, fun, [val | args])
end

defp validate_field(val, {:cond, condition, true_type, else_type}, data) do
Expand Down Expand Up @@ -328,7 +325,7 @@ defmodule Peri do
end)
else
info = [length: length(types), actual: length(Tuple.to_list(val))]
template = "expected tuple of size %{length} received tuple wwith %{actual} length"
template = "expected tuple of size %{length} received tuple with %{actual} length"
{:error, template, info}
end
end
Expand All @@ -344,13 +341,18 @@ defmodule Peri do
end

defp validate_field(data, {:list, type}, source) when is_list(data) do
Enum.reduce_while(data, :ok, fn el, :ok ->
Enum.reduce_while(data, {:ok, nil}, fn el, {:ok, val} ->
case validate_field(el, type, source) do
:ok -> {:cont, :ok}
:ok -> {:cont, {:ok, val}}
{:ok, val} -> {:cont, {:ok, val}}
{:error, errors} -> {:halt, {:error, errors}}
{:error, reason, info} -> {:halt, {:error, reason, info}}
end
end)
|> then(fn
{:ok, _} -> :ok
err -> err
end)
end

defp validate_field(data, schema, _data) when is_enumerable(data) do
Expand Down
2 changes: 1 addition & 1 deletion lib/peri/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defmodule Peri.Parser do
%Peri.Parser{data: %{name: "Alice", age: 30}, errors: [], path: []}
"""
def update_data(%__MODULE__{} = state, key, val) do
%{state | data: put_in(state.data[key], val)}
%{state | data: put_in(state.data[key], val).data}
end

@doc """
Expand Down
219 changes: 204 additions & 15 deletions test/peri_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -438,25 +438,35 @@ defmodule PeriTest do
test "validates tuple with incorrect size" do
data = %{coordinates: {10.5}}

assert {:error,
[
%Peri.Error{
path: [:coordinates],
message: "expected tuple of size 2 received tuple wwith 1 length"
}
]} = tuple_example(data)
assert {
:error,
[
%Peri.Error{
path: [:coordinates],
key: :coordinates,
content: %{length: 2, actual: 1},
message: "expected tuple of size 2 received tuple with 1 length",
errors: nil
}
]
} = tuple_example(data)
end

test "validates tuple with extra elements" do
data = %{coordinates: {10.5, 20.5, 30.5}}

assert {:error,
[
%Peri.Error{
path: [:coordinates],
message: "expected tuple of size 2 received tuple wwith 3 length"
}
]} = tuple_example(data)
assert {
:error,
[
%Peri.Error{
path: [:coordinates],
key: :coordinates,
content: %{length: 2, actual: 3},
message: "expected tuple of size 2 received tuple with 3 length",
errors: nil
}
]
} = tuple_example(data)
end

test "handles missing tuple correctly" do
Expand Down Expand Up @@ -589,7 +599,9 @@ defmodule PeriTest do

test "validates schema with extra fields" do
data = %{id: 1, scores: %{name: "test", score: 95.5, extra: "field"}}
assert string_score_map(data) == {:ok, data}
expected = %{id: 1, scores: %{name: "test", score: 95.5}}

assert {:ok, ^expected} = string_score_map(data)
end
end

Expand Down Expand Up @@ -1153,4 +1165,181 @@ defmodule PeriTest do
assert {:ok, ^schema} = Peri.validate_schema(schema)
end
end

# defschema(:default_values, %{
# name: {:string, {:default, "Anonymous"}},
# age: {:integer, {:default, 0}},
# email: {:required, :string}
# })

# defschema(:nested_default_values, %{
# user: %{
# name: {:string, {:default, "John Doe"}},
# profile:
# {:required,
# %{
# email: {:string, {:default, "[email protected]"}},
# address:
# {:required,
# %{
# street: {:string, {:default, "123 Main St"}},
# number: {:integer, {:default, 1}}
# }}
# }}
# }
# })

# defschema(:invalid_nested_default_values, %{
# user: %{
# name: {:string, {:default, "John Doe"}},
# profile:
# {:required,
# %{
# age: {:required, :integer},
# email: {:required, {:string, {:default, "[email protected]"}}},
# address: %{
# street: {:string, {:default, "123 Main St"}},
# number: {:integer, {:default, 1}}
# }
# }}
# }
# })

# describe "default values schema validation" do
# test "applies default values when fields are missing" do
# data = %{email: "[email protected]"}
# expected_data = %{name: "Anonymous", age: 0, email: "[email protected]"}
# assert {:ok, ^expected_data} = default_values(data)
# end

# test "does not override provided values with defaults" do
# data = %{name: "Alice", age: 25, email: "[email protected]"}
# assert {:ok, ^data} = default_values(data)
# end

# test "handles missing required fields" do
# data = %{name: "Alice", age: 25}

# assert {:error, [%Peri.Error{path: [:email], message: "is required"}]} =
# default_values(data)
# end
# end

# describe "nested default values schema validation" do
# test "applies default values in nested schema" do
# data = %{user: %{profile: %{email: nil, address: %{number: nil, street: nil}}}}

# expected_data = %{
# user: %{
# name: "John Doe",
# profile: %{
# email: "[email protected]",
# address: %{street: "123 Main St", number: 1}
# }
# }
# }

# assert {:ok, ^expected_data} = nested_default_values(data)
# end

# test "does not override provided values in nested schema" do
# data = %{
# user: %{
# name: "Jane Doe",
# profile: %{
# email: "[email protected]",
# address: %{street: "456 Elm St", number: 99}
# }
# }
# }

# assert {:ok, ^data} = nested_default_values(data)
# end

# test "required fields should not receive default values" do
# data = %{user: %{profile: %{age: 30}}}

# assert {
# :error,
# [
# %Peri.Error{
# path: [:user],
# key: :user,
# content: nil,
# message: nil,
# errors: [
# %Peri.Error{
# path: [:user, :profile],
# key: :profile,
# content: nil,
# message: nil,
# errors: [
# %Peri.Error{
# path: [:user, :profile, :email],
# key: :email,
# content: %{
# type: :string,
# value: "[email protected]",
# schema: %{
# address: %{
# number: {:integer, {:default, 1}},
# street: {:string, {:default, "123 Main St"}}
# },
# age: {:required, :integer},
# email: {:required, {:string, {:default, "[email protected]"}}}
# }
# },
# message:
# "cannot set default value of [email protected] for required field of type :string",
# errors: nil
# }
# ]
# }
# ]
# }
# ]
# } = invalid_nested_default_values(data)
# end
# end

# defschema(:simple_list, [
# {:name, {:string, {:default, "Default Name"}}},
# {:age, {:integer, {:default, 18}}},
# {:email, {:required, :string}}
# ])

# defschema(
# :simple_tuple,
# {:tuple,
# [
# {:integer, {:default, 0}},
# {:string, {:default, "Unknown"}}
# ]}
# )

# describe "simple list schema validation" do
# test "applies default values for missing fields in keyword list schema" do
# data = [email: "[email protected]"]
# expected_data = [age: 18, name: "Default Name", email: "[email protected]"]
# assert {:ok, ^expected_data} = simple_list(data)
# end

# test "does not override provided values in keyword list schema" do
# data = [name: "Alice", age: 25, email: "[email protected]"]
# assert {:ok, ^data} = simple_list(data)
# end
# end

# describe "simple tuple schema validation" do
# test "applies default values for missing elements in tuple schema" do
# data = {nil, nil}
# expected_data = {0, "Unknown"}
# assert {:ok, ^expected_data} = simple_tuple(data)
# end

# test "does not override provided values in tuple schema" do
# data = {42, "Provided"}
# assert {:ok, ^data} = simple_tuple(data)
# end
# end
end

0 comments on commit a569ecf

Please sign in to comment.