guides/01_overview_and_quickstart.md

# Overview and Quickstart

This guide introduces Exdantic's architecture and gives a practical path from first schema to advanced workflows.

## What Exdantic Solves

Exdantic provides a single data-contract stack in Elixir for:

- Schema definition with field constraints
- Structured validation with typed error paths
- Cross-field logic and transformations
- Derived fields
- Runtime schema generation
- JSON Schema export and optimization
- Environment-driven settings loading

## Core Building Blocks

Exdantic has three complementary layers:

1. Compile-time schema modules
- `use Exdantic`
- Best for stable contracts and shared domain models
- Supports model validators, computed fields, and optional struct output

2. Runtime schemas
- `Exdantic.Runtime.create_schema/2`
- Best for dynamic field sets discovered at runtime
- Supports both basic (`DynamicSchema`) and enhanced (`EnhancedSchema`) pipelines

3. Type-centric validation
- `Exdantic.TypeAdapter`
- Best when you need to validate values without defining full schema modules

## First Schema

```elixir
defmodule AccountSchema do
  use Exdantic, define_struct: true

  schema "Account payload" do
    field :name, :string do
      required()
      min_length(2)
      max_length(80)
    end

    field :email, :string do
      required()
      format(~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
    end

    field :active, :boolean do
      default(true)
    end

    config do
      title("Account")
      strict(true)
    end
  end
end
```

Validate data:

```elixir
{:ok, account} = AccountSchema.validate(%{
  name: "Jane",
  email: "jane@example.com"
})

# account is %AccountSchema{} because define_struct: true
```

Raise on failure:

```elixir
account = AccountSchema.validate!(%{name: "Jane", email: "jane@example.com"})
```

Serialize struct back to map:

```elixir
{:ok, payload} = AccountSchema.dump(account)
```

## Error Model

Validation errors use `Exdantic.Error`:

- `path`: nested location of the failure
- `code`: machine-friendly error code
- `message`: readable description

For `validate!/1`, Exdantic raises `Exdantic.ValidationError` containing all errors.

## Runtime Quickstart

```elixir
fields = [
  {:answer, :string, [required: true]},
  {:confidence, :float, [required: true, gteq: 0.0, lteq: 1.0]}
]

schema = Exdantic.Runtime.create_schema(fields, title: "LLM Result", strict: true)

{:ok, result} = Exdantic.Runtime.validate(%{answer: "42", confidence: 0.95}, schema)
```

## TypeAdapter Quickstart

```elixir
{:ok, 42} = Exdantic.TypeAdapter.validate(:integer, "42", coerce: true)
{:ok, ["a", "b"]} = Exdantic.TypeAdapter.validate({:array, :string}, ["a", "b"])
```

## JSON Schema Quickstart

Compile-time schema:

```elixir
schema = AccountSchema.json_schema()
```

Type spec:

```elixir
schema = Exdantic.TypeAdapter.json_schema({:array, :integer})
```

Resolve references or enforce provider requirements:

```elixir
resolved = Exdantic.JsonSchema.Resolver.resolve_references(schema)
openai = Exdantic.JsonSchema.Resolver.enforce_structured_output(schema, provider: :openai)
```

## Choosing the Right API

Use compile-time schema modules when:

- Contract is stable and shared across codebase
- You need full DSL expressiveness
- You want struct output and introspection

Use runtime schemas when:

- Fields are discovered at runtime
- You need programmatic schema assembly
- You still want map-based validation + JSON Schema generation

Use `TypeAdapter` when:

- You validate isolated values or fragments
- You want minimal surface area and low ceremony

## Next Guides

- `guides/02_schema_dsl_and_types.md`: field DSL, constraints, and type system
- `guides/03_structs_model_validators_computed_fields.md`: full validation pipeline behavior
- `guides/04_runtime_schemas.md`: dynamic schema creation and enhanced runtime pipeline