Errors, Reports, and Operations

Copy Markdown View Source

This guide covers error handling contracts, validation reports, and practical operating patterns.

Error Types

Exdantic.Error

Every validation failure is represented as:

  • path ([atom() | String.t() | integer()])

  • code (atom())
  • message (String.t())

Create errors:

error = Exdantic.Error.new([:user, :email], :format, "invalid email")

Format errors:

Exdantic.Error.format(error)
# "user.email: invalid email"

Exdantic.ValidationError

validate!/1 style APIs raise Exdantic.ValidationError containing the collected errors list.

Common Error Codes

Codes vary by operation, but common categories include:

  • :required
  • :type
  • :additional_properties
  • constraint codes like :min_length, :gteq, :choices
  • :model_validation
  • :computed_field
  • :computed_field_type
  • env-related settings codes like :env_cast, :env_json, :env_key_conflict

Validation Entry Points

Compile-time schema:

  • MySchema.validate/1
  • MySchema.validate!/1
  • MySchema.validate_enhanced/2

Runtime:

Universal:

Type-only:

Reporting APIs

Quick report

report = Exdantic.EnhancedValidator.validation_report(target, input)

Returns a lightweight summary: validation result, generated JSON Schema, target and input analysis, timing metrics, and config summary.

Comprehensive report

report =
  Exdantic.EnhancedValidator.comprehensive_validation_report(
    target,
    input,
    test_providers: [:openai, :anthropic],
    include_performance_analysis: true,
    include_dspy_analysis: true
  )

Adds provider compatibility, complexity metrics, and recommendations.

Schema Introspection in Production

Compile-time schema helpers:

  • __schema_info__/0
  • __enhanced_schema_info__/0

Useful for diagnostics and contract inventory at runtime.

Operational Patterns

1. Strict contracts at boundaries

At API or queue boundaries, prefer strict mode to catch unknown keys early.

2. Coercion strategy by trust level

  • trusted/internal paths: can use :safe coercion
  • external/untrusted paths: prefer strict, explicit typing

3. Separate input/output schemas when needed

If computed fields should not be accepted from input, generate output schema normally but derive input schemas with computed fields removed:

output_schema = Exdantic.JsonSchema.from_schema(MySchema)
input_schema = Exdantic.JsonSchema.remove_computed_fields(output_schema)

4. Validate provider compatibility in CI

Use enhanced resolver compatibility checks before shipping schema changes:

:ok = Exdantic.JsonSchema.EnhancedResolver.validate_schema_compatibility(MySchema)

5. Keep examples executable

Run example scripts as contract tests for documentation and onboarding quality.

Testing Suggestions

  • Unit-test custom validators and computed field functions directly
  • Add integration tests for full schema pipelines (validate + json_schema)
  • Include strict-mode tests for unknown key handling
  • For settings schemas, test env decoding and nested exploded env paths
  • For LLM contracts, test provider-shaped schema snapshots

Documentation and Maintenance Workflow

When adding a new schema capability:

  1. Add/adjust tests in test/
  2. Update affected guide(s)
  3. Update README entry points if API visibility changes
  4. Validate docs build (mix docs) and examples as needed

Final Note

Exdantic's strength is consistency across compile-time, runtime, and type-only validation. Treat schemas as first-class contracts, and use the reporting and resolver tools to keep those contracts stable under change.