Sinter.Schema (Sinter v0.0.1)

View Source

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

Summary

Functions

Returns the schema configuration.

Extracts constraint information from schema fields.

Defines a schema from field specifications.

Defines a field in the schema.

Extracts field types from a schema for analysis and introspection.

Returns the field definitions map.

Returns summary information about the schema.

Adds an option to the schema being defined.

Returns a list of optional field names.

Returns the post-validation function if defined.

Returns a list of required field names.

Returns true if the schema is in strict mode.

Compile-time schema definition macro.

Types

config()

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

field_definition()

@type field_definition() :: %{
  name: atom(),
  type: Sinter.Types.type_spec(),
  required: boolean(),
  constraints: keyword(),
  description: String.t() | nil,
  example: term() | nil,
  default: term() | nil
}

field_spec()

@type field_spec() :: {atom(), Sinter.Types.type_spec(), keyword()}

metadata()

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

t()

@type t() :: %Sinter.Schema{
  config: config(),
  definition: map(),
  fields: %{required(atom()) => field_definition()},
  metadata: metadata()
}

Functions

config(schema)

@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(schema)

@spec constraints(t()) :: %{required(atom()) => 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(field_specs, opts \\ [])

@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(name, type_spec, opts \\ [])

(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_types(schema)

@spec field_types(t()) :: %{required(atom()) => 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(schema)

@spec fields(t()) :: %{required(atom()) => 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(schema)

@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"

option(key, value)

(macro)

Adds an option to the schema being defined.

Used within use_schema blocks.

Examples

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

optional_fields(schema)

@spec optional_fields(t()) :: [atom()]

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(schema)

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

required_fields(schema)

@spec required_fields(t()) :: [atom()]

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?(schema)

@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(list)

(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()