Validate
Validate incoming requests in an easy to reason-about way using Elixir.
🚨 Validate is an active WIP. Building mostly for fun/to satisfy my own needs on a project 🚨
Coming from languages like PHP and Node.js it can be difficult to reason about validating your requests using Ecto. This provides a simple data validation layer that aims to be extensible to allow for custom validation logic provided by the user.
Todo
Give option to provide atoms in rule lists for simple validators to get rid of needing to import each one-
Test usage with mixed keys (
:atomsand"strings") - Add more out-of-the-box validators
Provide documentation on custom validators- i18n support for errors
- Support more input params than just maps
-
Make
:skipapi more clear - Turn errors into list
Installation
The package can be installed
by adding validate to your list of dependencies in mix.exs:
def deps do
[
{:validate, "~> 0.2.4"}
]
endUsage
defmodule MyApp.UserController do
import Validate
def create(conn, params) do
case validate(params, create_rules) do
{:ok, data} ->
# ... create user
# `data` is filtered to only the keys provided in `rules`
{:error, errors} ->
# errors is a map that matches the rules provided
# in this case: %{ "username" => "required" }
json(conn, errors)
end
end
defp create_rules, do: %{
"username": [:required],
}
endCustom Validators
Writing your own validator is as simple as writing a function that returns one of four results:
# valid, pass on value and continue with rest of validators
{:ok, value}
# invalid, provide reason
{:error, message}
# (WIP Prevent further validation)
# invalid, provide reason and do not continue with rest of validators
{:skip, message}
# valid, but do not continue
{:skip}
Let's build a validator that expects a string and ensures that it is one of n values.
defmodule MyApp.Allowed do
def allowed(ok_values, value) when value in ok_values, do: {:ok, value}
def allowed(_ok_values, _value), do: {:error, "value not allowed"}
endNow we can simply use it like so:
import Validate
import MyApp.Allowed
input = %{
"plan" => "elite"
}
rules = %{
"plan" => [:required, :string, &allowed(["basic", "pro"], &1)]
}
validate(input, rules)
# {:error, %{"plan" => "value not allowed"}}Validators
Required
Validates input is not:
undefinednull""[]{}
import Validate
data = %{ "username" => "" }
rules = %{
"username" => [
:required
]
}
validate(data, rules)
# {:error, %{ "username" => "required" }}Optional
Does not continue with the rest of the validators if the value is not present or nil
undefinednull
import Validate
data = %{}
rules = %{
"username" => [
:optional,
:string,
]
}
# value not present, so it's ok
validate(data, rules)
# {:ok, %{}}import Validate
data = %{ "username" => 123 }
rules = %{
"username" => [
:optional,
:string,
]
}
# value present, so it continues on to next validators
validate(data, rules)
# {:error, %{"username" => "not a string"}}String
Validates input is a string
import Validate
data = %{
"username" => 123
}
rules = %{
"username" => [
:string,
]
}
validate(data, rules)
# {:error, %{"username" => "not a string"}}Number
Validates input is a number (float or int)
import Validate
data = %{
"balance" => "very low"
}
rules = %{
"balance" => [
:number,
]
}
validate(data, rules)
# {:error, %{"balance" => "not a number"}}List
Validates input is a list (array)
import Validate
data = %{
"cities" => "saskatoon"
}
rules = %{
"cities" => [
:list,
]
}
validate(data, rules)
# {:error, %{"balance" => "not a list"}}Map
Validates input is a map (object). Can be used to specify nested validations.
Simple use case:
import Validate
data = %{
"user" => 123
}
rules = %{
"user" => [:map]
}
validate(data, rules)
# {:error, %{"user" => "not a map"}}Nested use case:
import Validate
data = %{
"user" => %{
"username" => "nehero",
"password" => "",
"team" => %{
"name" => ""
}
}
}
rules = %{
"user" => [
:required,
map: %{
"username" => [:required, :string],
"password" => [:required, :string],
"team" => [map: %{
"name" => [:required, :string]
}]
}
]
}
validate(data, rules)
#{
# :error,
# %{
# "user" => %{
# "password" => "required",
# "team" => %{
# "name" => "required"
# }
# }
# }
#}
"""