Skip to content

Commit

Permalink
renamed to on_load/on_dump the custom field function
Browse files Browse the repository at this point in the history
  • Loading branch information
phcurado committed Jan 4, 2023
1 parent d5725da commit b1e0eb0
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 46 deletions.
32 changes: 16 additions & 16 deletions lib/parameter/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ defmodule Parameter.Field do
* `:virtual` - If `true` the field will be ignored on `Parameter.load/3` and `Parameter.dump/3` functions.
* `:load_func` - Function to specify how to load the field. The function must have two arguments where the first one is the field value and the second one
will be the data to be loaded.
* `:on_load` - Function to specify how to load the field. The function must have two arguments where the first one is the field value and the second one
will be the data to be loaded. Should return `{:ok, value}` or `{:error, reason}` tuple.
* `:dump_func` - Function to specify how to dump the field. The function must have two arguments where the first one is the field value and the second one
will be the data to be dumped.
* `:on_dump` - Function to specify how to dump the field. The function must have two arguments where the first one is the field value and the second one
will be the data to be dumped. Should return `{:ok, value}` or `{:error, reason}` tuple.
> NOTE: Validation only occurs on `Parameter.load/3`.
> By desgin, data passed into `Parameter.dump/3` are considered valid.
Expand All @@ -49,8 +49,8 @@ defmodule Parameter.Field do
:default,
:load_default,
:dump_default,
:load_func,
:dump_func,
:on_load,
:on_dump,
type: :string,
required: false,
validator: nil,
Expand All @@ -63,8 +63,8 @@ defmodule Parameter.Field do
default: any(),
load_default: any(),
dump_default: any(),
load_func: fun() | nil,
dump_func: fun() | nil,
on_load: fun() | nil,
on_dump: fun() | nil,
type: Types.t(),
required: boolean(),
validator: fun() | nil,
Expand Down Expand Up @@ -104,8 +104,8 @@ defmodule Parameter.Field do
default = Keyword.get(opts, :default)
load_default = Keyword.get(opts, :load_default)
dump_default = Keyword.get(opts, :dump_default)
load_func = Keyword.get(opts, :load_func)
dump_func = Keyword.get(opts, :dump_func)
on_load = Keyword.get(opts, :on_load)
on_dump = Keyword.get(opts, :on_dump)
required = Keyword.get(opts, :required, false)
validator = Keyword.get(opts, :validator)
virtual = Keyword.get(opts, :virtual, false)
Expand All @@ -115,8 +115,8 @@ defmodule Parameter.Field do
:ok <- Types.validate(:string, key),
:ok <- Types.validate(:boolean, required),
:ok <- Types.validate(:boolean, virtual),
:ok <- load_func_valid?(load_func),
:ok <- dump_func_valid?(dump_func),
:ok <- on_load_valid?(on_load),
:ok <- on_dump_valid?(on_dump),
:ok <- validator_valid?(validator) do
struct!(__MODULE__, opts)
end
Expand Down Expand Up @@ -155,12 +155,12 @@ defmodule Parameter.Field do
end
end

defp load_func_valid?(load_func) do
function_valid?(load_func, 2, "load_func must be a function")
defp on_load_valid?(on_load) do
function_valid?(on_load, 2, "on_load must be a function")
end

defp dump_func_valid?(dump_func) do
function_valid?(dump_func, 2, "dump_func must be a function")
defp on_dump_valid?(on_dump) do
function_valid?(on_dump, 2, "on_dump must be a function")
end

