# `Sinter`
[🔗](https://github.com/nshkrdotcom/sinter/blob/v0.3.1/lib/sinter.ex#L1)

Unified schema definition, validation, and JSON generation for Elixir.

Sinter provides a focused, high-performance schema validation library designed
specifically for dynamic frameworks. It follows the "One True Way" principle:

- **One way** to define schemas (unified core engine)
- **One way** to validate data (single validation pipeline)
- **One way** to generate JSON Schema (unified generator)

## Quick Start

    # Define a schema
    fields = [
      {:name, :string, [required: true]},
      {:age, :integer, [optional: true, gt: 0]}
    ]
    schema = Sinter.Schema.define(fields)

    # Validate data
    {:ok, validated} = Sinter.Validator.validate(schema, %{
      name: "Alice",
      age: 30
    })

    # Generate JSON Schema
    json_schema = Sinter.JsonSchema.generate(schema)

## Dynamic Schema Creation

Sinter supports dynamic schema creation for teleprompters and runtime optimization:

    # Infer schema from examples (perfect for MIPRO teleprompter)
    examples = [
      %{"name" => "Alice", "age" => 30},
      %{"name" => "Bob", "age" => 25}
    ]
    schema = Sinter.infer_schema(examples)

    # Merge schemas for signature composition
    input_schema = Sinter.Schema.define([{:query, :string, [required: true]}])
    output_schema = Sinter.Schema.define([{:answer, :string, [required: true]}])
    program_schema = Sinter.merge_schemas([input_schema, output_schema])

## Convenience Helpers

This module provides convenient helper functions for common validation tasks
that internally use the core unified engine.

## Design Philosophy

Sinter distills data validation to its pure essence, extracting the essential
power from complex systems while eliminating unnecessary abstraction. It follows
three key principles:

1. **Validation, Not Transformation** - Sinter validates data structure and constraints
   but does not perform business logic transformations
2. **Runtime-First Design** - Schemas are data structures that can be created and
   modified at runtime, perfect for dynamic frameworks
3. **Unified Core Engine** - All validation flows through a single, well-tested
   pipeline for consistency and reliability

# `schema`

```elixir
@type schema() :: Sinter.Schema.t()
```

# `validation_opts`

```elixir
@type validation_opts() :: Sinter.Validator.validation_opts()
```

# `validation_result`

```elixir
@type validation_result() :: {:ok, term()} | {:error, [Sinter.Error.t()]}
```

# `batch_validator_for`

```elixir
@spec batch_validator_for([{atom(), Sinter.Types.type_spec()}], validation_opts()) ::
  (map() -&gt;
     {:ok, map()}
     | {:error,
        [
          Sinter.Error.t()
        ]})
```

Creates a batch validator function for multiple type specifications.

## Parameters

  * `field_specs` - List of field specifications or `{name, type_spec}` tuples
  * `base_opts` - Base validation options

## Examples

    iex> batch_validator = Sinter.batch_validator_for([
    ...>   {:name, :string},
    ...>   {:age, :integer}
    ...> ])
    iex> batch_validator.(%{name: "Alice", age: 30})
    {:ok, %{name: "Alice", age: 30}}

# `infer_schema`

```elixir
@spec infer_schema(
  [map()],
  keyword()
) :: Sinter.Schema.t()
```

Creates a schema by analyzing examples to infer field types and requirements.

This function is essential for DSPEx teleprompters like MIPRO that need to
dynamically optimize schemas based on program execution examples.

## Parameters

  * `examples` - List of maps representing example data
  * `opts` - Schema creation options

## Options

  * `:title` - Schema title
  * `:description` - Schema description
  * `:strict` - Whether to reject unknown fields (default: false)
  * `:min_occurrence_ratio` - Minimum ratio for field to be considered required (default: 0.8)

## Returns

  * `Schema.t()` - A schema inferred from the examples

## Examples

    iex> examples = [
    ...>   %{"name" => "Alice", "age" => 30},
    ...>   %{"name" => "Bob", "age" => 25},
    ...>   %{"name" => "Charlie", "age" => 35}
    ...> ]
    iex> schema = Sinter.infer_schema(examples)
    iex> fields = Sinter.Schema.fields(schema)
    iex> fields[:name].type
    :string
    iex> fields[:age].type
    :integer

## Algorithm

1. **Field Discovery**: Find all unique field names across examples
2. **Type Inference**: Determine the most common type for each field
3. **Requirement Analysis**: Fields present in >= min_occurrence_ratio are required
4. **Constraint Inference**: Infer basic constraints from value patterns

# `merge_schemas`

```elixir
@spec merge_schemas(
  [Sinter.Schema.t()],
  keyword()
) :: Sinter.Schema.t()
```

Merges multiple schemas into a single schema.

This is useful for DSPEx signature composition where you need to combine
input and output schemas or merge component signatures.

## Parameters

  * `schemas` - List of Schema.t() to merge
  * `opts` - Optional schema configuration overrides

## Merge Rules

1. **Fields**: All fields from all schemas are included
2. **Conflicts**: Later schemas override earlier ones for conflicting field definitions
3. **Configuration**: First non-nil configuration value wins, except for `:strict` where last wins

## Examples

    iex> input_schema = Sinter.Schema.define([
    ...>   {:query, :string, [required: true]}
    ...> ])
    iex> output_schema = Sinter.Schema.define([
    ...>   {:answer, :string, [required: true]},
    ...>   {:confidence, :float, [optional: true]}
    ...> ])
    iex> merged = Sinter.merge_schemas([input_schema, output_schema])
    iex> fields = Sinter.Schema.fields(merged)
    iex> Map.keys(fields)
    [:query, :answer, :confidence]

# `validate_many`

```elixir
@spec validate_many(
  [
    {Sinter.Types.type_spec(), term()}
    | {atom(), Sinter.Types.type_spec(), term()}
    | {atom(), Sinter.Types.type_spec(), term(), keyword()}
  ],
  validation_opts()
) :: {:ok, [term()]} | {:error, %{required(integer()) =&gt; [Sinter.Error.t()]}}
```

Validates multiple values efficiently against their respective type specifications.

## Parameters

  * `type_value_pairs` - List of `{type_spec, value}` or `{field_name, type_spec, value}` tuples
  * `opts` - Validation options

## Examples

    iex> Sinter.validate_many([
    ...>   {:string, "hello"},
    ...>   {:integer, 42},
    ...>   {:email, :string, "test@example.com", [format: ~r/@/]}
    ...> ])
    {:ok, ["hello", 42, "test@example.com"]}

# `validate_type`

```elixir
@spec validate_type(Sinter.Types.type_spec(), term(), validation_opts()) ::
  validation_result()
```

Validates a single value against a type specification.

This is a convenient helper for one-off type validation that creates a
temporary schema internally and uses the unified validation engine.

## Parameters

  * `type_spec` - The type specification to validate against
  * `value` - The value to validate
  * `opts` - Validation options

## Options

  * `:coerce` - Enable type coercion (default: false)
  * `:constraints` - Additional constraints to apply

## Returns

  * `{:ok, validated_value}` on success
  * `{:error, errors}` on validation failure

## Examples

    iex> Sinter.validate_type(:integer, "42", coerce: true)
    {:ok, 42}

    iex> Sinter.validate_type({:array, :string}, ["hello", "world"])
    {:ok, ["hello", "world"]}

    iex> {:error, [error]} = Sinter.validate_type(:string, 123)
    iex> error.code
    :type

# `validate_value`

```elixir
@spec validate_value(atom(), Sinter.Types.type_spec(), term(), validation_opts()) ::
  validation_result()
```

Validates a single named value against a type specification.

This is a convenient helper for single field validation that creates a
temporary schema internally.

## Parameters

  * `field_name` - Name for the field (used in error messages)
  * `type_spec` - The type specification to validate against
  * `value` - The value to validate
  * `opts` - Validation options and constraints

## Examples

    iex> Sinter.validate_value(:email, :string, "test@example.com",
    ...>   constraints: [format: ~r/@/])
    {:ok, "test@example.com"}

    iex> Sinter.validate_value(:score, :integer, "95",
    ...>   coerce: true, constraints: [gteq: 0, lteq: 100])
    {:ok, 95}

# `validator_for`

```elixir
@spec validator_for(Sinter.Types.type_spec(), validation_opts()) :: (term() -&gt;
                                                                 validation_result())
```

Creates a reusable validation function for a specific type and constraints.

Returns a function that can be used to validate multiple values against
the same specification efficiently.

## Parameters

  * `type_spec` - The type specification
  * `base_opts` - Base validation options and constraints

## Examples

    iex> email_validator = Sinter.validator_for(:string,
    ...>   constraints: [format: ~r/@/])
    iex> email_validator.("test@example.com")
    {:ok, "test@example.com"}
    iex> {:error, [error]} = email_validator.("invalid")
    iex> error.code
    :format

---

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