Valdi (Valdi v0.6.0)

View Source

A comprehensive Elixir data validation library that provides flexible and composable validation functions.

Features

  • Type validation - validate data types including numbers, strings, lists, maps, structs, and Decimal types
  • Constraint validation - validate ranges, lengths, formats, and inclusion/exclusion
  • Flattened validators - use convenient aliases like min, max, min_length without nesting
  • Pattern matching - efficient validation dispatch using Elixir's pattern matching
  • Composable - combine multiple validations in a single call
  • Backward compatible - works with existing validation patterns
  • Conditional type checking - skip type validation when not needed for better performance

Quick Examples

Basic validation

# Type validation
Valdi.validate("hello", type: :string)
#=> :ok

# Constraint validation without type checking
Valdi.validate("hello", min_length: 3, max_length: 10)
#=> :ok

# Combined validations
Valdi.validate(15, type: :integer, min: 10, max: 20, greater_than: 5)
#=> :ok

Flattened validators (new!)

# Instead of nested syntax
Valdi.validate("test", type: :string, length: [min: 3, max: 10])

# Use flattened syntax
Valdi.validate("test", type: :string, min_length: 3, max_length: 10)

# Mix both styles
Valdi.validate(15, min: 10, number: [max: 20])

List and map validation

# Validate each item in a list
Valdi.validate_list([1, 2, 3], type: :integer, min: 0)

# Validate map with schema
schema = %{
  name: [type: :string, required: true, min_length: 2],
  age: [type: :integer, min: 0, max: 150],
  email: [type: :string, format: ~r/.+@.+/]
}
Valdi.validate_map(%{name: "John", age: 30}, schema)

Available Validators

Core validators

  • type - validate data type
  • required - ensure value is not nil
  • format/pattern - regex pattern matching
  • in/enum - value inclusion validation
  • not_in - value exclusion validation
  • func - custom validation function

Numeric validators

  • number - numeric constraints (nested)
  • min - minimum value (≥)
  • max - maximum value (≤)
  • greater_than - strictly greater than (>)
  • less_than - strictly less than (<)

Length validators

  • length - length constraints (nested)
  • min_length - minimum length
  • max_length - maximum length
  • min_items - minimum array items (alias for min_length)
  • max_items - maximum array items (alias for max_length)

Other validators

  • each - validate each item in arrays
  • decimal - decimal validation (deprecated, use number instead)

Options

  • ignore_unknown: true - skip unknown validators instead of returning errors

Individual Validation Functions

Each validator can also be used independently:

Valdi.validate_type("hello", :string)
Valdi.validate_number(15, min: 10, max: 20)
Valdi.validate_length("hello", min: 3, max: 10)
Valdi.validate_inclusion("red", ["red", "green", "blue"])

Summary

Functions

Validate value against list of validations.

Validate decimal values.

Apply validation for each array item

Validate embed types

Check if value is not included in the given enumerable. Similar to validate_inclusion/2

Checks whether a string match the given regex pattern.

Check if value is included in the given enumerable.

Check if length of value match given conditions. Length condions are the same with validate_number/2

Validate list value aganst validator and return error if any item is not valid. In case of error {:error, errors}, errors is list of error detail for all error item includes [index, message]

Validate map value with given map specification. Validation spec is a map

Validate number value

Validate value if value is not nil. This function can receive a function to dynamicall calculate required or not.

Validate data types.

Types

error()

@type error() :: {:error, String.t()}

support_length_types()

@type support_length_types() :: String.t() | map() | list() | tuple()

Functions

validate(value, validators, opts \\ [])

Validate value against list of validations.

iex> Valdi.validate("email@g.c", type: :string, format: ~r/.+@.+.[a-z]{2,10}/)
{:error, "does not match format"}

All supported validations:

  • type: validate datatype
  • format|pattern: check if binary value matched given regex
  • number: validate number value (supports both regular numbers and Decimal types)
  • length: validate length of supported types. See validate_length/2 for more details.
  • in|enum: validate inclusion
  • not_in: validate exclusion
  • min: validate minimum value for numbers
  • max: validate maximum value for numbers
  • greater_than: validate value is greater than specified number
  • less_than: validate value is less than specified number
  • min_length: validate minimum length for strings, lists, maps, tuples
  • max_length: validate maximum length for strings, lists, maps, tuples
  • min_items: validate minimum number of items in arrays (alias for min_length)
  • max_items: validate maximum number of items in arrays (alias for max_length)
  • func: custom validation function follows spec func(any()):: :ok | {:error, message::String.t()}

  • each: validate each item in list with given validator. Supports all above validator
  • decimal: validate decimal values (deprecated, use number instead)

Options:

  • ignore_unknown: when true, unknown validators are ignored instead of returning an error (default: false)
