3 stable releases
| new 2.0.0 | Feb 12, 2026 |
|---|---|
| 1.2.0 | Jan 21, 2026 |
| 1.0.0 | Jan 9, 2026 |
#2904 in Parser implementations
Used in hedl-lsp
2.5MB
46K
SLoC
hedl
The unified HEDL API -parse, validate, convert, and canonicalize with a single, ergonomic interface.
Production systems need simple APIs. Importing a dozen crates for basic operations creates cognitive overhead. Feature flags should work intuitively. Errors need rich context without boilerplate. The most common operations should be frictionless.
hedl is the facade crate providing the complete HEDL experience through 7 core functions and comprehensive error handling utilities. Zero default dependencies -only pay for features you enable. Full re-exports from 13 specialized crates beneath a unified API. Designed for developers who want results, not dependency management.
What's Implemented
Unified API with zero-overhead ergonomics:
- 7 Core Functions: parse, parse_lenient, canonicalize, to_json, from_json, lint, validate
- 8 Feature Flags: serde, yaml, xml, csv, parquet, neo4j, toon, all-formats
- Zero Default Dependencies: Core parsing only -enable formats as needed
- Error Context Extensions: HedlResultExt trait with .context() and .with_context()
- Full Re-Exports: 10 internal crates exposed through unified namespace
- Type Aliases: Convenient Result = std::result::Result<T, HedlError>
- 299 Integration Tests: Comprehensive validation including error context propagation
- Minimal Overhead: < 1% compared to using crates directly
- Feature Composition: Mix and match formats without conflicts
- Ergonomic Imports: use hedl::*; gives you everything you need
Installation
Minimal (Core Only)
Parse, validate, and work with HEDL documents:
[dependencies]
hedl = "2.0"
What you get: Parsing, canonicalization, linting, validation What you don't get: Format conversion (JSON/YAML/XML/etc.)
With Specific Formats
Enable only the formats you need:
[dependencies]
hedl = { version = "2.0", features = ["yaml", "xml"] }
All Features
Get everything (recommended for applications):
[dependencies]
hedl = { version = "2.0", features = ["all-formats"] }
Includes: JSON, YAML, XML, CSV, Parquet, Neo4j, TOON, Serde
Core API
parse(input: &str) -> Result
Parse HEDL document with strict validation:
use hedl::parse;
let hedl = r#"
%V:2.0
%NULL:~
%QUOTE:"
%S:User:[id, name, age]
---
users: @User
|alice, Alice Smith, 30
|bob, Bob Jones, 25
"#;
let doc = parse(hedl)?;
println!("Version: {}.{}", doc.version.0, doc.version.1);
Behavior:
- Strict parsing (all errors fatal)
- Schema validation
- Reference checking
- UTF-8 validation
Use When: Production parsing, CI/CD validation, strict correctness required
parse_lenient(bytes: &[u8]) -> Result
Parse with relaxed validation:
let doc = parse_lenient(hedl_bytes)?;
// Tolerates some malformations:
// - Missing schemas (inferred from data)
// - Unresolved references (preserved as strings)
// - Inconsistent indentation (best-effort parse)
Use When: Parsing user-generated content, migration from other formats, exploratory analysis
validate(bytes: &[u8]) -> Result<()>
Validate document without constructing AST:
use hedl::validate;
if validate(hedl_bytes).is_ok() {
println!("Valid HEDL document");
}
Performance: Faster than parse() when you only need validation (no AST construction)
Use Cases: CI/CD checks, pre-commit hooks, batch validation
canonicalize(doc: &Document) -> Result
Convert to canonical form:
use hedl::{parse, canonicalize};
let doc = parse(hedl_bytes)?;
let canonical = canonicalize(&doc)?;
// Same document always produces identical output
// Perfect for version control and content-addressable storage
Output Characteristics:
- Deterministic formatting (2-space indentation)
- Count hints on matrix lists
- Normalized floats (no trailing zeros)
- Sorted keys (optional)
Use When: Git commits, content hashing, cache keys, comparing documents
lint(doc: &Document) -> Vec
Run lint checks for best practices:
use hedl::{parse, lint};
let doc = parse(hedl_bytes)?;
let diagnostics = lint(&doc);
for diag in diagnostics {
println!("[{}] Line {}: {}",
diag.severity, diag.line, diag.message);
}
Rules Checked (default):
- id-naming: ID naming conventions
- unused-schema: Unused %STRUCT definitions
- empty-list: Empty matrix lists
- unqualified-kv-ref: Unqualified references in key-value context
Use When: Code review automation, CI/CD quality gates, pre-commit hooks
to_json(doc: &Document) -> Result
Convert to JSON (requires "json" feature, enabled by default):
use hedl::{parse, to_json};
let doc = parse(hedl_bytes)?;
let json = to_json(&doc)?;
// Use with existing JSON APIs
Configuration: Use hedl_json::to_json_with_config() for custom options (pretty-printing, type preservation)
from_json(json: &str) -> Result
Convert from JSON to HEDL:
use hedl::from_json;
let json = r#"{"users": [{"id": "alice", "name": "Alice"}]}"#;
let doc = from_json(json)?;
// Now use HEDL's structured API
Type Inference: Automatically detects arrays-of-objects → matrix lists
Error Handling with Context
The Problem
Standard Rust errors lose context during propagation:
fn load_config() -> Result<Config> {
let data = std::fs::read("config.hedl")?;
let doc = parse(&data)?;
// Error: "Parse error at line 42: unexpected token"
// Missing: WHICH file? WHAT operation?
}
The Solution: HedlResultExt
Add context at error boundaries:
use hedl::{parse, HedlResultExt};
fn load_config(path: &str) -> Result<Config> {
let data = std::fs::read(path)
.context("Failed to read config file")?;
let doc = parse(&data)
.with_context(|| format!("Failed to parse config: {}", path))?;
Ok(/* ... */)
}
Output:
Failed to parse config: config.hedl
caused by: Parse error at line 42: unexpected token ':'
HedlResultExt Methods
.context(msg: &str)
Add static context:
parse(data).context("Failed to parse HEDL document")?;
.with_context<F: FnOnce() -> String>(f: F)
Add computed context (lazily evaluated):
parse(data)
.with_context(|| format!("Failed to parse file: {}", filename))?;
Benefits:
- Lazy: Closure only evaluated on error (zero cost on success path)
- Flexible: Capture variables, format strings, complex logic
.map_err_to_hedl()
Convert external errors to HedlError:
std::fs::read("file.hedl")
.map_err_to_hedl()
.context("Failed to read input")?;
Use Cases:
- Wrapping std::io::Error
- Converting serde errors
- Unifying error types
Feature Flags
serde
Enable Serde serialization for Document type:
hedl = { version = "2.0", features = ["serde"] }
Provides: Serialize/Deserialize impls for Document, Value, Reference
Use Cases: Storing parsed documents in databases, IPC, caching
yaml
Enable YAML format conversion:
hedl = { version = "2.0", features = ["yaml"] }
Adds:
hedl::to_yaml(doc) -> Result<String>hedl::from_yaml(yaml) -> Result<Document>
Dependencies: serde_yaml 0.9
xml
Enable XML format conversion:
hedl = { version = "2.0", features = ["xml"] }
Adds:
hedl::to_xml(doc) -> Result<String>hedl::from_xml(xml) -> Result<Document>
Dependencies: quick-xml 0.31
csv
Enable CSV format conversion:
hedl = { version = "2.0", features = ["csv"] }
Adds:
hedl::to_csv(doc) -> Result<String>(exports first entity list)hedl::from_csv(csv, type_name) -> Result<Document>
Dependencies: csv 1.3
parquet
Enable Apache Parquet columnar format:
hedl = { version = "2.0", features = ["parquet"] }
Adds:
hedl::to_parquet(doc, writer) -> Result<()>hedl::from_parquet(reader) -> Result<Document>
Dependencies: arrow 57.0, parquet 57.0
neo4j
Enable Neo4j Cypher generation:
hedl = { version = "2.0", features = ["neo4j"] }
Adds:
hedl::to_cypher(doc) -> Result<String>- Cypher CREATE/MERGE statements for Neo4j import
Dependencies: None (pure Cypher generation)
toon
Enable TOON format (token-optimized for LLMs):
hedl = { version = "2.0", features = ["toon"] }
Adds:
hedl::to_toon(doc) -> Result<String>hedl::from_toon(toon) -> Result<Document>
Use Case: Maximum token efficiency for LLM contexts
all-formats
Enable all format converters:
hedl = { version = "2.0", features = ["all-formats"] }
Equivalent to: ["serde", "yaml", "xml", "csv", "parquet", "neo4j", "toon"]
Recommended for: Applications (not libraries)
Re-Exported Modules
Full access to specialized crates through unified namespace:
use hedl::{
// Core types
Document, Value, Reference, HedlError,
// Sub-modules (explicit opt-in)
core, // hedl-core (parsing, AST)
c14n, // hedl-c14n (canonicalization)
json, // hedl-json (always available)
lint, // hedl-lint (linting)
// Feature-gated modules
yaml, // hedl-yaml (requires "yaml")
xml, // hedl-xml (requires "xml")
csv, // hedl-csv (requires "csv")
parquet, // hedl-parquet (requires "parquet")
neo4j, // hedl-neo4j (requires "neo4j")
toon, // hedl-toon (requires "toon")
};
Use When: Need advanced configuration beyond core API (e.g., hedl::json::JsonConfig for custom JSON options)
Common Patterns
Parse and Convert
use hedl::{parse, to_json};
let doc = parse(hedl_bytes)?;
let json = to_json(&doc)?;
// Send JSON to REST API
Validate Before Processing
use hedl::{validate, parse};
if validate(hedl_bytes).is_err() {
return Err("Invalid HEDL document");
}
let doc = parse(hedl_bytes)?; // Safe to unwrap
Multi-Format Pipeline
use hedl::{from_json, to_yaml, to_xml};
let doc = from_json(json_str)?;
let yaml = to_yaml(&doc)?;
let xml = to_xml(&doc)?;
Error Context Chaining
use hedl::{parse, HedlResultExt};
fn process_file(path: &str) -> Result<()> {
let data = std::fs::read(path)
.context("Failed to read input file")?;
let doc = parse(&data)
.with_context(|| format!("Failed to parse: {}", path))?;
let canonical = canonicalize(&doc)
.context("Failed to canonicalize document")?;
std::fs::write(format!("{}.canonical", path), canonical)
.context("Failed to write canonical output")?;
Ok(())
}
Type Aliases
Convenient Result type with HedlError:
pub type Result<T> = std::result::Result<T, HedlError>;
// Use in function signatures
fn my_function() -> Result<Document> {
parse(input)?
}
Performance Characteristics
Zero Overhead: Facade adds < 1% overhead vs using crates directly
Feature Gating: Unused features don't bloat binary (tree-shaking works)
Compile Time: Minimal -most work delegated to specialized crates
Binary Size: Core-only build ~200 KB, all-formats ~800 KB (compressed)
Use Cases
Application Development: Use features = ["all-formats"] for maximum flexibility without managing individual crate versions.
Library Development: Minimize dependencies -only enable formats your library needs. Let downstream users add more formats via their own dependencies.
CLI Tools: Import hedl with all-formats for comprehensive format support in user-facing tools.
Embedded Systems: Use core-only (no features) for minimal binary size. Add formats selectively based on constraints.
Microservices: Enable only formats you consume/produce (e.g., JSON for REST APIs, Parquet for analytics export).
What This Crate Doesn't Do
Streaming Parsing: Use hedl-stream directly for memory-efficient streaming of multi-GB files.
LSP/MCP Servers: Use hedl-lsp or hedl-mcp directly for server applications.
C/WASM Bindings: Use hedl-ffi or hedl-wasm directly for non-Rust integrations.
Benchmarking: Use hedl-bench directly for performance measurement and regression detection.
Testing Utilities: Use hedl-test directly for shared test fixtures and builders.
Dependencies
Core (Zero Features)
hedl-core2.0 - Parsing and ASThedl-c14n1.0 - Canonicalizationhedl-json1.0 - JSON (always included)hedl-lint1.0 - Lintingthiserror1.0 - Error types
Optional (Feature-Gated)
hedl-yaml1.0 - YAML conversion (feature: yaml)hedl-xml1.0 - XML conversion (feature: xml)hedl-csv1.0 - CSV conversion (feature: csv)hedl-parquet1.0 - Parquet conversion (feature: parquet)hedl-neo4j1.0 - Neo4j Cypher (feature: neo4j)hedl-toon1.0 - TOON format (feature: toon)serde1.0 - Serialization (feature: serde)
License
Apache-2.0
Dependencies
~3–11MB
~217K SLoC