haystack_core/codecs/mod.rs
1//! Haystack wire format codecs for serialization and deserialization.
2//!
3//! Provides the [`Codec`] trait and five built-in implementations:
4//!
5//! | MIME Type | Module | Description |
6//! |----------|--------|-------------|
7//! | `text/zinc` | [`zinc`] | Zinc — the default Haystack text format (fastest encode/decode) |
8//! | `text/trio` | [`trio`] | Trio — tag-oriented format for defining entities and defs |
9//! | `application/json` | [`json`] (v4) | Haystack JSON v4 — standard JSON encoding |
10//! | `application/json;v=3` | [`json`] (v3) | Haystack JSON v3 — legacy JSON encoding |
11//! | `text/csv` | [`csv`] | CSV — comma-separated values for spreadsheet interop |
12//!
13//! Use [`codec_for()`] to look up a codec by MIME type:
14//!
15//! ```rust
16//! use haystack_core::codecs::codec_for;
17//!
18//! let zinc = codec_for("text/zinc").unwrap();
19//! let grid = zinc.decode_grid("ver:\"3.0\"\nempty\n").unwrap();
20//! let encoded = zinc.encode_grid(&grid).unwrap();
21//! ```
22//!
23//! The [`shared`] submodule provides common encoding/decoding helper functions
24//! used by multiple codec implementations.
25
26pub mod csv;
27pub mod json;
28pub mod shared;
29pub mod trio;
30pub mod zinc;
31
32use crate::data::{HCol, HDict, HGrid};
33use crate::kinds::Kind;
34
35/// Errors that can occur during encoding or decoding.
36#[derive(Debug, thiserror::Error)]
37pub enum CodecError {
38 #[error("parse error at position {pos}: {message}")]
39 Parse { pos: usize, message: String },
40 #[error("encoding error: {0}")]
41 Encode(String),
42 #[error("unsupported kind for this codec")]
43 UnsupportedKind,
44}
45
46/// Trait for Haystack wire format codecs.
47pub trait Codec: Send + Sync {
48 /// The MIME type for this codec (e.g. `"text/zinc"`).
49 fn mime_type(&self) -> &str;
50
51 /// Encode an HGrid to a string.
52 fn encode_grid(&self, grid: &HGrid) -> Result<String, CodecError>;
53
54 /// Decode a string to an HGrid.
55 fn decode_grid(&self, input: &str) -> Result<HGrid, CodecError>;
56
57 /// Encode a single scalar Kind value to a string.
58 fn encode_scalar(&self, val: &Kind) -> Result<String, CodecError>;
59
60 /// Decode a string to a single scalar Kind value.
61 fn decode_scalar(&self, input: &str) -> Result<Kind, CodecError>;
62
63 /// Encode the grid header (version line + meta + column definitions).
64 ///
65 /// Default implementation returns the full encoded grid (no streaming benefit).
66 fn encode_grid_header(&self, grid: &HGrid) -> Result<Vec<u8>, CodecError> {
67 self.encode_grid(grid).map(|s| s.into_bytes())
68 }
69
70 /// Encode a single grid row given the column definitions.
71 ///
72 /// Default implementation returns an empty vec (header contained everything).
73 fn encode_grid_row(&self, _cols: &[HCol], _row: &HDict) -> Result<Vec<u8>, CodecError> {
74 Ok(Vec::new())
75 }
76}
77
78static ZINC: zinc::ZincCodec = zinc::ZincCodec;
79static TRIO: trio::TrioCodec = trio::TrioCodec;
80static JSON4: json::Json4Codec = json::Json4Codec;
81static JSON3: json::Json3Codec = json::Json3Codec;
82static CSV: csv::CsvCodec = csv::CsvCodec;
83
84/// Look up a codec by MIME type.
85///
86/// Returns a static codec reference for the given MIME type, or `None` if
87/// the MIME type is not supported.
88pub fn codec_for(mime_type: &str) -> Option<&'static dyn Codec> {
89 match mime_type {
90 "text/zinc" => Some(&ZINC),
91 "text/trio" => Some(&TRIO),
92 "application/json" => Some(&JSON4),
93 "application/json;v=3" => Some(&JSON3),
94 "text/csv" => Some(&CSV),
95 _ => None,
96 }
97}