serializer/lib.rs
1//! # DX Serializer
2//!
3//! A high-performance serialization library optimized for Humans, LLMs, AND Machines.
4//!
5//! ## Two Primary Formats
6//!
7//! | Format | Use Case | Performance |
8//! |--------|----------|-------------|
9//! | **DX LLM** | Text format for humans & LLMs | 26.8% more efficient than TOON |
10//! | **DX Machine** | Binary format for runtime | 0.70ns field access |
11//!
12//! ## Quick Start
13//!
14//! The simplest way to use dx-serializer is with the convenience functions:
15//!
16//! ```rust
17//! use serializer::{serialize, deserialize, DxDocument, DxLlmValue};
18//!
19//! // Create a document
20//! let mut doc = DxDocument::new();
21//! doc.context.insert("name".to_string(), DxLlmValue::Str("MyApp".to_string()));
22//! doc.context.insert("version".to_string(), DxLlmValue::Str("1.0.0".to_string()));
23//!
24//! // Serialize to LLM format (text)
25//! let text = serialize(&doc);
26//!
27//! // Deserialize back
28//! let parsed = deserialize(&text).unwrap();
29//! ```
30//!
31//! ## Advanced Configuration
32//!
33//! For more control over formatting and output options, use the builder pattern:
34//!
35//! ```rust
36//! use serializer::{SerializerBuilder, DxDocument, DxLlmValue};
37//!
38//! let mut doc = DxDocument::new();
39//! doc.context.insert("name".to_string(), DxLlmValue::Str("MyApp".to_string()));
40//!
41//! // Configure with builder pattern
42//! let serializer = SerializerBuilder::new()
43//! .indent_size(4)
44//! .expand_keys(true)
45//! .validate_output(false)
46//! .for_humans() // Preset for human readability
47//! .build();
48//!
49//! // Use configured serializer
50//! let text = serializer.serialize(&doc);
51//! let human_format = serializer.format_human_unchecked(&doc);
52//! ```
53//!
54//! ## Format-Specific APIs
55//!
56//! For more control, use the format-specific functions:
57//!
58//! ```rust
59//! use serializer::{DxDocument, DxLlmValue};
60//! use serializer::{document_to_llm, llm_to_document}; // LLM format
61//! use serializer::zero::DxZeroBuilder; // Machine format
62//!
63//! // Create a document
64//! let mut doc = DxDocument::new();
65//! doc.context.insert("name".to_string(), DxLlmValue::Str("MyApp".to_string()));
66//!
67//! // Convert to LLM format (text, 26.8% better than TOON)
68//! let llm_text = document_to_llm(&doc);
69//!
70//! // Convert to Machine format (binary, 0.70ns access)
71//! let mut buffer = Vec::new();
72//! let mut builder = DxZeroBuilder::new(&mut buffer, 8, 1);
73//! builder.write_u64(0, 12345);
74//! builder.write_string(8, "MyApp");
75//! builder.finish();
76//! ```
77//!
78//! ## Thread Safety
79//!
80//! All public types in dx-serializer are designed to be thread-safe:
81//!
82//! ### Types that implement `Send + Sync`
83//!
84//! The following types can be safely shared between threads:
85//!
86//! | Type | Send | Sync | Notes |
87//! |------|------|------|-------|
88//! | [`DxDocument`] | ✓ | ✓ | Immutable sharing is safe; clone for mutation |
89//! | [`DxLlmValue`] | ✓ | ✓ | All variants are thread-safe |
90//! | [`DxSection`] | ✓ | ✓ | Contains only thread-safe types |
91//! | [`DxValue`] | ✓ | ✓ | Core value type, fully thread-safe |
92//! | [`DxArray`] | ✓ | ✓ | Vector-backed, thread-safe |
93//! | [`DxObject`] | ✓ | ✓ | HashMap-backed, thread-safe |
94//! | [`DxError`] | ✓ | ✓ | Error type, fully thread-safe |
95//! | [`Mappings`] | ✓ | ✓ | Global singleton, read-only after init |
96//! | [`SerializerBuilder`] | ✓ | ✓ | Builder pattern, thread-safe |
97//! | [`Serializer`] | ✓ | ✓ | Configured serializer instance |
98//!
99//! ### Stateless Parsing
100//!
101//! All parsing functions are stateless and can be called concurrently from
102//! multiple threads without synchronization:
103//!
104//! ```rust
105//! use std::thread;
106//! use serializer::parse;
107//!
108//! let handles: Vec<_> = (0..4).map(|i| {
109//! thread::spawn(move || {
110//! let input = format!("key{}:value{}", i, i);
111//! parse(input.as_bytes())
112//! })
113//! }).collect();
114//!
115//! for handle in handles {
116//! let result = handle.join().unwrap();
117//! assert!(result.is_ok());
118//! }
119//! ```
120//!
121//! ### Thread Safety Guarantees
122//!
123//! 1. **No global mutable state**: Parsers create fresh state for each invocation
124//! 2. **Immutable singletons**: [`Mappings::get()`] returns a read-only reference
125//! 3. **No interior mutability**: Types use standard Rust ownership semantics
126//! 4. **Safe concurrent reads**: All types support concurrent read access
127//!
128//! ### Types NOT Thread-Safe
129//!
130//! The following types contain mutable state and should not be shared:
131//!
132//! | Type | Reason | Alternative |
133//! |------|--------|-------------|
134//! | [`Parser`] | Contains mutable parsing state | Create one per thread |
135//! | `DxZeroBuilder` | Writes to mutable buffer | Create one per thread |
136//! | `StreamCompressor` | Maintains compression state | Create one per thread |
137//! | `StreamDecompressor` | Maintains decompression state | Create one per thread |
138//!
139//! These types implement `Send` (can be moved between threads) but not `Sync`
140//! (cannot be shared via `&T`). Create separate instances for each thread.
141//!
142//! ## Triple Format Architecture (2026 Update)
143//!
144//! DX seamlessly converts between three formats:
145//! - **Human Format** (Front-facing files: .sr, .dx) - Beautiful, readable, on disk
146//! - **LLM Format** (.dx/serializer/*.llm) - Token-efficient, 26.8% better than TOON
147//! - **Machine Format** (.dx/serializer/*.machine) - Binary, 0.70ns access
148//!
149//! ### New Architecture (January 2026)
150//! - Front-facing .sr/.dx files now contain **Human format** (readable)
151//! - LLM format moved to `.dx/serializer/*.llm` (token-optimized for AI)
152//! - Machine format remains in `.dx/serializer/*.machine` (binary performance)
153//!
154//! ## Key Features
155//! - Base62 integers (%x): 320→5A, 540→8k
156//! - Auto-increment (%#): Sequential IDs generated automatically
157//! - Holographic inflate/deflate for editor integration
158//! - Binary format using RKYV zero-copy architecture
159//!
160//! ## Safety
161//!
162//! This crate uses `unsafe` code in specific, well-documented locations for
163//! performance-critical operations. All unsafe code follows these principles:
164//!
165//! 1. **Minimal scope**: Unsafe blocks are as small as possible
166//! 2. **Documented invariants**: Every unsafe block has a `// SAFETY:` comment
167//! 3. **Validated preconditions**: Safe wrappers validate before unsafe operations
168//!
169//! ### Unsafe Operations by Module
170//!
171//! | Module | Operation | Justification |
172//! |--------|-----------|---------------|
173//! | `zero/deserialize` | Zero-copy pointer cast | Validated size and alignment |
174//! | `zero/safe_deserialize` | Safe wrapper for casts | Validates bounds and alignment before cast |
175//! | `zero/quantum` | Unchecked field access | Compile-time offsets, caller validates bounds |
176//! | `zero/prefetch` | CPU cache prefetch hints | Hint-only, no memory access |
177//! | `zero/simd` | SSE4.2/AVX2 intrinsics | Target feature guards ensure CPU support |
178//! | `zero/simd512` | AVX-512 intrinsics | Target feature guards ensure CPU support |
179//! | `zero/mmap` | Memory-mapped access | Caller validates offset and type |
180//! | `zero/arena` | Arena allocation | Capacity checked before allocation |
181//! | `zero/inline` | UTF-8 unchecked | UTF-8 validated on construction |
182//! | `utf8` | UTF-8 unchecked | Manual UTF-8 validation precedes conversion |
183//! | `safety` | Safe cast wrappers | Validates size and alignment before cast |
184//!
185//! ### Safe Wrappers
186//!
187//! For most use cases, prefer the safe wrappers in the [`safety`] module:
188//!
189//! - `safety::safe_cast` - Validates size and alignment before casting
190//! - `safety::safe_read_slice` - Validates bounds before reading a slice
191//! - [`safety::check_bounds`] - Validates offset and length are within bounds
192//! - [`safety::check_alignment`] - Validates pointer alignment for a type
193//!
194//! The `zero::safe_deserialize::SafeDeserializer` provides a fully safe API
195//! for zero-copy deserialization with automatic bounds and alignment checking.
196//!
197//! ### SIMD Safety
198//!
199//! SIMD operations are guarded by `#[target_feature]` attributes and `cfg` blocks:
200//!
201//! - **Compile-time**: `#[cfg(target_feature = "avx512f")]` ensures the code is
202//! only compiled when the target supports the feature
203//! - **Runtime**: `is_x86_feature_detected!` macro checks CPU capabilities
204//! - **Fallback**: Portable implementations are always available
205//!
206//! ### Memory Safety Guarantees
207//!
208//! 1. **No undefined behavior**: All unsafe code maintains Rust's safety invariants
209//! 2. **No data races**: No shared mutable state in unsafe code
210//! 3. **No use-after-free**: Lifetime annotations ensure references remain valid
211//! 4. **No buffer overflows**: Bounds are validated before unsafe access
212
213// =============================================================================
214// Crate-level lint configuration
215// =============================================================================
216//
217// These allows are intentional and justified:
218//
219// 1. `should_implement_trait`: Methods like `from_str()` in `zero/inline.rs` and
220// `zero/format.rs` return `Option<Self>` instead of `Result<Self, E>` because
221// they are fallible constructors, not trait implementations. The `FromStr` trait
222// requires `Result`, but these methods intentionally use `Option` for simpler APIs.
223//
224// 2. `only_used_in_recursion`: Parameters like `depth` in recursive formatters are
225// passed through recursion for tracking but may not be used in all branches.
226// This is intentional for consistent recursive signatures.
227//
228// 3. `doc_nested_refdefs`: Documentation for binary formats uses reference-style
229// links in list items to document byte layouts clearly.
230//
231#![allow(clippy::should_implement_trait)]
232#![allow(clippy::only_used_in_recursion)]
233#![allow(clippy::doc_nested_refdefs)]
234
235pub mod base62;
236#[cfg(test)]
237mod base62_props;
238pub mod binary_output;
239pub mod biome_config;
240pub mod builder;
241
242// Safety validation utilities (inlined from dx-safety for standalone publishability)
243pub mod safety;
244
245/// Compression helpers for compact machine-format output.
246pub mod compress;
247pub mod converters;
248pub mod encoder;
249pub mod error;
250#[cfg(test)]
251mod error_props;
252pub mod formatter;
253// TODO: Re-enable when async-io feature is implemented
254// #[cfg(feature = "async-io")]
255// pub mod io;
256pub mod llm;
257pub mod llm_models;
258pub mod machine;
259/// Compatibility exports for the documented DX-Zero machine-format API.
260///
261/// The implementation now lives under [`machine`], but the public `zero`
262/// namespace remains available for existing callers and specification tests.
263pub mod zero {
264 /// Header validation exports for callers that import `zero::header::HeaderError`.
265 pub mod header {
266 pub use crate::machine::header::HeaderError;
267 }
268
269 pub use crate::machine::compress::{StreamCompressor, StreamDecompressor};
270 pub use crate::machine::header::{
271 FLAG_HAS_HEAP, FLAG_HAS_INTERN, FLAG_HAS_LENGTH_TABLE, FLAG_LITTLE_ENDIAN, HeaderError,
272 };
273 pub use crate::machine::{
274 CompressionLevel, DxCompressed, DxFormat, DxMachineBuilder as DxZeroBuilder,
275 DxMachineHeader as DxZeroHeader, DxMachineSlot as DxZeroSlot, HEAP_MARKER, INLINE_MARKER,
276 MAGIC, VERSION, detect_format,
277 };
278}
279/// Key and path abbreviation mappings used by text and machine encoders.
280pub mod mappings;
281/// Key and path optimization helpers for compact DX output.
282pub mod optimizer;
283pub mod parser;
284#[cfg(test)]
285mod parser_security_props;
286pub mod schema;
287pub mod tokenizer;
288pub mod types;
289pub mod utf8;
290#[cfg(test)]
291mod utf8_props;
292#[cfg(test)]
293mod value_props;
294pub mod wasm;
295pub mod watch;
296
297// Re-export derive macro when feature is enabled
298#[cfg(feature = "derive")]
299pub use dx_serializer_derive::{QuantumLayout, dx_static_serialize, include_serialized};
300
301pub use base62::{decode_base62, encode_base62};
302pub use binary_output::{
303 BinaryConfig, get_binary_path, hash_path, is_cache_valid, read_binary, write_binary,
304};
305pub use biome_config::{
306 DxBiomeConfig, DxBiomeConfigEntry, DxBiomeConfigError, DxBiomeTarget,
307 biome_config_from_document, biome_config_from_source, load_biome_config,
308};
309pub use compress::{compress_to_writer, format_machine};
310pub use converters::{convert_to_dx, dx_to_toon, toon_to_dx};
311#[cfg(feature = "converters")]
312pub use converters::{json_to_document, json_to_dx, toml_to_document, toml_to_dx, yaml_to_dx};
313pub use encoder::{Encoder, encode, encode_to_writer};
314pub use error::{DxError, Result};
315pub use formatter::{HumanFormatter as BinaryHumanFormatter, format_human};
316pub use mappings::Mappings;
317pub use optimizer::{optimize_key, optimize_path};
318pub use parser::{Parser, parse, parse_stream};
319pub use schema::{Schema, TypeHint};
320pub use types::{DxArray, DxObject, DxValue};
321pub use utf8::{
322 Utf8ValidationError, validate_string_input, validate_utf8, validate_utf8_detailed,
323 validate_utf8_owned,
324};
325
326// Re-export IndexMap for external use
327pub use indexmap::IndexMap;
328
329// Re-export LLM/Human format types at crate root for convenience
330#[cfg(feature = "mmap")]
331pub use llm::machine_file_to_document_mmap;
332pub use llm::{
333 AbbrevDict, ConvertError, DxDocument, DxLlmValue, DxSection, HumanFormatConfig, HumanFormatter,
334 HumanParseError, HumanParser, LlmParser, LlmSerializer, MachineFormat,
335 ParseError as LlmParseError,
336};
337pub use llm::{
338 document_to_human, document_to_llm, document_to_machine, human_to_document, human_to_llm,
339 human_to_machine, human_to_machine_uncompressed, is_llm_format, llm_to_document, llm_to_human,
340 llm_to_machine, machine_bytes_to_document, machine_to_document, machine_to_human,
341 machine_to_llm, try_document_to_machine_with_compression,
342};
343
344// Re-export Serializer Output types for .dx/serializer/ generation
345pub use llm::{
346 SerializerOutput, SerializerOutputConfig, SerializerOutputError, SerializerPaths,
347 SerializerResult,
348};
349
350// Re-export utility types
351pub use llm::{
352 CacheConfig, CacheError, CacheGenerator, CachePaths, CacheResult, PrettyPrintError,
353 PrettyPrinter, PrettyPrinterConfig, TableWrapper, TableWrapperConfig,
354};
355
356// Re-export token counting types
357pub use llm::{ModelType, TokenCounter, TokenInfo};
358
359// Re-export LLM model pricing and analysis
360pub use llm_models::{
361 LLM_MODELS, LlmModel, Provider, TokenAnalysis, analyze_all_models, format_cost, format_tokens,
362 models_by_provider,
363};
364
365// Re-export DX Serializer format types (token-efficient LLM format)
366pub use llm::{
367 LlmParser as DxSerializerParser, LlmSerializer as DxSerializerSerializer,
368 ParseError as DxSerializerParseError,
369};
370
371// Re-export WASM types for VS Code extension
372pub use wasm::{DxSerializer, SerializerConfig, TransformResult, ValidationResult, smart_quote};
373
374// Re-export builder pattern for advanced configuration
375pub use builder::{Serializer, SerializerBuilder};
376
377// =============================================================================
378// Simplified Public API
379// =============================================================================
380
381/// Serialize a DxDocument to the LLM text format.
382///
383/// This is the recommended format for most use cases as it's:
384/// - Human-readable
385/// - LLM-friendly (token-efficient)
386/// - Easy to debug
387///
388/// # Example
389///
390/// ```rust
391/// use serializer::{serialize, DxDocument, DxLlmValue};
392///
393/// let mut doc = DxDocument::new();
394/// doc.context.insert("name".to_string(), DxLlmValue::Str("MyApp".to_string()));
395///
396/// let text = serialize(&doc);
397/// assert!(text.contains("name") && text.contains("MyApp"));
398/// ```
399///
400/// # Errors
401///
402/// This function is infallible and always returns a valid string representation
403/// of the document. The serialization process handles all value types gracefully.
404#[must_use]
405pub fn serialize(doc: &DxDocument) -> String {
406 document_to_llm(doc)
407}
408
409/// Deserialize LLM text format to a DxDocument.
410///
411/// This parses the token-efficient LLM format back into a structured document.
412///
413/// # Example
414///
415/// ```rust
416/// use serializer::{deserialize, DxLlmValue};
417///
418/// let text = "name=MyApp\nversion=1.0.0";
419/// let doc = deserialize(text).unwrap();
420///
421/// assert!(doc.context.contains_key("name"));
422/// ```
423///
424/// # Errors
425///
426/// Returns a [`ConvertError`] in the following cases:
427///
428/// - [`ConvertError::LlmParse`] - When the input contains invalid DX Serializer/LLM syntax:
429/// - **Unexpected character**: Invalid character at a specific position
430/// - **Unexpected EOF**: Input ends prematurely (e.g., unclosed brackets)
431/// - **Invalid value format**: Malformed value that cannot be parsed
432/// - **Schema mismatch**: Table row has wrong number of columns
433/// - **UTF-8 error**: Input contains invalid UTF-8 sequences (with byte offset)
434/// - **Input too large**: Input exceeds `MAX_INPUT_SIZE` (100 MB)
435/// - **Unclosed bracket/parenthesis**: Missing closing delimiter
436/// - **Missing value**: Key without corresponding value after `=`
437/// - **Invalid table format**: Malformed table definition
438///
439/// # Example Error Handling
440///
441/// ```rust
442/// use serializer::{deserialize, ConvertError};
443///
444/// let result = deserialize("invalid[[[");
445/// match result {
446/// Ok(doc) => println!("Parsed {} context entries", doc.context.len()),
447/// Err(ConvertError::LlmParse(e)) => eprintln!("Parse error: {}", e),
448/// Err(e) => eprintln!("Other error: {}", e),
449/// }
450/// ```
451pub fn deserialize(input: &str) -> std::result::Result<DxDocument, ConvertError> {
452 llm_to_document(input)
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn test_round_trip() {
461 // Simple key-value format that the parser supports
462 let input = b"name:Test
463value:123
464active:+";
465
466 let parsed = parse(input).expect("Parse failed");
467 let encoded = encode(&parsed).expect("Encode failed");
468 let reparsed = parse(&encoded).expect("Reparse failed");
469
470 assert_eq!(parsed, reparsed);
471 }
472
473 #[test]
474 fn test_human_format() {
475 let input = b"data=id%i name%s
4761 Test
4772 Demo";
478
479 let parsed = parse(input).expect("Parse failed");
480 let human = format_human(&parsed).expect("Format failed");
481
482 assert!(human.contains("DATA TABLE"));
483 assert!(human.contains("Test"));
484 assert!(human.contains("Demo"));
485 }
486
487 #[test]
488 fn test_serialize_deserialize_convenience() {
489 // Test the simplified API
490 let mut doc = DxDocument::new();
491 doc.context
492 .insert("name".to_string(), DxLlmValue::Str("TestApp".to_string()));
493 doc.context
494 .insert("version".to_string(), DxLlmValue::Str("1.0.0".to_string()));
495 doc.context
496 .insert("count".to_string(), DxLlmValue::Num(42.0));
497 doc.context
498 .insert("active".to_string(), DxLlmValue::Bool(true));
499
500 // Serialize
501 let text = serialize(&doc);
502 assert!(!text.is_empty());
503
504 // Deserialize
505 let parsed = deserialize(&text).expect("Deserialize failed");
506
507 // Verify round-trip preserves data
508 assert_eq!(parsed.context.len(), doc.context.len());
509 }
510
511 #[test]
512 fn test_serialize_empty_document() {
513 let doc = DxDocument::new();
514 let text = serialize(&doc);
515 // Empty document should produce minimal output
516 assert!(text.is_empty() || text.trim().is_empty());
517 }
518
519 #[test]
520 fn test_deserialize_invalid_input() {
521 // Invalid input should return an error, not panic
522 let result = deserialize("this is not valid LLM format {{{{");
523 // The function should handle invalid input gracefully
524 // It may succeed with partial parsing or return an error
525 let _ = result;
526 }
527}