TypeDialog/docs/nickel.md
2025-12-24 03:11:32 +00:00

8.4 KiB

TypeDialog Logo

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):

cargo install nickel-lang
# Or download from: https://github.com/nickel-lang/nickel/releases

Run Examples

# 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/ for complete examples.

Core Modules

contract_parser

Parse and validate Nickel contracts.

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.

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.

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.

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

{
  title = "User Registration",
  fields = {
    name = {
      type = "text",
      label = "Full Name",
      required = true,
    },
    email = {
      type = "email",
      label = "Email Address",
      required = true,
    },
  }
}

With Contracts

{
  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

{
  i18n = "en-US",
  fields = {
    name = {
      label = "i18n:form.fields.name",
      help = "i18n:form.fields.name.help",
    }
  }
}

With Templates

{
  template = "form.j2",
  context = {
    theme = "dark",
    layout = "grid",
  },
  fields = { ... }
}

Building with Nickel

Build project with Nickel support:

# 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/:

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

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

typedialog --format nickel form.ncl

TUI Backend

typedialog-tui form.ncl

Web Backend

typedialog-web --config form.ncl

Advanced Topics

Custom Contracts

Define custom validation predicates:

{
  fields = {
    age = {
      type = "number",
      contracts = [
        {
          predicate = |n| n >= 18,
          error = "Must be 18+"
        }
      ]
    }
  }
}

Reusable Components

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:

let base = import "schemas/base.ncl";

base & {
  fields = base.fields & {
    custom_field = { ... }
  }
}

I18n Management

Extract and manage translations:

# 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:

nickel export form.ncl

Contract errors

Verify predicates are boolean functions:

# ❌ 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:

# 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:

# ❌ 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

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

pub struct I18nExtractor;

impl I18nExtractor {
    pub fn new() -> Self;
    pub fn extract(&self, ast: &NickelAst) -> Result<I18nMap>;
}

NickelTemplateContext

pub struct NickelTemplateContext {
    pub schema: NickelSchemaIR,
    pub fields: Map<String, FieldContext>,
}

impl NickelTemplateContext {
    pub fn from_schema(schema: NickelSchemaIR) -> Result<Self>;
}

RoundtripConfig

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


Latest Update: December 2024 Status: Stable