TypeDialog/examples/14-validators-and-contracts
Jesús Pérez 8149523e5b
Some checks failed
CI / Lint (bash) (push) Has been cancelled
CI / Lint (markdown) (push) Has been cancelled
CI / Lint (nickel) (push) Has been cancelled
CI / Lint (nushell) (push) Has been cancelled
CI / Lint (rust) (push) Has been cancelled
CI / Benchmark (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / License Compliance (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
chore: new examples
2026-01-12 03:31:00 +00:00
..
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00

Validators and Contracts Example

Comprehensive guide to validation, constraints, and type-safe contracts in TypeDialog.

Learn how to ensure data quality and enforce business rules with declarative validators that work across all backends (CLI, TUI, Web).

Overview

TypeDialog provides multiple levels of validation:

Level Mechanism Examples
Field-Level Built-in validators required, min/max, date ranges
Array-Level Constraints min_items, max_items, unique
Selection-Level Multiselect limits min_selected, max_selected
Conditional Smart visibility when clause for dependencies
Type-Safe Nickel contracts Compile-time validation

Examples Included

1. Basic Validators (basic-validators.toml)

Foundational validation patterns:

cargo run --example basic_validators

# Or with specific backend
cargo run -p typedialog-tui --example basic_validators
cargo run -p typedialog-web -- --config examples/14-validators-and-contracts/basic-validators.toml

Demonstrates

  • required - Fields that cannot be empty
  • min/max - Numeric range validation
  • min_selected/max_selected - Multiselect constraints
  • min_items/max_items - Array size limits
  • min_date/max_date - Date range validation
  • Constraints as configuration (single source of truth)

Key Features:

# Required field
[[elements]]
name = "username"
required = true

# Multiselect with limits
[[elements]]
name = "interests"
min_selected = 2
max_selected = 4

# Date range validation
[[elements]]
name = "birth_date"
min_date = "1950-01-01"
max_date = "2006-12-31"

# Constraints (DRY configuration)
[constraints]
username_min_length = 3
username_max_length = 32

2. Advanced Validators (advanced-validators.toml)

Sophisticated validation patterns and field dependencies:

cargo run --example advanced_validators

Demonstrates

  • Smart defaults with environment variables ({{ env.USER }})
  • Field dependencies (options_from)
  • Array uniqueness (unique = true)
  • Multi-field validation rules
  • Regex patterns for validation
  • Constraint reuse for consistency

Key Features:

# Smart defaults from environment
[[elements]]
name = "current_user"
default = "{{ env.USER }}"

# Field dependencies - language options depend on platform
[[elements]]
name = "platform"
type = "select"
options = [...]

[[elements]]
name = "language"
options_from = "platform"  # Filter based on platform selection
options = [...]

# Array with uniqueness constraint
[[elements]]
name = "team_members"
type = "repeating-group"
min_items = 1
max_items = 10
fragment = "fragments/team-member.toml"

Constraint Configuration:

[constraints]
min_connections = 1
max_connections = 10000

[constraints.regex_patterns]
email = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
hostname = "^[a-z0-9]..."
url = "^https?://..."
version = "^[0-9]+\\.[0-9]+\\.[0-9]+"

3. Conditional Validators (conditional-validators.toml)

Master field visibility and conditional requirements using the when clause:

cargo run --example conditional_validators

Demonstrates

  • Conditional field visibility (when clause)
  • Conditional requirements (show + require)
  • Cascading conditions (multiple levels)
  • Smart defaults based on conditions

Key Features:

# Conditional visibility
[[elements]]
name = "account_type"
type = "select"
options = [
  { value = "personal", label = "Personal" },
  { value = "business", label = "Business" },
]

# Only show if personal or education
[[elements]]
name = "first_name"
when = "account_type == personal || account_type == education"
required = true

# Only show if business
[[elements]]
name = "company_name"
when = "account_type == business"
required = true

Multi-Level Conditions:

# Level 1: Enable feature
[[elements]]
name = "enable_2fa"
type = "confirm"

# Level 2: Choose method (if enabled)
[[elements]]
name = "2fa_method"
when = "enable_2fa == true"
required = true

# Level 3: Provide details (if specific method)
[[elements]]
name = "phone_for_2fa"
when = "enable_2fa == true && 2fa_method == sms"
required = true

4. Type-Safe Contracts (nickel-validators.ncl)

Compile-time validation using Nickel contracts:

# View the schema
nickel eval examples/14-validators-and-contracts/nickel-validators.ncl

# Query the inputs section
nickel query examples/14-validators-and-contracts/nickel-validators.ncl inputs

# Convert to TOML form
nickel query examples/14-validators-and-contracts/nickel-validators.ncl inputs | \
  typedialog parse-nickel > nickel-output.toml

Demonstrates

  • Custom validation contracts
  • Type constraints (std.string.NonEmpty, std.number.Between)
  • Email and password validation
  • Enum-like validation
  • Optional fields with contracts
  • Nested object validation

Key Features:

# Custom email validator
let EmailContract = std.contract.from_predicate (
  fun x =>
    if std.string.is_match "^[^@]+@[^@]+\\.[^@]+$" x then true
    else std.contract.blame_with ("Invalid email format")
)
in

# Usage in schema
{
  email | EmailContract = "",

  # Type-safe enum
  role | std.contract.EnumContract ["user", "admin"] = "user",

  # Number range
  port | std.number.Between 1 65535 = 8080,

  # Non-empty string list
  tags | std.contract.list std.string.NonEmpty = [],

  # Optional field
  bio | (std.string.MaxLength 500 | std.contract.optional) = null,
}

Validation Rules Reference

Field-Level Validators

Validator Type Example Use Case
required Boolean required = true Field cannot be empty
default String default = "value" Pre-fill field with value
placeholder String placeholder = "hint" Show hint text
min_date Date min_date = "2020-01-01" Minimum date
max_date Date max_date = "2025-12-31" Maximum date
when Expression when = "field == value" Show if condition true

Selection Validators

Validator Type Example Use Case
min_selected Number min_selected = 2 Minimum selected items
max_selected Number max_selected = 5 Maximum selected items
options_from Field options_from = "platform" Filter options

Array Validators

Validator Type Example Use Case
min_items Number min_items = 1 Minimum array size
max_items Number max_items = 10 Maximum array size
unique Boolean unique = true No duplicate items

Constraints Section

Store validation limits in a [constraints] section for DRY principle:

[constraints]
username_min_length = 3
username_max_length = 32

password_min_length = 8
password_requires_uppercase = true
password_requires_digits = true

[constraints.regex_patterns]
email = "^[^@]+@[^@]+\\.[^@]+$"
url = "^https?://..."

Backend Compatibility

All validators work across all backends:

Feature CLI TUI Web
required
min/max
min_items/max_items
when (conditional)
min_selected/max_selected
Date range validation
Nickel contracts

Common Patterns

1. Account Type-Based Validation

Show different fields based on account type:

[[elements]]
name = "account_type"
type = "select"
options = [
  { value = "personal", label = "Personal" },
  { value = "business", label = "Business" },
]

# Only for business accounts
[[elements]]
name = "company_name"
when = "account_type == business"
required = true

2. Smart Defaults from Environment

Auto-populate from system:

[[elements]]
name = "current_user"
default = "{{ env.USER }}"

[[elements]]
name = "home_dir"
default = "{{ env.HOME }}"

3. Multi-Level Conditional Validation

Show fields based on multiple conditions:

# Level 1: Enable feature
[[elements]]
name = "enable_api"
type = "confirm"

# Level 2: Choose type (if enabled)
[[elements]]
name = "api_type"
when = "enable_api == true"
required = true
options = [...]

# Level 3: Provide details (if OAuth2)
[[elements]]
name = "oauth_client_id"
when = "enable_api == true && api_type == oauth2"
required = true

4. Array Constraints with Uniqueness

Ensure unique items in arrays:

[[elements]]
name = "team_members"
type = "repeating-group"
min_items = 1
max_items = 10
fragment = "fragments/unique-member.toml"

# In unique-member.toml:
[[elements]]
name = "email"
required = true
unique = true  # No duplicate emails

5. Range Validation

Constrain numeric values:

[[elements]]
name = "age"
type = "text"
placeholder = "18-100"  # Hint

[[elements]]
name = "port"
placeholder = "1024-65535"

[constraints]
age_min = 18
age_max = 100
port_min = 1024
port_max = 65535

Testing Validators

CLI Backend

# Run with interactive prompts
cargo run --example conditional_validators

# Try entering invalid values:
# - Empty required fields
# - Out of range values
# - Wrong date ranges

TUI Backend

# Rich terminal UI shows validation errors in real-time
cargo run -p typedialog-tui --example conditional_validators

# Keyboard navigation shows which fields are required
# Arrows/Tab show conditional fields appear/disappear

Web Backend

# Start web server
cargo run -p typedialog-web -- \
  --config examples/14-validators-and-contracts/basic-validators.toml

# Open browser to http://localhost:3000
# Test validation - HTML5 constraints + server-side

Performance Considerations

Constraints vs. Hardcoded Values

Good (DRY)

[constraints]
max_connections = 10000

[[elements]]
name = "connections"
placeholder = "1-${constraint.max_connections}"

Avoid (Duplication)

[[elements]]
name = "connections"
placeholder = "1-10000"

# If you change the limit, you need to update multiple places

When to Use Nickel Contracts

Use Nickel when

  • You need compile-time validation
  • You want type-safety
  • Custom business logic is complex
  • You're generating schemas from Nickel

Use TOML when

  • Simple validation is sufficient
  • You want quick iteration
  • You're manually writing forms

Troubleshooting

"Field appears/disappears unexpectedly"

Check your when clause syntax:

# ✅ CORRECT
when = "account_type == business"
when = "enable_2fa == true && api_type == oauth2"

# ❌ INCORRECT
when = "account_type = business"  # Single = is assignment
when = "account_type eq business"  # Wrong syntax

"Validation not working on Web backend"

Ensure field is in the form definition:

# Make sure required is set
[[elements]]
name = "email"
required = true  # This is checked by web backend

# Check min/max constraints
min_selected = 2
max_selected = 5

"Nickel contract errors"

Verify Nickel syntax:

# Check for syntax errors
nickel eval nickel-validators.ncl

# If it fails, review contract definitions
# Ensure all contracts use std.contract.from_predicate


Next Steps

  1. Start Simple - Run basic-validators.toml, understand each field
  2. Add Complexity - Try advanced-validators.toml with dependencies
  3. Use Conditionals - Explore conditional-validators.toml with multi-level logic
  4. Go Type-Safe - Learn Nickel contracts with nickel-validators.ncl
  5. Combine Approaches - Mix TOML and Nickel in your own forms

Questions? See ../../docs/field-types.md for field type reference.

Want advanced patterns? Check 11-prov-gen for enterprise-scale validation.