# Nickel Schema Support
Full support for type-safe form schemas using the Nickel language.
## Overview
TypeDialog integrates Nickel for:
- **Type-safe schemas** - Validate form structure at parse time
- **Contract validation** - Enforce type and content contracts
- **I18n extraction** - Auto-extract translatable strings
- **Template rendering** - Generate forms via Tera templates
- **Roundtrip support** - Idempotent read/write transformations
## Quick Start
### Prerequisites
Install Nickel CLI (optional, for schema validation):
```bash
cargo install nickel-lang
# Or download from: https://github.com/nickel-lang/nickel/releases
```
### Run Examples
```bash
# List all Nickel examples
just test::list | grep nickel
# Run basic Nickel schema
cargo run --example basic_nickel_schema
# Run with roundtrip (read, transform, write)
cargo run --example nickel_roundtrip
# Run with i18n extraction
cargo run --example nickel_i18n_extraction
```
See [examples/07-nickel-generation/](../examples/07-nickel-generation/) for complete examples.
## Core Modules
### contract_parser
Parse and validate Nickel contracts.
```rust
use typedialog_core::nickel::ContractParser;
let parser = ContractParser::new();
let contracts = parser.parse(nickel_schema)?;
```
**Validates:**
- Type correctness
- Field constraints
- Required vs optional fields
- Custom predicates
### i18n_extractor
Automatically extract translatable strings from Nickel schemas.
```rust
use typedialog_core::nickel::I18nExtractor;
let extractor = I18nExtractor::new();
let strings = extractor.extract(nickel_ast)?;
// Output: Map of translatable strings with locations
```
**Extracts:**
- Field labels
- Help text
- Error messages
- Custom metadata
### template_renderer
Render Nickel schemas using Tera templates.
```rust
use typedialog_core::nickel::NickelTemplateContext;
let context = NickelTemplateContext::from_schema(schema)?;
let rendered = template_engine.render("form.j2", &context)?;
```
**Supports:**
- Tera template syntax
- Schema introspection
- Custom filters
- Conditional rendering
### roundtrip
Idempotent read/write for Nickel schemas - complete workflow from `.ncl` → form → `.ncl`.
```rust
use typedialog_core::nickel::RoundtripConfig;
let mut config = RoundtripConfig::with_template(
input_ncl, // Path to existing .ncl file
form_toml, // Path to form definition
output_ncl, // Path for generated .ncl
template, // Optional .ncl.j2 template
);
config.validate = true;
config.verbose = true;
// Execute with any backend (CLI, TUI, Web)
let result = config.execute_with_backend(backend.as_mut()).await?;
```
**Preserves:**
- Comments
- Formatting
- Import structure
- Custom layouts
- Field contracts and validators
**Returns:**
- `RoundtripSummary` with diff viewer
- Validation status
- Change detection (what fields changed)
## Schema Structure
### Basic Form
```nicl
{
title = "User Registration",
fields = {
name = {
type = "text",
label = "Full Name",
required = true,
},
email = {
type = "email",
label = "Email Address",
required = true,
},
}
}
```
### With Contracts
```nicl
{
fields = {
password = {
type = "text",
label = "Password",
contracts = [
{ predicate = |s| string.length s >= 8, error = "Min 8 chars" },
{ predicate = |s| string.contains s "!", error = "Need special char" },
]
}
}
}
```
### With I18n
```nicl
{
i18n = "en-US",
fields = {
name = {
label = "i18n:form.fields.name",
help = "i18n:form.fields.name.help",
}
}
}
```
### With Templates
```nicl
{
template = "form.j2",
context = {
theme = "dark",
layout = "grid",
},
fields = { ... }
}
```
## Roundtrip Workflow
The roundtrip workflow enables interactive reconfiguration of existing Nickel files through forms, preserving structure and generating human-readable diffs.
### Roundtrip Overview
**Workflow:** `config.ncl` (input) → **Form** (edit) → `config.ncl` (output)
1. **Load** existing `.ncl` configuration
2. **Extract** default values using `nickel_path` mappings
3. **Populate** form with current values
4. **Edit** via CLI/TUI/Web interface
5. **Generate** new `.ncl` using template (or preserve contracts)
6. **Validate** with `nickel typecheck`
7. **Show** summary with diff viewer
### The `nickel_path` Attribute
**Critical:** Fields MUST have `nickel_path` to participate in roundtrip.
Maps form field names to nested Nickel structure:
```toml
[[elements]]
name = "parallel_jobs"
type = "text"
prompt = "Parallel Jobs"
default = "4"
nickel_path = ["ci", "github_actions", "parallel_jobs"]
```
Extracts from:
```nickel
{
ci = {
github_actions = {
parallel_jobs = 4
}
}
}
```
**Rules:**
- Array of strings representing path
- Each element is a nested key
- Top-level: `["field_name"]`
- Nested: `["parent", "child", "field"]`
- Arrays: Use RepeatingGroup with `nickel_path = ["array_name"]`
### Roundtrip with CLI Backend
```bash
typedialog nickel-roundtrip \
--input config.ncl \
--form ci-form.toml \
--output config.ncl \
--ncl-template config.ncl.j2 \
--verbose
```
**Output:**
```text
╔════════════════════════════════════════════════════════════╗
║ ✅ Configuration Saved Successfully! ║
╠════════════════════════════════════════════════════════════╣
║ 📄 File: config.ncl ║
║ ✓ Validation: ✓ PASSED ║
║ 📊 Fields: 5/27 changed, 22 unchanged ║
╠════════════════════════════════════════════════════════════╣
║ 📋 What Changed: ║
║ ├─ parallel_jobs: 4 → 8 ║
║ ├─ timeout_minutes: 60 → 120 ║
║ ├─ enable_clippy: true → false ║
║ ├─ rust_version: stable → nightly ║
║ ├─ cache_enabled: false → true ║
╠════════════════════════════════════════════════════════════╣
║ 💡 Next Steps: ║
║ • Review: cat config.ncl ║
║ • Apply CI tools: ./setup-ci.sh ║
║ • Re-configure: ./ci-configure.sh ║
╚════════════════════════════════════════════════════════════╝
```
### Roundtrip with TUI Backend
```bash
typedialog-tui nickel-roundtrip \
--input config.ncl \
--form ci-form.toml \
--output config.ncl \
--ncl-template config.ncl.j2
```
Interactive TUI form + terminal summary (same as CLI).
### Roundtrip with Web Backend
```bash
typedialog-web nickel-roundtrip \
--input config.ncl \
--form ci-form.toml \
--output config.ncl \
--ncl-template config.ncl.j2 \
--verbose
```
**Features:**
- Opens browser to `http://localhost:8080`
- All fields pre-populated with current values
- Real-time validation
- **Summary page** on submit:
- Visual diff viewer (old → new)
- Field statistics
- Download button for generated config
- Auto-close after 30 seconds
**Terminal output:**
```text
Starting interactive form on http://localhost:8080
Complete the form and submit to continue...
Web UI available at http://localhost:8080
[web] Complete form initialized with 63 default values
╔════════════════════════════════════════════════════════════╗
║ ✅ Configuration Saved Successfully! ║
╠════════════════════════════════════════════════════════════╣
...
```
### Form Requirements
For roundtrip to work, your form MUST:
1. **Include `nickel_path` on ALL fields:**
```toml
[[elements]]
name = "project_name"
nickel_path = ["project", "name"] # ✅ Required
```
2. **Use correct path syntax:**
```toml
# Flat field
nickel_path = ["enable_ci"]
# Nested field
nickel_path = ["ci", "github_actions", "timeout"]
# Array field (RepeatingGroup)
nickel_path = ["ci", "tools", "linters"]
```
3. **Match template variables:**
```jinja2
# Template: config.ncl.j2
{
project = {
name = "{{ project_name }}", # ← Form field "project_name"
},
ci = {
enabled = {{ enable_ci }}, # ← Form field "enable_ci"
}
}
```
### Template Support
Use Tera templates (`.ncl.j2`) for complex output structures:
**Template:** `config.ncl.j2`
```jinja2
# Generated by TypeDialog
let imports = import "ci/lib.ncl"
let validators = import "ci/validators.ncl"
{
project = {
name = "{{ project_name }}",
version = "{{ project_version }}",
},
ci = {
github_actions = {
enabled = {{ enable_github_actions }},
parallel_jobs = {{ parallel_jobs }},
timeout_minutes = {{ timeout_minutes }},
{% if enable_cache %}
cache = {
enabled = true,
paths = {{ cache_paths | json }},
},
{% endif %}
},
tools = {
{% for tool in ci_tools %}
{{ tool.name }} = {
enabled = {{ tool.enabled }},
version = "{{ tool.version }}",
},
{% endfor %}
},
}
} | validators.CiConfig
```
**Form values are injected automatically.**
### Complete Example
See `examples/08-nickel-roundtrip/` for a full CI configuration workflow:
```bash
cd examples/08-nickel-roundtrip/
# Initial setup
./01-generate-initial-config.sh
# Edit with CLI
./02-roundtrip-cli.sh
# Edit with TUI
./03-roundtrip-tui.sh
# Edit with Web
./04-roundtrip-web.sh
```
### Validation
Roundtrip automatically validates output:
```bash
typedialog nickel-roundtrip \
--input config.ncl \
--form form.toml \
--output config.ncl \
--ncl-template template.ncl.j2
# Runs: nickel typecheck config.ncl
```
**Disable validation:**
```bash
typedialog nickel-roundtrip ... --no-validate
```
### Summary Output
All backends generate summaries showing:
- **Total fields:** How many fields in the form
- **Changed fields:** Fields with different values
- **Unchanged fields:** Fields that kept the same value
- **Validation status:** Pass/fail from `nickel typecheck`
- **Change list:** Detailed old → new for each change
**Verbose mode** shows ALL changes (not just first 10):
```bash
typedialog nickel-roundtrip ... --verbose
```
### Roundtrip Troubleshooting
**Issue: "No default values loaded"**
✓ Check all fields have `nickel_path`:
```bash
grep -r "nickel_path" form.toml
```
**Issue: "Field not found in output"**
✓ Verify template includes the field:
```bash
grep "{{ field_name }}" template.ncl.j2
```
**Issue: "Validation failed"**
✓ Check Nickel syntax manually:
```bash
nickel typecheck config.ncl
```
**Issue: "Values not showing in web form"**
✓ Ensure `nickel export` works on input:
```bash
nickel export config.ncl
```
## Building with Nickel
Build project with Nickel support:
```bash
# Build (includes Nickel feature by default)
just build::default
# Test Nickel modules
just test::core
# Generate docs
just dev::docs
```
## Examples
Complete examples in [examples/07-nickel-generation/](../examples/07-nickel-generation/):
### Schemas
- **simple.ncl** - Basic form schema
- **complex.ncl** - Advanced with sections and groups
- **conditional.ncl** - Conditional field visibility
- **i18n.ncl** - Internationalization example
### Templates
- **config.ncl.j2** - Configuration form template
- **deployment.ncl.j2** - Deployment spec template
- **service_spec.ncl.j2** - Service specification template
### Usage
```bash
cd examples/07-nickel-generation/
# Validate schema
nickel export schemas/simple.ncl
# Parse and render
cargo run --manifest-path ../../Cargo.toml \
--example nickel_form_generation < schemas/simple.ncl
```
## Integration Patterns
### CLI Backend
```bash
typedialog --format nickel form.ncl
```
### TUI Backend
```bash
typedialog-tui form.ncl
```
### Web Backend
```bash
typedialog-web --config form.ncl
```
## Advanced Topics
### Custom Contracts
Define custom validation predicates:
```nicl
{
fields = {
age = {
type = "number",
contracts = [
{
predicate = |n| n >= 18,
error = "Must be 18+"
}
]
}
}
}
```
### Reusable Components
```nicl
let address_fields = {
street = { type = "text", label = "Street" },
city = { type = "text", label = "City" },
zip = { type = "text", label = "ZIP" },
};
{
fields = address_fields & {
country = { type = "select", label = "Country" }
}
}
```
### Schema Inheritance
Extend base schemas:
```nicl
let base = import "schemas/base.ncl";
base & {
fields = base.fields & {
custom_field = { ... }
}
}
```
### I18n Management
Extract and manage translations:
```bash
# Extract all translatable strings
cargo run --example nickel_i18n_extraction < form.ncl > strings.json
# Update translations
# (use with translation management system)
```
## Performance Considerations
### Parsing
- Schemas parsed once at startup
- AST cached in memory
- Validation is O(n) in field count
### Rendering
- Templates compiled once
- Context generation O(n)
- Output streamed for large forms
### Roundtrip
- Preserves source formatting
- Minimal allocations
- In-place transformations
## Troubleshooting
### Schema validation fails
Check syntax with Nickel CLI:
```bash
nickel export form.ncl
```
### Contract errors
Verify predicates are boolean functions:
```nicl
# ❌ Wrong
contracts = [
{ predicate = |s| string.length s, error = "..." }
]
# ✓ Correct
contracts = [
{ predicate = |s| string.length s >= 8, error = "..." }
]
```
### Template rendering fails
Ensure Tera template exists and context matches:
```bash
# Verify template
ls -la templates/form.j2
# Check context keys
cargo run --example nickel_template_context < form.ncl
```
### I18n strings not extracted
Verify string format:
```nicl
# ❌ Plain string
label = "Name"
# ✓ I18n reference
label = "i18n:form.name"
```
## Best Practices
1. **Schema Organization**
- Keep schemas modular
- Use `import` for shared components
- Version schemas in git
2. **Contracts**
- Keep predicates simple
- Provide clear error messages
- Test edge cases
3. **I18n**
- Use consistent key naming
- Extract before translations
- Validate all strings are referenced
4. **Templates**
- Keep templates simple
- Use filters for formatting
- Test with multiple data types
5. **Roundtrip**
- Always test read/write cycles
- Preserve comments with care
- Validate output structure
## API Reference
### ContractParser
```rust
pub struct ContractParser;
impl ContractParser {
pub fn new() -> Self;
pub fn parse(&self, source: &str) -> Result;
}
pub struct ParsedContracts {
pub fields: Map,
pub global: Vec,
}
```
### I18nExtractor
```rust
pub struct I18nExtractor;
impl I18nExtractor {
pub fn new() -> Self;
pub fn extract(&self, ast: &NickelAst) -> Result;
}
```
### NickelTemplateContext
```rust
pub struct NickelTemplateContext {
pub schema: NickelSchemaIR,
pub fields: Map,
}
impl NickelTemplateContext {
pub fn from_schema(schema: NickelSchemaIR) -> Result;
}
```
### RoundtripConfig
```rust
pub struct RoundtripConfig {
pub preserve_comments: bool,
pub preserve_formatting: bool,
pub indent: String,
}
impl Default for RoundtripConfig {
fn default() -> Self {
Self {
preserve_comments: true,
preserve_formatting: true,
indent: " ".to_string(),
}
}
}
```
## Next Steps
- [development.md](development.md) - Build and test
- [configuration.md](configuration.md) - Backend configuration
- [Examples](../examples/07-nickel-generation/) - Complete examples
- [Nickel Language](https://nickel-lang.org) - Language reference
---
**Latest Update**: December 2024
**Status**: Stable