Skip to content

Commit

Permalink
increment docs and add tests cases for transform with MFA
Browse files Browse the repository at this point in the history
  • Loading branch information
zoedsoupe committed Nov 26, 2024
1 parent 9b4f746 commit 2456eab
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 4 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ end
- `{type, {:default, default}}` - Provides a default value if the field is missing or `nil`.
- `{type, {:default, &some_fun/0}}` - The default values is retrieved from callinf `some_fun/0` if the field is missing.
- `{type, {:default, {mod, fun}}}` - The default values is retrieved from callinf `mod.fun/0` if the field is missing.
- `{type, {:transform, mapper}}` - Transforms the field value using the specified mapper function.
- `{type, {:transform, {mod, fun}}}` - Transforms the field value using the specified `mod.fun/1` function.
- `{type, {:transform, mapper}}` - Transforms the field value using the specified mapper function. It can be a 1 or 2 arity function: when is a single arity the mapper function will only receive the defined field value, while with 2 arity will receive the current defined field value and the whole data as the second argument.
- `{type, {:transform, {mod, fun}}}` - Transforms the field value using the specified `mod.fun/1` function. Notice that `fun` can be a 2 arity so it can receive the whole data being validated, in case on dependent fields transformations.
- `{type, {:transform, {mod, fun, args}}}` - Transforms the field value using the specified MFA. Notice that `fun` will be at least a 2 arity one so it can receive the whole data being validated, in case on dependent fields transformations and the maximum arity allowed will be 2 + `length(args)`.
- `{:either, {type1, type2}}` - Validates that the field is either of the two specified types.
- `{:oneof, types}` - Validates that the field is one of the specified types.
- `{:custom, callback}` - Validates that the field passes the custom validation function.
Expand Down
24 changes: 22 additions & 2 deletions lib/peri.ex
Original file line number Diff line number Diff line change
Expand Up @@ -675,14 +675,34 @@ defmodule Peri do
defp validate_field(val, {type, {:transform, {mod, fun}}}, data)
when is_atom(mod) and is_atom(fun) do
with :ok <- validate_field(val, type, data) do
{:ok, apply(mod, fun, [val])}
cond do
function_exported?(mod, fun, 1) ->
{:ok, apply(mod, fun, [val])}

function_exported?(mod, fun, 2) ->
{:ok, apply(mod, fun, [val, maybe_get_root_data(data)])}

true ->
template = "expected %{mod} to export %{fun}/1 or %{fun}/2"
{:error, template, mod: mod, fun: fun}
end
end
end

defp validate_field(val, {type, {:transform, {mod, fun, args}}}, data)
when is_atom(mod) and is_atom(fun) and is_list(args) do
with :ok <- validate_field(val, type, data) do
{:ok, apply(mod, fun, [val | args])}
cond do
function_exported?(mod, fun, length(args) + 2) ->
{:ok, apply(mod, fun, [val, maybe_get_root_data(data) | args])}

function_exported?(mod, fun, length(args) + 1) ->
{:ok, apply(mod, fun, [val | args])}

true ->
template = "expected %{mod} to export %{fun} with arity from %{base} to %{arity}"
{:error, template, mod: mod, fun: fun, arity: length(args), base: length(args) + 1}
end
end
end

Expand Down
47 changes: 47 additions & 0 deletions test/peri_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,53 @@ defmodule PeriTest do
end
end

describe "transform with MFA" do
test "it should apply the mapper function without additional argument" do
s = {:string, {:transform, {String, :to_integer}}}
assert {:ok, 10} = Peri.validate(s, "10")
end

test "it should apply the mapper function without additional argument but with dependent field" do
s = %{id: {:string, {:transform, {__MODULE__, :integer_by_name}}}, name: :string}
data = %{id: "10", name: "john"}
assert {:ok, %{id: 20, name: "john"}} = Peri.validate(s, data)

data = %{id: "10", name: "maria"}
assert {:ok, %{id: 10, name: "maria"}} = Peri.validate(s, data)
end

test "it should apply mapper function with additional arguments" do
s = {:string, {:transform, {String, :split, [~r/\D/, [trim: true]]}}}
assert {:ok, ["10"]} = Peri.validate(s, "omw 10")
end

test "it should apply mapper function with additional arguments with dependent field" do
s = %{
id: {:string, {:transform, {__MODULE__, :integer_by_name, [[make_sense?: false]]}}},
name: :string
}

data = %{id: "10", name: "john"}
assert {:ok, %{id: 10, name: "john"}} = Peri.validate(s, data)
end
end

def integer_by_name(id, %{name: name}) do
if name != "john" do
String.to_integer(id)
else
String.to_integer(id) + 10
end
end

def integer_by_name(id, %{name: name}, make_sense?: sense) do
cond do
sense && name != "john" -> String.to_integer(id) - 10
not sense && name == "john" -> String.to_integer(id)
true -> 42
end
end

defschema(:either_transform, %{
value: {:either, {{:integer, {:transform, &double/1}}, {:string, {:transform, &upcase/1}}}}
})
Expand Down

0 comments on commit 2456eab

Please sign in to comment.