Skip to main content

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//! Additional output-only codecs:
14//!
15//! | Module | Description |
16//! |--------|-------------|
17//! | [`rdf`] | RDF serialization in Turtle and JSON-LD formats |
18//!
19//! Use [`codec_for()`] to look up a codec by MIME type:
20//!
21//! ```rust
22//! use haystack_core::codecs::codec_for;
23//!
24//! let zinc = codec_for("text/zinc").unwrap();
25//! let grid = zinc.decode_grid("ver:\"3.0\"\nempty\n").unwrap();
26//! let encoded = zinc.encode_grid(&grid).unwrap();
27//! ```
28//!
29//! The [`shared`] submodule provides common encoding/decoding helper functions
30//! used by multiple codec implementations.
31
32#[cfg(feature = "arrow")]
33pub mod arrow_ipc;
34pub mod csv;
35#[cfg(feature = "haystack-serde")]
36pub mod hbf;
37pub mod json;
38pub mod rdf;
39pub mod shared;
40pub mod trio;
41pub mod zinc;
42
43use crate::data::{HCol, HDict, HGrid};
44use crate::kinds::Kind;
45
46/// MIME type for Haystack Binary Format.
47pub const HBF_MIME: &str = "application/x-haystack-binary";
48
49/// Errors that can occur during encoding or decoding.
50#[derive(Debug, thiserror::Error)]
51pub enum CodecError {
52    #[error("parse error at position {pos}: {message}")]
53    Parse { pos: usize, message: String },
54    #[error("encoding error: {0}")]
55    Encode(String),
56    #[error("unsupported kind for this codec")]
57    UnsupportedKind,
58}
59
60/// Trait for Haystack wire format codecs.
61pub trait Codec: Send + Sync {
62    /// The MIME type for this codec (e.g. `"text/zinc"`).
63    fn mime_type(&self) -> &str;
64
65    /// Encode an HGrid to a string.
66    fn encode_grid(&self, grid: &HGrid) -> Result<String, CodecError>;
67
68    /// Decode a string to an HGrid.
69    fn decode_grid(&self, input: &str) -> Result<HGrid, CodecError>;
70
71    /// Encode a single scalar Kind value to a string.
72    fn encode_scalar(&self, val: &Kind) -> Result<String, CodecError>;
73
74    /// Decode a string to a single scalar Kind value.
75    fn decode_scalar(&self, input: &str) -> Result<Kind, CodecError>;
76
77    /// Encode the grid header (version line + meta + column definitions).
78    ///
79    /// For Zinc: `ver:"3.0" meta\ncol1,col2,col3\n`
80    /// Default implementation returns the full encoded grid (no streaming benefit).
81    fn encode_grid_header(&self, grid: &HGrid) -> Result<Vec<u8>, CodecError> {
82        self.encode_grid(grid).map(|s| s.into_bytes())
83    }
84
85    /// Encode a single grid row given the column definitions.
86    ///
87    /// For Zinc: `val1,val2,val3\n`
88    /// Default implementation returns an empty vec (header contained everything).
89    fn encode_grid_row(&self, _cols: &[HCol], _row: &HDict) -> Result<Vec<u8>, CodecError> {
90        Ok(Vec::new())
91    }
92}
93
94static ZINC: zinc::ZincCodec = zinc::ZincCodec;
95static TRIO: trio::TrioCodec = trio::TrioCodec;
96static JSON4: json::Json4Codec = json::Json4Codec;
97static JSON3: json::Json3Codec = json::Json3Codec;
98static CSV: csv::CsvCodec = csv::CsvCodec;
99
100/// Look up a codec by MIME type.
101///
102/// Returns a static codec reference for the given MIME type, or `None` if
103/// the MIME type is not supported.
104pub fn codec_for(mime_type: &str) -> Option<&'static dyn Codec> {
105    match mime_type {
106        "text/zinc" => Some(&ZINC),
107        "text/trio" => Some(&TRIO),
108        "application/json" => Some(&JSON4),
109        "application/json;v=3" => Some(&JSON3),
110        "text/csv" => Some(&CSV),
111        _ => None,
112    }
113}
114
115/// Encode an HGrid to binary using HBF (Haystack Binary Format).
116///
117/// Returns `Some(bytes)` when the `haystack-serde` feature is enabled,
118/// or `None` otherwise. The MIME type for HBF is `application/x-haystack-binary`.
119#[cfg(feature = "haystack-serde")]
120pub fn encode_grid_binary(grid: &HGrid) -> Result<Vec<u8>, String> {
121    hbf::encode_grid(grid).map_err(|e| e.to_string())
122}
123
124/// Decode an HGrid from binary HBF (Haystack Binary Format) bytes.
125///
126/// Returns `Ok(grid)` when the `haystack-serde` feature is enabled.
127/// The MIME type for HBF is `application/x-haystack-binary`.
128#[cfg(feature = "haystack-serde")]
129pub fn decode_grid_binary(bytes: &[u8]) -> Result<HGrid, String> {
130    hbf::decode_grid(bytes).map_err(|e| e.to_string())
131}