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

Validator that optionally returns struct instances and executes model validators.

This module extends the existing validation logic to support:
1. Returning struct instances when a schema is defined with `define_struct: true`
2. Executing model validators after field validation succeeds
3. Computing derived fields after model validation succeeds

The validation pipeline:
1. Field validation (existing logic)
2. Model validation (Phase 2)
3. Computed field execution (Phase 3)
4. Struct creation (Phase 1)

## Phase 4 Enhancement: Anonymous Function Support

Enhanced to properly handle both named functions and generated anonymous functions
in model validators and computed fields. The validator can now execute:

1. Named function model validators: `{MyModule, :validate_something}`
2. Generated anonymous function validators: `{MyModule, :__generated_model_validator_123_456}`
3. Named function computed fields: `{field_name, %ComputedFieldMeta{function_name: :my_function}}`
4. Generated anonymous computed fields: `{field_name, %ComputedFieldMeta{function_name: :__generated_computed_field_name_123_456}}`

All function types are handled uniformly through the same execution pipeline.

# `validate_schema`

```elixir
@spec validate_schema(module(), map(), [atom() | String.t() | integer()]) ::
  {:ok, map() | struct()} | {:error, [Exdantic.Error.t()]}
```

Validates data against a schema with full pipeline support including computed fields.

## Enhanced Validation Pipeline
1. **Field Validation**: Validates individual fields using existing logic
2. **Model Validation**: Executes model validators in sequence (Phase 2)
3. **Computed Field Execution**: Executes computed fields to derive additional data (Phase 3)
4. **Struct Creation**: Optionally creates struct instance (Phase 1)

## Parameters
  * `schema_module` - The schema module to validate against
  * `data` - The data to validate (must be a map)
  * `path` - Current validation path for error reporting (defaults to `[]`)

## Returns
  * `{:ok, validated_data}` - where `validated_data` includes computed fields and is a struct if the schema
    defines one, otherwise a map
  * `{:error, errors}` - list of validation errors

## Model Validator Execution
Model validators are executed in the order they are declared in the schema.
Note: The validators are stored in reverse order due to Elixir's accumulate attribute behavior,
but they are reversed back during execution to maintain declaration order.
If any model validator returns an error, execution stops and the error is returned.
Model validators can transform data by returning modified data in the success case.

## Computed Field Execution
Computed fields are executed after model validation succeeds. Each computed field function:
- Receives the validated data (including any transformations from model validators)
- Must return `{:ok, computed_value}` or `{:error, reason}`
- Has its return value validated against the declared field type
- Contributes to the final validated result

## Examples

    # Basic model validation with computed fields
    defmodule UserSchema do
      use Exdantic, define_struct: true

      schema do
        field :first_name, :string, required: true
        field :last_name, :string, required: true
        field :email, :string, required: true

        model_validator :normalize_names
        computed_field :full_name, :string, :generate_full_name
        computed_field :email_domain, :string, :extract_email_domain
      end

      def normalize_names(validated_data) do
        normalized = %{
          validated_data |
          first_name: String.trim(validated_data.first_name),
          last_name: String.trim(validated_data.last_name)
        }
        {:ok, normalized}
      end

      def generate_full_name(validated_data) do
        {:ok, "#{validated_data.first_name} #{validated_data.last_name}"}
      end

      def extract_email_domain(validated_data) do
        {:ok, validated_data.email |> String.split("@") |> List.last()}
      end
    end

    iex> UserSchema.validate(%{
    ...>   first_name: "  John  ",
    ...>   last_name: "  Doe  ",
    ...>   email: "john@example.com"
    ...> })
    {:ok, %UserSchema{
      first_name: "John",      # normalized by model validator
      last_name: "Doe",       # normalized by model validator
      email: "john@example.com",
      full_name: "John Doe",   # computed field
      email_domain: "example.com"  # computed field
    }}

## Error Handling
Model validators can return errors in several formats:
- `{:error, "string message"}` - converted to validation error
- `{:error, %Exdantic.Error{}}` - used directly
- Exception during execution - caught and converted to validation error

Computed field errors are handled gracefully:
- Function execution errors are caught and converted to validation errors
- Type validation errors for computed values are reported with field context
- Computed field errors include the field path and computation function reference

---

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