iex> Valdi.validate("test", [type: :string, unknown_validator: :value], ignore_unknown: true)
:ok
iex> Valdi.validate("test", [type: :string, unknown_validator: :value], ignore_unknown: false)
{:error, "validate_unknown_validator is not supported"}
iex> Valdi.validate(15, type: :integer, min: 10, max: 20)
:ok
iex> Valdi.validate(15, type: :integer, greater_than: 10, less_than: 20)
:ok
iex> Valdi.validate("hello", type: :string, min_length: 3, max_length: 10)
:ok
iex> Valdi.validate([1, 2, 3], type: :list, min_items: 2, max_items: 5)
:ok
iex> Valdi.validate("hello", min_length: 3)
:ok

validate_decimal(value, checks)

This function is deprecated. Use validate_number/2 instead, which now supports both numbers and Decimal types.
@spec validate_decimal(
  Decimal.t(),
  keyword()
) :: :ok | error()

Validate decimal values.

Deprecated: Use validate_number/2 instead, which now supports both numbers and Decimal types.

# Instead of this (deprecated):
Valdi.validate_decimal(Decimal.new("12.5"), min: Decimal.new("10.0"))

# Use this:
Valdi.validate_number(Decimal.new("12.5"), min: Decimal.new("10.0"))

validate_each_item(list, validations, opts \\ [])

Apply validation for each array item

validate_embed(value, embed_type)

Validate embed types

validate_exclusion(value, enum)

Check if value is not included in the given enumerable. Similar to validate_inclusion/2

validate_format(value, check)

@spec validate_format(String.t(), Regex.t() | String.t()) :: :ok | error()

Checks whether a string match the given regex pattern.

iex> Valdi.validate_format("year: 2001", ~r/year:\s\d{4}/)
:ok
iex> Valdi.validate_format("hello", ~r/+/)
{:error, "does not match format"}
iex> Valdi.validate_format("hello", "h.*o")
:ok

validate_inclusion(value, enum)

Check if value is included in the given enumerable.

iex> Valdi.validate_inclusion(1, [1, 2])
:ok
iex> Valdi.validate_inclusion(1, {1, 2})
{:error, "given condition does not implement protocol Enumerable"}
iex> Valdi.validate_inclusion(1, %{a: 1, b: 2})
{:error, "not be in the inclusion list"}
iex> Valdi.validate_inclusion({:a, 1}, %{a: 1, b: 2})
:ok

validate_length(value, checks)

@spec validate_length(
  support_length_types(),
  keyword()
) :: :ok | error()

Check if length of value match given conditions. Length condions are the same with validate_number/2

iex> Valdi.validate_length([1], min: 2)
{:error, "length must be greater than or equal to 2"}
iex> Valdi.validate_length("hello", equal_to: 5)
:ok

Supported types

  • list
  • map
  • tuple
  • keyword
  • string

validate_list(items, validators, opts \\ [])

Validate list value aganst validator and return error if any item is not valid. In case of error {:error, errors}, errors is list of error detail for all error item includes [index, message]

iex> Valdi.validate_list([1,2,3], type: :integer, number: [min: 2])
{:error, [[0, "must be greater than or equal to 2"]]}

validate_map(data, validations_spec, opts \\ [])

Validate map value with given map specification. Validation spec is a map

validate_map use the key from validation to extract value from input data map and then validate value against the validators for that key.

In case of error, the error detail is a map of error for each key.

iex> validation_spec = %{
...>  email: [type: :string, required: true],
...>  password: [type: :string, length: [min: 8]],
...>  age: [type: :integer, number: [min: 16, max: 60]]
...>  }
iex> Valdi.validate_map(%{name: "dzung", password: "123456", email: "ddd@example.com", age: 28}, validation_spec)
{:error, %{password: "length must be greater than or equal to 8"}}

validate_number(value, checks)

@spec validate_number(
  integer() | float() | Decimal.t(),
  keyword()
) :: :ok | error()

Validate number value

iex> Valdi.validate_number(12, min: 10, max: 12)
:ok
iex> Valdi.validate_number(12, min: 15)
{:error, "must be greater than or equal to 15"}
iex> Valdi.validate_number(Decimal.new("12.5"), min: Decimal.new("10.0"))
:ok

Support conditions

  • equal_to
  • greater_than_or_equal_to | min

  • greater_than
  • less_than
  • less_than_or_equal_to | max

Works with both regular numbers and Decimal types.

validate_required(value, func)

Validate value if value is not nil. This function can receive a function to dynamicall calculate required or not.

iex> Valdi.validate_required(nil, true)
{:error, "is required"}
iex> Valdi.validate_required(1, true)
:ok
iex> Valdi.validate_required(nil, false)
:ok
iex> Valdi.validate_required(nil, fn -> 2 == 2 end)
{:error, "is required"}

validate_type(value, map)

Validate data types.

iex> Valdi.validate_type("a string", :string)
:ok
iex> Valdi.validate_type("a string", :number)
{:error, "is not a number"}

Support built-in types:

  • boolean
  • integer
  • float
  • number (integer or float)
  • string | binary

  • tuple
  • map
  • array
  • atom
  • function
  • keyword
  • date
  • datetime
  • naive_datetime
  • time

It can also check extend types

  • struct Ex: User
  • {:array, type} : array of type