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

Unified schema definition for Sinter.

This module provides the single, canonical way to define data validation schemas
in Sinter. It follows the "One True Way" principle - all schema creation flows
through `define/2`, whether at runtime or compile-time.

## Basic Usage

    # Runtime schema definition
    schema = Sinter.Schema.define([
      {:name, :string, [required: true, min_length: 2]},
      {:age, :integer, [optional: true, gt: 0]}
    ])

    # Compile-time schema definition
    defmodule UserSchema do
      use Sinter.Schema

      use_schema do
        field :name, :string, required: true, min_length: 2
        field :age, :integer, optional: true, gt: 0
      end
    end

## Field Specifications

Each field is specified as a tuple: `{name, type_spec, options}`

### Supported Options

* `:required` - Field must be present (default: true)
* `:optional` - Field may be omitted (default: false)
* `:default` - Default value if field is missing (implies optional: true)
* `:description` - Human-readable description
* `:example` - Example value for documentation

### Constraints

* `:min_length`, `:max_length` - For strings and arrays
* `:gt`, `:gteq`, `:lt`, `:lteq` - For numbers
* `:format` - Regex pattern for strings
* `:choices` - List of allowed values

## Schema Configuration

* `:title` - Schema title for documentation
* `:description` - Schema description
* `:strict` - Reject unknown fields (default: false)
* `:post_validate` - Custom validation function

# `config`

```elixir
@type config() :: %{
  title: String.t() | nil,
  description: String.t() | nil,
  strict: boolean(),
  post_validate: function() | nil,
  pre_validate: function() | nil
}
```

# `field_definition`

```elixir
@type field_definition() :: %{
  name: String.t(),
  type: Sinter.Types.type_spec(),
  required: boolean(),
  constraints: keyword(),
  description: String.t() | nil,
  example: term() | nil,
  default: term() | nil,
  validate: function() | [function()] | nil,
  alias: String.t() | nil
}
```

# `field_spec`

```elixir
@type field_spec() :: {atom() | String.t(), Sinter.Types.type_spec(), keyword()}
```

# `metadata`

```elixir
@type metadata() :: %{
  created_at: DateTime.t(),
  field_count: non_neg_integer(),
  sinter_version: String.t()
}
```

# `t`

```elixir
@type t() :: %Sinter.Schema{
  config: config(),
  definition: map(),
  fields: %{required(String.t()) =&gt; field_definition()},
  metadata: metadata()
}
```

# `config`

```elixir
@spec config(t()) :: config()
```

Returns the schema configuration.

## Examples

    iex> schema = Sinter.Schema.define([], title: "Test Schema")
    iex> config = Sinter.Schema.config(schema)
    iex> config.title
    "Test Schema"

# `constraints`

```elixir
@spec constraints(t()) :: %{required(String.t()) =&gt; keyword()}
```

Extracts constraint information from schema fields.

Returns a map of field names to their constraint lists, useful for
teleprompter analysis and optimization.

## Parameters

  * `schema` - A Sinter schema

## Returns

  * A map of field_name => constraints_list

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true, min_length: 2, max_length: 50]},
    ...>   {:score, :integer, [required: true, gt: 0, lteq: 100]}
    ...> ])
    iex> Sinter.Schema.constraints(schema)
    %{
      name: [min_length: 2, max_length: 50],
      score: [gt: 0, lteq: 100]
    }

# `define`

```elixir
@spec define(
  [field_spec()],
  keyword()
) :: t()
```

Defines a schema from field specifications.

This is the unified entry point for all schema creation in Sinter.
Both runtime and compile-time schema definition ultimately use this function.

## Parameters

  * `field_specs` - List of field specifications
  * `opts` - Schema configuration options

## Options

  * `:title` - Schema title for documentation
  * `:description` - Schema description
  * `:strict` - Reject unknown fields (default: false)
  * `:post_validate` - Custom validation function

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true, min_length: 2]},
    ...>   {:age, :integer, [optional: true, gt: 0]}
    ...> ], title: "User Schema")
    iex> schema.config.title
    "User Schema"

    iex> schema.fields[:name].required
    true

# `field`
*macro* 

Defines a field in the schema.

Used within `use_schema` blocks.

## Examples

    field :name, :string, required: true, min_length: 2
    field :age, :integer, optional: true, gt: 0
    field :active, :boolean, optional: true, default: true

# `field_aliases`

