View Source Tarams
Phoenix request params validation library.
Warning: Tarams v1.0.0 APIs is not back compatible
why-tarams
Why Tarams
- Reduce code boilerplate
- Shorter schema definition
- Default function which generate value each casting time
- Custom validation functions
- Custom parse functions
installation
Installation
Available in Hex, the package can be installed
by adding tarams to your list of dependencies in mix.exs:
def deps do
[
{:tarams, "~> 1.0.0"}
]
end
``**
## Usage
**Process order**
> Cast data -> validate casted data -> transform data
@index_params_schema %{
keyword: :string,
status: [type: :string, required: true],
group_id: [type: :integer, numer: [greater_than: 0]],
name: [type: :string, from: :another_field]}
def index(conn, params) do
with {:ok, better_params} <- Tarams.cast(params, @index_params_schema) do
# do anything with your params
else
{:error, errors} -> # return params error
endend
## Define schema
Schema is just a map and it can be nested. Each field is defined as
`<field_name>: [<field_spec>, ...]`
Or short form
`<field_name>: <type>`
Field specs is a keyword list thay may include:
- `type` is required, `Tarams` support same data type as `Ecto`. I borrowed code from Ecto
- `default`: default value or default function
- `cast_func`: custom cast function
- `number, format, length, in, not_in, func, required, each` are available validations
- `from`: use value from another field
- `as`: alias key you will receive from `Tarams.cast` if casting is succeeded
### Default value
You can define a default value for a field if it's missing from the params.
schema = %{
status: [type: :string, default: "pending"]}
Or you can define a default value as a function. This function is evaluated when `Tarams.cast` gets invoked.
schema = %{
date: [type: :utc_datetime, default: &Timex.now/0]}
### Custom cast function
You can define your own casting function, `tarams` provide `cast_func` option.
Your `cast_func` must follows this spec
#### 1. Custom cast fuction accept value only
fn(any) :: {:ok, any} | {:error, binary} | :error
def my_array_parser(value) do
if is_binary(value) do
ids =
String.split(value, ",")
|> Enum.map(&String.to_integer(&1))
{:ok, ids}
else
{:error, "Invalid string"
endend
schema = %{
user_id: [type: {:array, :integer}, cast_func: &my_array_parser/1]}
Tarams.cast(%{user_id: "1,2,3"}, schema)
This is a demo parser function.
#### 2. Custom cast function accept value and current object
data = %{ name: "tada", bold: true }
schema = %{
name: [type: :string, cast_func: fn value, data ->
{:ok, (if data.bold, do: String.upcase(value), else: value)}
end]}
Tarams.cast(data, schema)
> %{name: "TADA"}
#### 3.Custom cast function accept tuple {M, f}
Your cast function must accept 2 arguments
defmodule MyModule do
def upcase(value, data) do
{:ok, (if data.bold, do: String.upcase(value), else: value)}
endend
data = %{ name: "tada", bold: true }
schema = %{
name: [type: :string, cast_func: {MyModule, :upcase}]}
Tarams.cast(data, schema)
> %{name: "TADA"}
### Nested schema
With `Tarams` you can parse and validate nested map and list easily
@my_schema %{
status: :string,
pagination: %{
page: [type: :integer, number: [min: 1]],
size: [type: :integer, number: [min: 10, max: 100"]]
}}
Or nested list schema
@user_schema %{
name: :string,
email: [type: :string, required: true]
addresses: [type: {:array, %{
street: :string,
district: :string,
city: :string
}}]}
## Validation
`Tarams` uses `Valdi` validation library. You can read more about [Valdi here](https://github.com/bluzky/valdi)
Basically it supports following validation
- validate inclusion/exclusion
- validate length for string and enumerable types
- validate number
- validate string format/pattern
- validate custom function
- validate required(not nil) or not
- validate each array item
product_schema = %{
sku: [type: :string, required: true, length: [min: 6, max: 20]]
name: [type: :string, required: true],
quantity: [type: :integer, number: [min: 0]],
type: [type: :string, in: ~w(physical digital)],
expiration_date: [type: :naive_datetime, func: &my_validation_func/1],
# dynamic required
width: [type: :integer, required: fn value, data -> data.type == "physical" end],
# validate each array item
tags: [type: {:array, :string}, each: [length: [max: 50]]]}
### Dynamic required
- Can accept function or `{module, function}` tuple
- Only support 2 arity function
def require_email?(value, data), do: is_nil(email.phone)
....
%{ phone: :string name: [type: :string, required: fn value, data -> true end], email: [type: :string, required: {MODULE, :require_email?}] }
### Validate array item
Support validate array item with `:each` option, `each` accept a list of validators
%{ values: [type: {:array, :number}, each: [number: [min: 20, max: 50]]] }
## Transform data
### Field name alias
You can set alias name for schema fieldsdata = %{ name: "tada" }
schema = %{ name: [type: :string, as: :full_name] }
Tarams.cast(data, schema)
# > %{full_name: "tada"}
### Convert data
You can specify a function similar to `cast_func` to manipulate data after casted.
However data object passed to transform function is original data before casting.
data = %{status: 10}
schema = %{ name: [type: :string, into: fn value -> {:ok, "name: #{value}}" end] }
Tarams.cast(data, schema) # > %{name: "name: tada"}
- Transform function can return tuple `{:ok, value}`, `{:error, message}` or value directly.
schema = %{ value: [type: :integer, into: &to_string/1] }
## Contributors
If you find a bug or want to improve something, please send a pull request. Thank you!