Phoenix request params validation library.
Build Status Coverage Status Hex Version docs
Warning: Tarams v1.0.0 APIs is not back compatible
- Reduce code boilerplate
- Shorter schema definition
- Default function which generate value each casting time
- Custom validation functions
- Custom parse functions
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
Process order
Cast data -> validate casted data -> transform data
@index_params_schema %{ keyword: :string, status: [type: :string, required: true], group_id: [type: :integer, number: [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 end end
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:
typeis required,Taramssupport same data type asEcto. I borrowed code from Ectodefault: default value or default functioncast_func: custom cast functionnumber, format, length, in, not_in, func, required, eachare available validationsfrom: use value from another fieldas: alias key you will receive fromTarams.castif casting is succeeded
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] }
You can define your own casting function, tarams provide cast_func option.
Your cast_func must follows this spec
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" end end 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.
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"}
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)} end end
data = %{ name: "tada", bold: true } schema = %{ name: [type: :string, cast_func: {MyModule, :upcase}] } Tarams.cast(data, schema) # > %{name: "TADA"}
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 }}] }
Tarams uses Valdi validation library. You can read more about Valdi here
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]]] }
- 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?}] }
Support validate array item with :each option, each accept a list of validators
%{ values: [type: {:array, :number}, each: [number: [min: 20, max: 50]]] }
You can set alias name for schema fields
data = %{ name: "tada" } schema = %{ name: [type: :string, as: :full_name] } Tarams.cast(data, schema) # > %{full_name: "tada"}
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] }
If you find a bug or want to improve something, please send a pull request. Thank you!