Skip to main content

seq_runtime/
serialize.rs

1//! Serialization of Seq Values
2//!
3//! This module provides a serializable representation of Seq runtime values.
4//! It enables Value persistence and exchange with external systems.
5//!
6//! # Use Cases
7//!
8//! - **Actor persistence**: Event sourcing and state snapshots
9//! - **Data pipelines**: Arrow/Parquet integration
10//! - **IPC**: Message passing between processes
11//! - **Storage**: Database and file persistence
12//!
13//! # Why TypedValue?
14//!
15//! The runtime `Value` type contains arena-allocated strings (`SeqString`)
16//! which aren't directly serializable. `TypedValue` uses owned `String`s
17//! and can be serialized with serde/bincode.
18//!
19//! # Why BTreeMap instead of HashMap?
20//!
21//! `TypedValue::Map` uses `BTreeMap` (not `HashMap`) for deterministic serialization.
22//! This ensures that the same logical map always serializes to identical bytes,
23//! which is important for:
24//! - Content-addressable storage (hashing serialized data)
25//! - Reproducible snapshots for testing and debugging
26//! - Consistent behavior across runs
27//!
28//! The O(n log n) insertion overhead is acceptable since serialization is
29//! typically infrequent (snapshots, persistence) rather than on the hot path.
30//!
31//! # Performance
32//!
33//! Uses bincode for fast, compact binary serialization.
34//! For debugging, use `TypedValue::to_debug_string()`.
35//!
36//! # Byte-cleanliness boundary
37//!
38//! `TypedValue::String` and `TypedMapKey::String` hold owned `String` —
39//! UTF-8 by definition. Conversion from a runtime `Value::String`
40//! (which is byte-clean and may carry arbitrary bytes) goes through
41//! `as_str_or_empty()`: invalid UTF-8 collapses to the empty string.
42//! That is the deliberate, narrow contract of this module — it serves
43//! the *text-shaped* payloads of actor persistence, IPC, and
44//! Arrow/Parquet pipelines, not arbitrary binary blobs.
45//!
46//! Programs that need to persist binary `String` payloads should
47//! base64- or hex-encode them at the Seq layer before handing them
48//! to `serialize`, or use a binary-aware transport (file slurp/spit,
49//! HTTP body, channel send) which retains bytes verbatim.
50
51use crate::seqstring::global_string;
52use crate::value::{MapKey as RuntimeMapKey, Value, VariantData};
53use serde::{Deserialize, Serialize};
54use std::collections::{BTreeMap, HashMap};
55use std::sync::Arc;
56
57/// Error during serialization/deserialization
58#[derive(Debug)]
59pub enum SerializeError {
60    /// Cannot serialize quotations (code)
61    QuotationNotSerializable,
62    /// Cannot serialize closures
63    ClosureNotSerializable,
64    /// Cannot serialize channels (runtime state)
65    ChannelNotSerializable,
66    /// Bincode encoding error
67    BincodeEncodeError(bincode::error::EncodeError),
68    /// Bincode decoding error
69    BincodeDecodeError(bincode::error::DecodeError),
70    /// Invalid data structure
71    InvalidData(String),
72    /// Non-finite float (NaN or Infinity)
73    NonFiniteFloat(f64),
74}
75
76impl std::fmt::Display for SerializeError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            SerializeError::QuotationNotSerializable => {
80                write!(f, "Quotations cannot be serialized - code is not data")
81            }
82            SerializeError::ClosureNotSerializable => {
83                write!(f, "Closures cannot be serialized - code is not data")
84            }
85            SerializeError::ChannelNotSerializable => {
86                write!(f, "Channels cannot be serialized - runtime state")
87            }
88            SerializeError::BincodeEncodeError(e) => write!(f, "Bincode encode error: {}", e),
89            SerializeError::BincodeDecodeError(e) => write!(f, "Bincode decode error: {}", e),
90            SerializeError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
91            SerializeError::NonFiniteFloat(v) => {
92                write!(f, "Cannot serialize non-finite float: {}", v)
93            }
94        }
95    }
96}
97
98impl std::error::Error for SerializeError {
99    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
100        match self {
101            SerializeError::BincodeEncodeError(e) => Some(e),
102            SerializeError::BincodeDecodeError(e) => Some(e),
103            _ => None,
104        }
105    }
106}
107
108impl From<bincode::error::EncodeError> for SerializeError {
109    fn from(e: bincode::error::EncodeError) -> Self {
110        SerializeError::BincodeEncodeError(e)
111    }
112}
113
114impl From<bincode::error::DecodeError> for SerializeError {
115    fn from(e: bincode::error::DecodeError) -> Self {
116        SerializeError::BincodeDecodeError(e)
117    }
118}
119
120/// Serializable map key types
121///
122/// Subset of TypedValue that can be used as map keys.
123/// Mirrors runtime `MapKey` but with owned strings.
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
125pub enum TypedMapKey {
126    Int(i64),
127    Bool(bool),
128    String(String),
129}
130
131impl TypedMapKey {
132    /// Convert to a TypedValue
133    pub fn to_typed_value(&self) -> TypedValue {
134        match self {
135            TypedMapKey::Int(v) => TypedValue::Int(*v),
136            TypedMapKey::Bool(v) => TypedValue::Bool(*v),
137            TypedMapKey::String(v) => TypedValue::String(v.clone()),
138        }
139    }
140
141    /// Convert from runtime MapKey
142    pub fn from_runtime(key: &RuntimeMapKey) -> Self {
143        match key {
144            RuntimeMapKey::Int(v) => TypedMapKey::Int(*v),
145            RuntimeMapKey::Bool(v) => TypedMapKey::Bool(*v),
146            RuntimeMapKey::String(s) => TypedMapKey::String(s.as_str_or_empty().to_string()),
147        }
148    }
149
150    /// Convert to runtime MapKey (requires global string allocation)
151    pub fn to_runtime(&self) -> RuntimeMapKey {
152        match self {
153            TypedMapKey::Int(v) => RuntimeMapKey::Int(*v),
154            TypedMapKey::Bool(v) => RuntimeMapKey::Bool(*v),
155            TypedMapKey::String(s) => RuntimeMapKey::String(global_string(s.clone())),
156        }
157    }
158}
159
160/// Serializable representation of Seq Values
161///
162/// This type mirrors `Value` but uses owned data suitable for serialization.
163/// Quotations and closures cannot be serialized (they contain code, not data).
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
165pub enum TypedValue {
166    Int(i64),
167    Float(f64),
168    Bool(bool),
169    String(String),
170    /// Symbol (interned identifier)
171    Symbol(String),
172    /// Map with typed keys and values
173    Map(BTreeMap<TypedMapKey, TypedValue>),
174    /// Variant with tag (symbol name) and fields
175    Variant {
176        tag: String,
177        fields: Vec<TypedValue>,
178    },
179}
180
181impl TypedValue {
182    /// Convert from runtime Value
183    ///
184    /// Returns error if Value contains:
185    /// - Code (Quotation/Closure) - not serializable
186    /// - Non-finite floats (NaN/Infinity) - could cause logic issues
187    pub fn from_value(value: &Value) -> Result<Self, SerializeError> {
188        match value {
189            Value::Int(v) => Ok(TypedValue::Int(*v)),
190            Value::Float(v) => {
191                if !v.is_finite() {
192                    return Err(SerializeError::NonFiniteFloat(*v));
193                }
194                Ok(TypedValue::Float(*v))
195            }
196            Value::Bool(v) => Ok(TypedValue::Bool(*v)),
197            Value::String(s) => Ok(TypedValue::String(s.as_str_or_empty().to_string())),
198            Value::Symbol(s) => Ok(TypedValue::Symbol(s.as_str_or_empty().to_string())),
199            Value::Map(map) => {
200                let mut typed_map = BTreeMap::new();
201                for (k, v) in map.iter() {
202                    let typed_key = TypedMapKey::from_runtime(k);
203                    let typed_value = TypedValue::from_value(v)?;
204                    typed_map.insert(typed_key, typed_value);
205                }
206                Ok(TypedValue::Map(typed_map))
207            }
208            Value::Variant(data) => {
209                let mut typed_fields = Vec::with_capacity(data.fields.len());
210                for field in data.fields.iter() {
211                    typed_fields.push(TypedValue::from_value(field)?);
212                }
213                Ok(TypedValue::Variant {
214                    tag: data.tag.as_str_or_empty().to_string(),
215                    fields: typed_fields,
216                })
217            }
218            Value::Quotation { .. } => Err(SerializeError::QuotationNotSerializable),
219            Value::Closure { .. } => Err(SerializeError::ClosureNotSerializable),
220            Value::Channel(_) => Err(SerializeError::ChannelNotSerializable),
221            Value::WeaveCtx { .. } => Err(SerializeError::ChannelNotSerializable), // Weaves contain channels
222        }
223    }
224
225    /// Convert to runtime Value
226    ///
227    /// Note: Strings are allocated as global strings (not arena)
228    /// to ensure they outlive any strand context.
229    pub fn to_value(&self) -> Value {
230        match self {
231            TypedValue::Int(v) => Value::Int(*v),
232            TypedValue::Float(v) => Value::Float(*v),
233            TypedValue::Bool(v) => Value::Bool(*v),
234            TypedValue::String(s) => Value::String(global_string(s.clone())),
235            TypedValue::Symbol(s) => Value::Symbol(global_string(s.clone())),
236            TypedValue::Map(map) => {
237                let mut runtime_map = HashMap::new();
238                for (k, v) in map.iter() {
239                    runtime_map.insert(k.to_runtime(), v.to_value());
240                }
241                Value::Map(Box::new(runtime_map))
242            }
243            TypedValue::Variant { tag, fields } => {
244                let runtime_fields: Vec<Value> = fields.iter().map(|f| f.to_value()).collect();
245                Value::Variant(Arc::new(VariantData::new(
246                    global_string(tag.clone()),
247                    runtime_fields,
248                )))
249            }
250        }
251    }
252
253    /// Try to convert to a map key (fails for Float, Map, Variant)
254    pub fn to_map_key(&self) -> Result<TypedMapKey, SerializeError> {
255        match self {
256            TypedValue::Int(v) => Ok(TypedMapKey::Int(*v)),
257            TypedValue::Bool(v) => Ok(TypedMapKey::Bool(*v)),
258            TypedValue::String(v) => Ok(TypedMapKey::String(v.clone())),
259            TypedValue::Float(_) => Err(SerializeError::InvalidData(
260                "Float cannot be a map key".to_string(),
261            )),
262            TypedValue::Map(_) => Err(SerializeError::InvalidData(
263                "Map cannot be a map key".to_string(),
264            )),
265            TypedValue::Variant { .. } => Err(SerializeError::InvalidData(
266                "Variant cannot be a map key".to_string(),
267            )),
268            TypedValue::Symbol(v) => Ok(TypedMapKey::String(v.clone())),
269        }
270    }
271
272    /// Serialize to binary format (bincode)
273    pub fn to_bytes(&self) -> Result<Vec<u8>, SerializeError> {
274        bincode::serde::encode_to_vec(self, bincode::config::standard())
275            .map_err(SerializeError::from)
276    }
277
278    /// Deserialize from binary format (bincode)
279    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SerializeError> {
280        let (value, _read) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())
281            .map_err(SerializeError::from)?;
282        Ok(value)
283    }
284
285    /// Convert to human-readable debug string
286    pub fn to_debug_string(&self) -> String {
287        match self {
288            TypedValue::Int(v) => format!("{}", v),
289            TypedValue::Float(v) => format!("{}", v),
290            TypedValue::Bool(v) => format!("{}", v),
291            TypedValue::String(v) => format!("{:?}", v),
292            TypedValue::Symbol(v) => format!(":{}", v),
293            TypedValue::Map(m) => {
294                let entries: Vec<String> = m
295                    .iter()
296                    .map(|(k, v)| format!("{}: {}", key_to_debug_string(k), v.to_debug_string()))
297                    .collect();
298                format!("{{ {} }}", entries.join(", "))
299            }
300            TypedValue::Variant { tag, fields } => {
301                if fields.is_empty() {
302                    format!("(Variant#{})", tag)
303                } else {
304                    let field_strs: Vec<String> =
305                        fields.iter().map(|f| f.to_debug_string()).collect();
306                    format!("(Variant#{} {})", tag, field_strs.join(" "))
307                }
308            }
309        }
310    }
311}
312
313fn key_to_debug_string(key: &TypedMapKey) -> String {
314    match key {
315        TypedMapKey::Int(v) => format!("{}", v),
316        TypedMapKey::Bool(v) => format!("{}", v),
317        TypedMapKey::String(v) => format!("{:?}", v),
318    }
319}
320
321/// Extension trait for Value to add serialization methods
322pub trait ValueSerialize {
323    /// Convert to serializable TypedValue
324    fn to_typed(&self) -> Result<TypedValue, SerializeError>;
325
326    /// Serialize directly to bytes
327    fn to_bytes(&self) -> Result<Vec<u8>, SerializeError>;
328}
329
330impl ValueSerialize for Value {
331    fn to_typed(&self) -> Result<TypedValue, SerializeError> {
332        TypedValue::from_value(self)
333    }
334
335    fn to_bytes(&self) -> Result<Vec<u8>, SerializeError> {
336        TypedValue::from_value(self)?.to_bytes()
337    }
338}
339
340#[cfg(test)]
341mod tests;