defp validator_valid?(validator) do
Expand Down
54 changes: 48 additions & 6 deletions lib/parameter/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,12 @@ defmodule Parameter.Schema do
schema = %{
first_name: [key: "firstName", type: :string, required: true],
address: [required: true, type: {:has_one, %{street: [type: :string, required: true]}}],
address: [type: {:has_one, %{street: [type: :string, required: true]}}],
phones: [type: {:has_many, %{country: [type: :string, required: true]}}]
}
} |> Parameter.Schema.compile!()
compiled_schema = Parameter.Schema.compile!(schema)
Parameter.load(compiled_schema, %{"firstName" => "John"})
...
Parameter.load(schema, %{"firstName" => "John"})
{:ok, %{first_name: "John"}}
The same API can also be evaluated on compile time by using module attributes:
Expand All @@ -118,6 +116,7 @@ defmodule Parameter.Schema do
This makes it easy to dynamically create schemas or just avoid using any macros.
## Required fields
By default, `Parameter.Schema` considers all fields to be optional when validating the schema.
This behaviour can be changed by passing the module attribute `@fields_required true` on
the module where the schema is declared.
Expand All @@ -136,6 +135,49 @@ defmodule Parameter.Schema do
Parameter.load(MyApp.UserSchema, %{})
{:error, %{age: "is required", name: "is required"}}
## Custom field loading and dumping
The `load` and `dump` behavior can be customized per field by implementing `on_load` or `on_dump` functions in the field definition.
This can be useful if the field needs to be fetched or even validate in a different way than the defaults implemented by `Parameter`.
Both functions should return `{:ok, value}` or `{:error, reason}` tuple.
For example, imagine that there is a parameter called `full_name` in your schema that you want to customize on how it will be parsed:
defmodule MyApp.UserSchema do
use Parameter.Schema
param do
field :first_name, :string
field :last_name, :string
field :full_name, :string, on_load: &__MODULE__.load_full_name/2
end
def load_full_name(value, params) do
# if `full_name` is not `nil` it just return the `full_name`
if value do
{:ok, value}
else
# Otherwise it will join the `first_name` and `last_name` params
{:ok, params["first_name"] <> " " <> params["last_name"]}
end
end
end
Now when loading, the full_name field will be handled by the `load_full_name/2` function:
Parameter.load(MyApp.UserSchema, %{first_name: "John", last_name: "Doe", full_name: nil})
{:ok, %{first_name: "John", full_name: "John Doe", last_name: "Doe"}}
The same behavior is possible when dumping the schema parameters by using `on_dump/2` function:
schema = %{
level: [type: :integer, on_dump: fn value, _input -> {:ok, value || 0} end]
} |> Parameter.Schema.compile!()
Parameter.dump(schema, %{level: nil})
{:ok, %{"level" => 0}}
"""

alias Parameter.Field
Expand Down
8 changes: 4 additions & 4 deletions lib/parameter/schema/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ defmodule Parameter.Schema.Compiler do
raise ArgumentError, "validator cannot be used on nested fields"
end

if :load_func in keys do
raise ArgumentError, "load_func cannot be used on nested fields"
if :on_load in keys do
raise ArgumentError, "on_load cannot be used on nested fields"
end

if :dump_func in keys do
raise ArgumentError, "dump_func cannot be used on nested fields"
if :on_dump in keys do
raise ArgumentError, "on_dump cannot be used on nested fields"
end

opts
Expand Down
25 changes: 16 additions & 9 deletions lib/parameter/schema_fields.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ defmodule Parameter.SchemaFields do
end

def field_handler(
%Meta{parent_input: parent_input, operation: operation} = meta,
%Field{load_func: load_func} = field,
%Meta{parent_input: parent_input, operation: :load} = meta,
%Field{on_load: on_load} = field,
value,
opts
)
when not is_nil(load_func) and operation in [:load] do
case load_func.(value, parent_input) do
when not is_nil(on_load) do
case on_load.(value, parent_input) do
{:ok, value} ->
operation_handler(meta, field, value, opts)

Expand All @@ -122,13 +122,13 @@ defmodule Parameter.SchemaFields do
end

def field_handler(
%Meta{parent_input: parent_input, operation: operation} = meta,
%Field{dump_func: dump_func} = field,
%Meta{parent_input: parent_input, operation: :dump} = meta,
%Field{on_dump: on_dump} = field,
value,
opts
)
when not is_nil(dump_func) and operation in [:dump] do
case dump_func.(value, parent_input) do
when not is_nil(on_dump) do
case on_dump.(value, parent_input) do
{:ok, value} -> operation_handler(meta, field, value, opts)
error -> error
end
Expand Down Expand Up @@ -200,6 +200,10 @@ defmodule Parameter.SchemaFields do
error
end

defp operation_handler(_meta, _field, nil, _opts) do
{:ok, nil}
end

defp operation_handler(meta, %Field{type: type}, value, _opts) do
case meta.operation do
:dump -> Types.dump(type, value)
Expand All @@ -226,7 +230,10 @@ defmodule Parameter.SchemaFields do
check_required(field, :ignore, meta.operation)

{:ok, nil} ->
check_required(field, nil, meta.operation)
case check_required(field, nil, meta.operation) do
{:ok, value} -> field_handler(meta, field, value, opts)
other -> other
end

{:ok, value} ->
field_handler(meta, field, value, opts)
Expand Down
2 changes: 1 addition & 1 deletion lib/parameter/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ defmodule Parameter.Types do
end
end

@spec validate(atom(), any()) :: :ok | {:error, any()}
@spec validate(atom() | composite_types(), any()) :: :ok | {:error, any()}
def validate(type, values)

def validate({:has_one, inner_type}, values) when is_map(values) do
Expand Down
8 changes: 4 additions & 4 deletions test/parameter/schema/compiler_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ defmodule Parameter.Schema.CompilerTest do
Compiler.fetch_nested_opts!(dump_default: nil)
end

assert_raise ArgumentError, "load_func cannot be used on nested fields", fn ->
Compiler.fetch_nested_opts!(load_func: nil)
assert_raise ArgumentError, "on_load cannot be used on nested fields", fn ->
Compiler.fetch_nested_opts!(on_load: nil)
end

assert_raise ArgumentError, "dump_func cannot be used on nested fields", fn ->
Compiler.fetch_nested_opts!(dump_func: nil)
assert_raise ArgumentError, "on_dump cannot be used on nested fields", fn ->
Compiler.fetch_nested_opts!(on_dump: nil)
end

assert_raise ArgumentError, "validator cannot be used on nested fields", fn ->
Expand Down
12 changes: 6 additions & 6 deletions test/parameter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ defmodule ParameterTest do
param do
field :first_name, :string, key: "firstName", required: true
field :last_name, :string, key: "lastName", required: true, default: ""
field :age, :integer, load_func: &__MODULE__.load_age/2
field :age, :integer, on_load: &__MODULE__.load_age/2
field :metadata, :map, dump_default: %{"key" => "value"}
field :hex_amount, CustomTypeHexToDecimal, key: "hexAmount", default: "0"
field :paid_amount, :decimal, key: "paidAmount", default: Decimal.new("1")
Expand All @@ -79,16 +79,16 @@ defmodule ParameterTest do
field :type, :string

field :age, :integer,
load_func: &ParameterTest.UserTestSchema.load_info_age/2,
dump_func: &ParameterTest.UserTestSchema.dump_age/2
on_load: &ParameterTest.UserTestSchema.load_info_age/2,
on_dump: &ParameterTest.UserTestSchema.dump_age/2
end

has_many :info, Info do
field :id, :string

field :age, :string,
load_func: &ParameterTest.UserTestSchema.load_info_age/2,
dump_func: &ParameterTest.UserTestSchema.dump_info_age/2
on_load: &ParameterTest.UserTestSchema.load_info_age/2,
on_dump: &ParameterTest.UserTestSchema.dump_info_age/2
end
end

Expand Down Expand Up @@ -117,7 +117,7 @@ defmodule ParameterTest do
end

def dump_age(value, input) do
if age = input[:id_info][:age] do
if age = input.id_info.age do
{:ok, age}
else
{:ok, value}
Expand Down

0 comments on commit b1e0eb0

Please sign in to comment.