# `Valdi`
[🔗](https://github.com/bluzky/valdi/blob/main/lib/valdi.ex#L1)

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
```elixir
# 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!)
```elixir
# 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
```elixir
# 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:

```elixir
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"])
```

# `error`

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

# `support_length_types`

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

# `validate`

Validate value against list of validations.

```elixir
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`)

```elixir
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`

> This function is deprecated. Use validate_number/2 instead, which now supports both numbers and Decimal types.

```elixir
@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.

```elixir
# 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`

Apply validation for each array item

# `validate_embed`

Validate embedded types using a module's `validate/2` callback.

Accepts an `{:embed, module, params}` tuple where `module` must implement `validate/2`.
Also supports `{:array, {:embed, module, params}}` to validate a list of embedded values.

```elixir
# Single embedded struct
Valdi.validate_embed(%MyStruct{}, {:embed, MyStruct, [field: [type: :string]]})

# Array of embedded structs
Valdi.validate_embed([%MyStruct{}], {:array, {:embed, MyStruct, [field: [type: :string]]}})
```

# `validate_exclusion`

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

# `validate_format`

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

Checks whether a string match the given regex pattern.

```elixir
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`

Check if value is included in the given enumerable.

```elixir
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, "must be in the inclusion list"}
iex> Valdi.validate_inclusion({:a, 1}, %{a: 1, b: 2})
:ok
```

# `validate_length`

```elixir
@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`

```elixir
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`

Validate list value against 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]`

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

# `validate_map`

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.

```elixir
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`

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

Validate number value

```elixir
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`

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

```elixir
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`

Validate data types.

```elixir
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
- `%{key: validators}` : inline map validation spec (delegates to `validate_map/3`)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
