View Source Dsv Validator Documentation
Welcome to the guide on validating data with the Dsv
Validator module.
Dsv
provides multiple ways to validate data, catering to different user preferences.
This document outlines the various methods available for validating data.
Overview of Validation Approaches
Dsv offers different ways to validate data:
- Basic Validation: Perform validation on a single value using a keywrod list validators description.
- Pipeline Validation: Define validation rules for each field in a structured, readable manner.
- Mirror Data Structure Validation: Validate complex data structures by mirroring the structure.
- List of Paths and Validators: Use a list of paths and associated validators for nested data structures.
Basic Validation
Basic validation allows you to validate individual values quickly using the Dsv.validate/2
function.
Pass the value to validate as the first argument and specify the validator in the second argument.
Single value one validator
To validate simple data like string, number etc. use Dsv.validate/2
function.
Example single value validation with single validator:
iex> Dsv.validate("Test name", length: [min: 3])
:ok
This code will check if "Test name"
value is at least of length of 3.
As the validator passed, result is :ok
Single value multiple validators
You can combine multiple validators, and validation passes only if all of them succeed. When the value is valid, Dsv.validate/2
returns :ok
. If invalid, it returns a tuple with a list of errors.
Example - basic validation, multiple validators
Check if the value has a minimum length of 3 and starts with "Test".
iex> Dsv.validate("Test name", length: [min: 3], format: ~r/^Test.*/)
:ok
Example - basic validation, multiple validators, failed validation
Check if the value has a minimum length of 3, maximum length of 4 and starts with "Test".
iex> Dsv.validate("Wrong name", length: [min: 3, max: 4], format: ~r/^Test.*/)
{
:error,
[
"Value \"Wrong name\" has wrong length. Minimum lenght is 3, maximum length is 4",
"Value Wrong name does not match pattern ^Test.*"
]
}
Pipeline validations
Pipeline validation allows for structured and readable approach to complex data structure validation. This approach consists of three steps: creating validation, adding validators, and validating data.
Create validation
To use pipeline validations, Dsv
needs to create a data structure that gathers all the information about the validation rules.
Dsv.validation/1
allows you to create this structure. To do so, call this function with the data to validate.
create validation example
validation = Dsv.validation(%{
"first" => "This is the first element to validate",
"second" => %{
"third" => 42, "fourth" => [-1, 0, 1]
}
})
This is the first step. The result of this step can the be pipelined into the Dsv.add_validator/3
function.
Add validators
After creating the validation, the next step is to add all the desirable validators.
For each field that will be validated, you should call Dsv.add_validator/3
.
This function will add a list of validators to the field specified in the path.
The path is in the form of a list, where each element of the list represents a part of the path to the validated element.
Add validators example
validation = validation
|> Dsv.add_validator(["first"], length: [min: 1])
|> Dsv.add_validator(["second", "fourth"], length: [max: 20], position: ["1": [equal: 0]])
Validate data
The final step in the pipeline is to initiate the validation process by calling the Dsv.validate
function.
validate data example
validation |> Dsv.validate
Pipeline validation - full example
%{"first" => "This is the first element to validate", "second" => %{"third" => 42, "fourth" => [-1, 0, 1]}}
|> Dsv.add_validator(["first"], length: [min: 1])
|> Dsv.add_validator(["second", "fourth"], length: [max: 20], position: ["1": [equal: 0]])
|> Dsv.validate
Two fields from validated data in one validator
Sometimes there is a need to use two different fields from input data in a single validator.
A good example is when a user sends a new password and a password confirmation.
In such cases, the validator needs to check if both values are equal.
To accomplish this, the Dsv.add_validator/4
function is designed.
It is intended to accept paths to both fields, where the first path functions as in the standard Dsv.add_validator/3
function, and the second path is used as an argument for the provided validator.
Two fields from validated data in one validator - example
%{user: %{password: "test", password_confirmation: "test_confirmation"}}
|> Dsv.validation
|> Dsv.add_validator([:user, :password], length: [min: 8, max: 40])
|> Dsv.add_validator([:user, :password], [:user, :password_confirmation], [:equal])
|> Dsv.validate
This will give the same result as:
%{user: %{password: "test", password_confirmation: "test_confirmation"}}
|> Dsv.validation
|> Dsv.add_validator([:user, :password], length: [min: 8, max: 40])
|> Dsv.add_validator([:user, :password], [equal: "test_confirmation"])
|> Dsv.validate
Two fields from validate data in one validator - number example
%{min_members: 10, max_members: 100, actual_members: 30}
|> Dsv.validation
|> Dsv.add_validator([:actual_members], [:min_members], [number: gte])
|> Dsv.add_validator([:actual_members], [:max_members], [number: lte])
|> Dsv.validate
This can be translated to:
%{min_members: 10, max_members: 100, actual_members: 30}
|> Dsv.validation
|> Dsv.add_validator([:actual_members], number: [gte: 10, lte: 100])
|> Dsv.validate
Struct example
It is possible to use pipelines to validate structs.
Lets define Address struct and the Person struct which will contain address.
defmodule Address do
defstruct [:street, :city, :country, :postal_code]
end
defmodule Person do
defstruct [:name, :surname, :age, :address]
end
Now we can validate those data
%Person{name: "Martin", surname: "Smith", age: 43, address: %Address{street: "St Marks Ave", city: "New York", country: "USA", postal_code: "11213"}}
|> Dsv.validation
|> Dsv.add_validator([:name], length: [min: 2, max: 30])
|> Dsv.add_validator([:surname], length: [min: 2, max: 30])
|> Dsv.add_validator([:age], number: [gt: 0, lt: 140])
|> Dsv.add_validator([:address, :street], length: [min: 1, max: 100])
|> Dsv.add_validator([:address, :country], in: ["USA", "Canada", "Mexico"])
|> Dsv.validate
Mirror data structure
This approach involves validating complex data structures by mirroring the structure in the validation rules, to do that use Dsv.validate/2
function.
Basic success example
Data to validate
user_info = %{
"name" => "Martin",
"age" => 45,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "company name",
"address" => %{
"city" => "New York",
"zip code" => "10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Validate user info data
Dsv.validate(user_info, %{
"name" => [length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
"age" => [number: [gte: 21, lte: 120]],
"company_data" => %{
"name" => [length: [min: 2]],
"address" => %{
"city" => [length: [min: 2]],
"zip code" => [format: ~r/[0-9]{1,5}/]
}
}
})
In this example input data meets validation createria, so the result of the validation will be :ok.
Basic failure example
Map with data to validate.
user_info = %{
"name" => "M",
"age" => 145,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "",
"address" => %{
"city" => "New York",
"zip code" => "a10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Validate data
Dsv.validate(user_info, %{
"name" => [length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
"age" => [number: [gte: 21, lte: 120]],
"company_data" => %{
"name" => [length: [min: 2]],
"address" => %{
"city" => [length: [min: 2]],
"zip code" => [format: ~r/[0-9]{1,5}/]
}
}
})
In this case validator will return a tuple with a map of errors:
{:error,
%{
"age" => ["Value 145 must be lower than or equal: 120 and greater than or equal: 21"],
"company_data" => %{
"name" => ["Value \"\" is to short. Minimum lenght is 2"]
},
"name" => [
"Value \"M\" has wrong length. Minimum lenght is 2, maximum length is 50",
"Value M does not match pattern ^[A-Z][a-z]+"
]
}}
Use input data as validator argument
Sometimes there is a need to use two different fields from input data in a single validator.
In this example, we want to compare the levels of two different skills.
To achieve this, the Dsv.validate/3
function is designed.
This function operates similarly to Dsv.validate/2
but with the addition of a list of comparators.
It is meant to accept a list of lists with two paths and validation options.
The first path is treated in the same way as in a standard validation definition, and the second path is expanded to its value and used as the argument for the validator options.
Success example
user_info = %{
"name" => "Martin",
"age" => 45,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "Company name",
"address" => %{
"city" => "New York",
"zip code" => "10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Dsv.validate(user_info, %{
"name" => [length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
"age" => [number: [gte: 21, lte: 120]],
"company_data" => %{
"name" => [length: [min: 2]],
"address" => %{
"city" => [length: [min: 2]],
"zip code" => [format: ~r/[0-9]{1,5}/]
}
}
},
[
[
["skills", :programming], ["skills", :cooking], [number: :gt]
]
])
:ok
Failure example
user_info = %{
"name" => "Martin",
"age" => 45,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "Company name",
"address" => %{
"city" => "New York",
"zip code" => "10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Dsv.validate(user_info, %{
"name" => [length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
"age" => [number: [gte: 21, lte: 120]],
"company_data" => %{
"name" => [length: [min: 2]],
"address" => %{
"city" => [length: [min: 2]],
"zip code" => [format: ~r/[0-9]{1,5}/]
}
}
},
[
[
["skills", :programming], ["skills", :cooking], [:equal]
]
])
{:error, %{"skills" => %{programming: ["Values must be equal"]}}}
Struct example
As in other approches it is possible to use map with validators to validate structs.
Lets define Address struct and the Person struct which will contain address.
defmodule Address do
defstruct [:street, :city, :country, :postal_code]
end
defmodule Person do
defstruct [:name, :surname, :age, :address]
end
Now we can validate those data
person = %Person{name: "Martin", surname: "Smith", age: 43, address: %Address{street: "St Marks Ave", city: "New York", country: "USA", postal_code: "11213"}}
Dsv.validate(person, %{
name: [length: [min: 2, max: 30]],
surname: [length: [min: 2, max: 30]],
age: [number: [gt: 0, lt: 140]],
address: %{
street: [length: [min: 1, max: 100]],
country: [in: ["USA", "Canada", "Mexico"]]
}
})
List of paths and validators
This approach involves using a list of keyword lists, where the path
key is used to define the path to the validated field, and other keys represent the names of the validators.
List of paths and validators - example
user_info = %{
"name" => "Martin",
"age" => 45,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "company name",
"address" => %{
"city" => "New York",
"zip code" => "10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Dsv.validate(user_info, paths([
[path: ["name"], length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
[path: ["age"], number: [gte: 21, lte: 120]],
[path: ["company_data", "name"], length: [min: 2]],
[path: ["company_data", "address", "city"], length: [min: 2]],
[path: ["company_data", "address", "zip code"], format: ~r/[0-9]{1,5}/]
]))
List of paths - error example
user_info = %{
"name" => "M",
"age" => 145,
"programming_languages" => ["PHP", "Python", "Java", "Go", "Elixir", "Kotlin"],
"company_data" => %{
"name" => "",
"address" => %{
"city" => "New York",
"zip code" => "a10001"
}
},
"skills" => %{
:programming => 100,
:cooking => 60
}
}
Dsv.validate(user_info, Dsv.paths([
[path: ["name"], length: [min: 2, max: 50], format: ~r/^[A-Z][a-z]+/],
[path: ["age"], number: [gte: 21, lte: 120]],
[path: ["company_data", "name"], length: [min: 2]],
[path: ["company_data", "address", "city"], length: [min: 2]],
[path: ["company_data", "address", "zip code"], format: ~r/[0-9]{1,5}/]
]))
{:error,
%{
"age" => ["Value 145 must be lower than or equal: 120 and greater than or equal: 21"],
"company_data" => %{
"name" => ["Value \"\" is to short. Minimum lenght is 2"]
},
"name" => [
"Value \"M\" has wrong length. Minimum lenght is 2, maximum length is 50",
"Value M does not match pattern ^[A-Z][a-z]+"
]
}}
Struct example
As in other approches it is possible to use list of paths with validators to validate structs.
Lets define Address struct and the Person struct which will contain address.
defmodule Address do
defstruct [:street, :city, :country, :postal_code]
end
defmodule Person do
defstruct [:name, :surname, :age, :address]
end
Now we can validate those data
person = %Person{name: "Martin", surname: "Smith", age: 43, address: %Address{street: "St Marks Ave", city: "New York", country: "USA", postal_code: "11213"}}
Dsv.validate(person, Dsv.paths([
[path: [:name], length: [min: 2, max: 30]],
[path: [:surname], length: [min: 2, max: 30]],
[path: [:age], number: [gt: 0, lt: 140]],
[path: [:address, :street], length: [min: 1, max: 100]],
[path: [:address, :country], in: ["USA", "Canada", "Mexico"]]
]))