480 lines
8.4 KiB
Markdown
480 lines
8.4 KiB
Markdown
|
|
<div align="center">
|
||
|
|
<img src="../imgs/typedialog_logo_h_s.svg" alt="TypeDialog Logo" width="600" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
# 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.
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use typedialog_core::nickel::RoundtripConfig;
|
||
|
|
|
||
|
|
let config = RoundtripConfig::default();
|
||
|
|
let schema = parser.parse_file("form.ncl")?;
|
||
|
|
let modified = transform(schema)?;
|
||
|
|
serializer.write_file("form.ncl", &modified, &config)?;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Preserves:**
|
||
|
|
- Comments
|
||
|
|
- Formatting
|
||
|
|
- Import structure
|
||
|
|
- Custom layouts
|
||
|
|
|
||
|
|
## 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 = { ... }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 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<ParsedContracts>;
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct ParsedContracts {
|
||
|
|
pub fields: Map<String, FieldContract>,
|
||
|
|
pub global: Vec<Contract>,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### I18nExtractor
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct I18nExtractor;
|
||
|
|
|
||
|
|
impl I18nExtractor {
|
||
|
|
pub fn new() -> Self;
|
||
|
|
pub fn extract(&self, ast: &NickelAst) -> Result<I18nMap>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### NickelTemplateContext
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct NickelTemplateContext {
|
||
|
|
pub schema: NickelSchemaIR,
|
||
|
|
pub fields: Map<String, FieldContext>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl NickelTemplateContext {
|
||
|
|
pub fn from_schema(schema: NickelSchemaIR) -> Result<Self>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 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
|