# `Exdantic.Types`
[🔗](https://github.com/nshkrdotcom/exdantic/blob/v0.1.0/lib/exdantic/types.ex#L1)

Core type system for Exdantic schemas.

Provides functions for defining and working with types:
- Basic types (:string, :integer, :float, :boolean)
- Complex types (arrays, maps, unions)
- Type constraints
- Type validation
- Type coercion

## Basic Types

    # String type
    Types.string()

    # Integer type with constraints
    Types.integer()
    |> Types.with_constraints(gt: 0, lt: 100)

## Complex Types

    # Array of strings
    Types.array(Types.string())

    # Map with string keys and integer values
    Types.map(Types.string(), Types.integer())

    # Union of types
    Types.union([Types.string(), Types.integer()])

## Type Constraints

Constraints can be added to types to enforce additional rules:

    Types.string()
    |> Types.with_constraints([
      min_length: 3,
      max_length: 10,
      format: ~r/^[a-z]+$/
    ])

# `constraint_with_message`

```elixir
@type constraint_with_message() :: {atom(), any()} | {atom(), any(), String.t()}
```

# `type_definition`

```elixir
@type type_definition() ::
  {:type, atom(), [any()]}
  | {:array, type_definition(), [any()]}
  | {:map, {type_definition(), type_definition()}, [any()]}
  | {:object, %{required(atom()) =&gt; type_definition()}, [any()]}
  | {:union, [type_definition()], [any()]}
  | {:ref, atom()}
```

# `array`

```elixir
@spec array(type_definition()) :: {:array, type_definition(), []}
```

# `boolean`

```elixir
@spec boolean() :: {:type, :boolean, []}
```

# `coerce`

```elixir
@spec coerce(atom(), term()) :: {:ok, term()} | {:error, String.t()}
```

Coerces a value to the specified type.

## Parameters
  * `type` - The target type to coerce to
  * `value` - The value to coerce

## Returns
  * `{:ok, coerced_value}` on success
  * `{:error, reason}` on failure

## Examples

    iex> Exdantic.Types.coerce(:string, 42)
    {:ok, "42"}

    iex> Exdantic.Types.coerce(:integer, "123")
    {:ok, 123}

    iex> Exdantic.Types.coerce(:integer, "abc")
    {:error, "invalid integer format"}

# `float`

```elixir
@spec float() :: {:type, :float, []}
```

# `integer`

```elixir
@spec integer() :: {:type, :integer, []}
```

# `map`

```elixir
@spec map(type_definition(), type_definition()) ::
  {:map, {type_definition(), type_definition()}, []}
```

# `normalize_type`

```elixir
@spec normalize_type(term()) :: type_definition()
```

Normalizes a type definition to the standard internal format.

## Parameters
  * `type` - The type definition to normalize

## Returns
  * A normalized type definition tuple

## Examples

    iex> Exdantic.Types.normalize_type(:string)
    {:type, :string, []}

    iex> Exdantic.Types.normalize_type({:array, :integer})
    {:array, {:type, :integer, []}, []}

# `object`

```elixir
@spec object(%{required(atom()) =&gt; type_definition()}) ::
  {:object, %{required(atom()) =&gt; type_definition()}, []}
```

# `ref`

```elixir
@spec ref(atom()) :: {:ref, atom()}
```

# `string`

```elixir
@spec string() :: {:type, :string, []}
```

# `tuple`

```elixir
@spec tuple([type_definition()]) :: {:tuple, [type_definition()]}
```

# `type`

```elixir
@spec type(atom()) :: {:type, atom(), []}
```

# `union`

```elixir
@spec union([type_definition()]) :: {:union, [type_definition()], []}
```

# `validate`

```elixir
@spec validate(atom(), term()) :: {:ok, term()} | {:error, Exdantic.Error.t()}
```

Validates a value against a basic type.

## Parameters
  * `type` - The type to validate against
  * `value` - The value to validate

## Returns
  * `{:ok, value}` if validation succeeds
  * `{:error, Exdantic.Error.t()}` if validation fails

## Examples

    iex> Exdantic.Types.validate(:string, "hello")
    {:ok, "hello"}

    iex> Exdantic.Types.validate(:integer, "not a number")
    {:error, %Exdantic.Error{path: [], code: :type, message: "expected integer, got "not a number""}}

# `with_constraints`

```elixir
@spec with_constraints(type_definition(), [term()]) :: {atom(), term(), [term()]}
```

Adds constraints to a type definition.

## Parameters
  * `type` - The type definition to add constraints to
  * `constraints` - List of constraints to add

## Returns
  * Updated type definition with constraints

## Examples

    iex> string_type = Exdantic.Types.string()
    iex> Exdantic.Types.with_constraints(string_type, [min_length: 3, max_length: 10])
    {:type, :string, [min_length: 3, max_length: 10]}

# `with_error_message`

```elixir
@spec with_error_message(type_definition(), atom(), String.t()) ::
  {atom(), term(), [term()]}
```

Adds a custom error message for a specific constraint to a type definition.

## Parameters
  * `type` - The type definition to add the custom error message to
  * `constraint` - The constraint name (atom) to customize the error for
  * `message` - The custom error message to use when this constraint fails

## Returns
  * Updated type definition with custom error message

## Examples

    iex> string_type = Exdantic.Types.string()
    iex> |> Exdantic.Types.with_constraints([min_length: 3])
    iex> |> Exdantic.Types.with_error_message(:min_length, "Name must be at least 3 characters long")
    {:type, :string, [min_length: 3, {:error_message, :min_length, "Name must be at least 3 characters long"}]}

# `with_error_messages`

```elixir
@spec with_error_messages(
  type_definition(),
  [{atom(), String.t()}] | %{required(atom()) =&gt; String.t()}
) ::
  {atom(), term(), [term()]}
```

Adds multiple custom error messages for constraints to a type definition.

## Parameters
  * `type` - The type definition to add the custom error messages to
  * `error_messages` - A keyword list or map of constraint => message pairs

## Returns
  * Updated type definition with custom error messages

## Examples

    iex> string_type = Exdantic.Types.string()
    iex> |> Exdantic.Types.with_constraints([min_length: 3, max_length: 50])
    iex> |> Exdantic.Types.with_error_messages([
    iex>      min_length: "Name must be at least 3 characters long",
    iex>      max_length: "Name cannot exceed 50 characters"
    iex>    ])
    {:type, :string, [min_length: 3, max_length: 50, {:error_message, :min_length, "Name must be at least 3 characters long"}, {:error_message, :max_length, "Name cannot exceed 50 characters"}]}

# `with_validator`

```elixir
@spec with_validator(type_definition(), (term() -&gt;
                                     {:ok, term()} | {:error, String.t()})) ::
  {atom(), term(), [term()]}
```

Adds a custom validation function to a type definition.

## Parameters
  * `type` - The type definition to add the custom validator to
  * `validator_fn` - A function that takes a value and returns {:ok, value} | {:error, message}

## Returns
  * Updated type definition with custom validator

## Examples

    iex> email_type = Exdantic.Types.string()
    iex> |> Exdantic.Types.with_constraints([min_length: 3])
    iex> |> Exdantic.Types.with_validator(fn value ->
    iex>      if String.contains?(value, "@"), do: {:ok, value}, else: {:error, "Must contain @"}
    iex>    end)
    {:type, :string, [min_length: 3, {:validator, #Function<...>}]}

---

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