```elixir
@spec field_aliases(t()) :: %{required(String.t()) =&gt; String.t()}
```

Returns a map of canonical field names to their aliases.

Only includes fields that have aliases defined.

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:account_name, :string, [alias: "accountName"]},
    ...>   {:no_alias, :string, []}
    ...> ])
    iex> Sinter.Schema.field_aliases(schema)
    %{"account_name" => "accountName"}

# `field_types`

```elixir
@spec field_types(t()) :: %{required(String.t()) =&gt; Sinter.Types.type_spec()}
```

Extracts field types from a schema for analysis and introspection.

This is useful for DSPEx teleprompters that need to analyze the structure
of schemas for optimization purposes.

## Parameters

  * `schema` - A Sinter schema

## Returns

  * A map of field_name => type_spec

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true]},
    ...>   {:tags, {:array, :string}, [optional: true]}
    ...> ])
    iex> Sinter.Schema.field_types(schema)
    %{
      name: :string,
      tags: {:array, :string}
    }

# `fields`

```elixir
@spec fields(t()) :: %{required(String.t()) =&gt; field_definition()}
```

Returns the field definitions map.

## Examples

    iex> schema = Sinter.Schema.define([{:name, :string, [required: true]}])
    iex> fields = Sinter.Schema.fields(schema)
    iex> fields[:name].required
    true

# `info`

```elixir
@spec info(t()) :: map()
```

Returns summary information about the schema.

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true]},
    ...>   {:age, :integer, [optional: true]}
    ...> ], title: "User Schema")
    iex> info = Sinter.Schema.info(schema)
    iex> info.field_count
    2
    iex> info.title
    "User Schema"

# `object`

```elixir
@spec object(
  [field_spec()] | t(),
  keyword()
) :: {:object, t()}
```

Builds a nested object schema type from field specifications.

## Examples

    iex> address = Sinter.Schema.object([{:street, :string, [required: true]}])
    iex> schema = Sinter.Schema.define([{:address, address, [required: true]}])

# `option`
*macro* 

Adds an option to the schema being defined.

Used within `use_schema` blocks.

## Examples

    option :title, "User Schema"
    option :strict, true

# `optional_fields`

```elixir
@spec optional_fields(t()) :: [String.t()]
```

Returns a list of optional field names.

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true]},
    ...>   {:age, :integer, [optional: true]}
    ...> ])
    iex> Sinter.Schema.optional_fields(schema)
    [:age]

# `post_validate_fn`

```elixir
@spec post_validate_fn(t()) :: function() | nil
```

Returns the post-validation function if defined.

## Examples

    iex> post_fn = fn data -> {:ok, data} end
    iex> schema = Sinter.Schema.define([], post_validate: post_fn)
    iex> Sinter.Schema.post_validate_fn(schema)
    #Function<...>

# `pre_validate_fn`

```elixir
@spec pre_validate_fn(t()) :: function() | nil
```

Returns the pre-validation function if defined.

## Examples

    iex> pre_fn = fn data -> Map.put(data, "extra", "value") end
    iex> schema = Sinter.Schema.define([], pre_validate: pre_fn)
    iex> Sinter.Schema.pre_validate_fn(schema)
    #Function<...>

# `required_fields`

```elixir
@spec required_fields(t()) :: [String.t()]
```

Returns a list of required field names.

## Examples

    iex> schema = Sinter.Schema.define([
    ...>   {:name, :string, [required: true]},
    ...>   {:age, :integer, [optional: true]}
    ...> ])
    iex> Sinter.Schema.required_fields(schema)
    [:name]

# `strict?`

```elixir
@spec strict?(t()) :: boolean()
```

Returns true if the schema is in strict mode.

## Examples

    iex> schema = Sinter.Schema.define([], strict: true)
    iex> Sinter.Schema.strict?(schema)
    true

# `use_schema`
*macro* 

Compile-time schema definition macro.

This macro provides a DSL for defining schemas at compile time.
It accumulates field and option definitions and creates a schema
using `define/2`.

## Example

    defmodule UserSchema do
      use Sinter.Schema

      use_schema do
        option :title, "User Schema"
        option :strict, true

        field :name, :string, required: true, min_length: 2
        field :age, :integer, optional: true, gt: 0
        field :active, :boolean, optional: true, default: true
      end
    end

    # The module will have a schema/0 function
    UserSchema.schema()

---

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