Sinter (Sinter v0.0.1)
View SourceUnified 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:
- Validation, Not Transformation - Sinter validates data structure and constraints but does not perform business logic transformations
- Runtime-First Design - Schemas are data structures that can be created and modified at runtime, perfect for dynamic frameworks
- Unified Core Engine - All validation flows through a single, well-tested pipeline for consistency and reliability
Summary
Functions
Creates a batch validator function for multiple type specifications.
Creates a schema by analyzing examples to infer field types and requirements.
Merges multiple schemas into a single schema.
Validates multiple values efficiently against their respective type specifications.
Validates a single value against a type specification.
Validates a single named value against a type specification.
Creates a reusable validation function for a specific type and constraints.
Types
@type schema() :: Sinter.Schema.t()
@type validation_opts() :: Sinter.Validator.validation_opts()
@type validation_result() :: {:ok, term()} | {:error, [Sinter.Error.t()]}
Functions
@spec batch_validator_for([{atom(), Sinter.Types.type_spec()}], validation_opts()) :: (map() -> {: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}
tuplesbase_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}}
@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 dataopts
- 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
- Field Discovery: Find all unique field names across examples
- Type Inference: Determine the most common type for each field
- Requirement Analysis: Fields present in >= min_occurrence_ratio are required
- Constraint Inference: Infer basic constraints from value patterns
@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 mergeopts
- Optional schema configuration overrides
Merge Rules
- Fields: All fields from all schemas are included
- Conflicts: Later schemas override earlier ones for conflicting field definitions
- 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]
@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()) => [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}
tuplesopts
- Validation options
Examples
iex> Sinter.validate_many([
...> {:string, "hello"},
...> {:integer, 42},
...> {:email, :string, "test@example.com", [format: ~r/@/]}
...> ])
{:ok, ["hello", 42, "test@example.com"]}
@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 againstvalue
- The value to validateopts
- 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
@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 againstvalue
- The value to validateopts
- 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}
@spec validator_for(Sinter.Types.type_spec(), validation_opts()) :: (term() -> 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 specificationbase_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