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 (
whenclause) - ✅ 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
Related Examples
02-advanced- Conditional logic05-fragments- Array constraints07-nickel-generation- Nickel basics08-nickel-roundtrip- Full Nickel workflow
Next Steps
- Start Simple - Run
basic-validators.toml, understand each field - Add Complexity - Try
advanced-validators.tomlwith dependencies - Use Conditionals - Explore
conditional-validators.tomlwith multi-level logic - Go Type-Safe - Learn Nickel contracts with
nickel-validators.ncl - 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.