Skip to main content

sayiir_core/
codec.rs

1//! Pluggable serialization codec traits.
2//!
3//! Every workflow carries a codec that serializes task inputs/outputs to
4//! [`Bytes`] and back. The trait hierarchy is:
5//!
6//! - [`Encoder`] / [`Decoder`] — marker traits
7//! - [`sealed::EncodeValue<T>`] / [`sealed::DecodeValue<T>`] — per-type encode/decode
8//! - [`Codec`] — blanket `Encoder + Decoder`
9//! - [`EnvelopeCodec`] — object-safe helpers for branch routing envelopes
10//!
11//! Concrete implementations live in `sayiir-runtime` (`JsonCodec`, `RkyvCodec`).
12
13use crate::error::BoxError;
14use bytes::Bytes;
15
16/// Sealed helper traits for codec implementations.
17/// These traits allow implementations to specify their own type bounds.
18///
19/// # Implementation Note
20///
21/// To implement `Encoder` or `Decoder`, you need to:
22/// 1. Implement the `Encoder` or `Decoder` trait (empty impl is fine)
23/// 2. Implement `sealed::EncodeValue<T>` or `sealed::DecodeValue<T>` with your desired bounds
24pub mod sealed {
25    use super::{BoxError, Bytes};
26
27    /// Helper trait for encoding with custom bounds.
28    pub trait EncodeValue<T>: Send + Sync + 'static {
29        /// Encode the value into bytes.
30        ///
31        /// # Errors
32        ///
33        /// Returns an error if serialization fails.
34        fn encode_value(&self, value: &T) -> Result<Bytes, BoxError>;
35    }
36
37    /// Helper trait for decoding with custom bounds.
38    pub trait DecodeValue<T>: Send + Sync + 'static {
39        /// Decode a value from bytes.
40        ///
41        /// # Errors
42        ///
43        /// Returns an error if deserialization fails.
44        fn decode_value(&self, bytes: Bytes) -> Result<T, BoxError>;
45    }
46}
47
48/// An encoder that can serialize a value into a byte stream.
49pub trait Encoder: Send + Sync + 'static {
50    /// Encode a value into bytes.
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if serialization fails.
55    fn encode<T>(&self, value: &T) -> Result<Bytes, BoxError>
56    where
57        Self: sealed::EncodeValue<T>,
58    {
59        sealed::EncodeValue::encode_value(self, value)
60    }
61}
62
63/// A decoder that can deserialize a value from a byte stream.
64pub trait Decoder: Send + Sync + 'static {
65    /// Decode a value from bytes.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if deserialization fails.
70    fn decode<T>(&self, bytes: Bytes) -> Result<T, BoxError>
71    where
72        Self: sealed::DecodeValue<T>,
73    {
74        sealed::DecodeValue::decode_value(self, bytes)
75    }
76}
77
78/// A codec that can serialize and deserialize a value.
79pub trait Codec: Encoder + Decoder {}
80
81/// Blanket impl `Codec` for any type that implements Encoder and Decoder.
82impl<U> Codec for U where U: Encoder + Decoder {}
83
84/// Blanket implementations for `Arc<C>` to allow passing Arc-wrapped codecs.
85impl<C, T> sealed::EncodeValue<T> for std::sync::Arc<C>
86where
87    C: sealed::EncodeValue<T>,
88{
89    fn encode_value(&self, value: &T) -> Result<Bytes, BoxError> {
90        (**self).encode_value(value)
91    }
92}
93
94impl<C, T> sealed::DecodeValue<T> for std::sync::Arc<C>
95where
96    C: sealed::DecodeValue<T>,
97{
98    fn decode_value(&self, bytes: Bytes) -> Result<T, BoxError> {
99        (**self).decode_value(bytes)
100    }
101}
102
103impl<C> Encoder for std::sync::Arc<C> where C: Encoder {}
104
105impl<C> Decoder for std::sync::Arc<C> where C: Decoder {}
106
107/// Object-safe trait for branch envelope operations in the execution layer.
108///
109/// [`Codec`] is generic over `T`, which makes it non-object-safe — callers must
110/// know the concrete type at compile time. The execution layer, however, is
111/// type-erased: it shuffles opaque `Bytes` between tasks without knowing their
112/// Rust types, so it cannot call `Codec` directly.
113///
114/// `EnvelopeCodec` bridges this gap by exposing **byte-level** operations that
115/// the runtime executor can call through a trait object (`dyn EnvelopeCodec`).
116/// It abstracts the serialization format used for:
117/// - Deserializing routing keys from branch key functions
118/// - Constructing discriminated `{"branch": key, "result": value}` envelopes
119/// - Serializing named fork/join results
120///
121/// By default, `JsonCodec` implements this with `serde_json`. Other codecs
122/// (e.g. `RkyvCodec`) can return clear errors if envelope operations are
123/// unsupported.
124pub trait EnvelopeCodec: Send + Sync {
125    /// Decode a routing key (String) from bytes produced by a branch key function.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if the bytes cannot be decoded as a string.
130    fn decode_string(&self, bytes: &[u8]) -> Result<String, BoxError>;
131
132    /// Encode a branch envelope containing the routing key and result bytes.
133    ///
134    /// Produces a discriminated envelope (e.g. `{"branch": key, "result": value}`)
135    /// so downstream tasks know which branch produced the result.
136    ///
137    /// # Errors
138    ///
139    /// Returns an error if envelope construction or serialization fails.
140    fn encode_branch_envelope(&self, key: &str, result_bytes: &[u8]) -> Result<Bytes, BoxError>;
141
142    /// Serialize named branch results for a fork/join.
143    ///
144    /// Encodes a `Vec<(String, Bytes)>` of branch results into a single `Bytes`
145    /// value suitable for passing to a join task.
146    ///
147    /// # Errors
148    ///
149    /// Returns an error if serialization fails.
150    fn encode_named_results(&self, results: &[(String, Bytes)]) -> Result<Bytes, BoxError>;
151}
152
153/// Blanket implementation for `&C` to allow passing references generically.
154impl<C: EnvelopeCodec> EnvelopeCodec for &C {
155    fn decode_string(&self, bytes: &[u8]) -> Result<String, BoxError> {
156        (**self).decode_string(bytes)
157    }
158
159    fn encode_branch_envelope(&self, key: &str, result_bytes: &[u8]) -> Result<Bytes, BoxError> {
160        (**self).encode_branch_envelope(key, result_bytes)
161    }
162
163    fn encode_named_results(&self, results: &[(String, Bytes)]) -> Result<Bytes, BoxError> {
164        (**self).encode_named_results(results)
165    }
166}
167
168/// Blanket implementation for `Arc<C>` to allow passing Arc-wrapped envelope codecs.
169impl<C: EnvelopeCodec> EnvelopeCodec for std::sync::Arc<C> {
170    fn decode_string(&self, bytes: &[u8]) -> Result<String, BoxError> {
171        (**self).decode_string(bytes)
172    }
173
174    fn encode_branch_envelope(&self, key: &str, result_bytes: &[u8]) -> Result<Bytes, BoxError> {
175        (**self).encode_branch_envelope(key, result_bytes)
176    }
177
178    fn encode_named_results(&self, results: &[(String, Bytes)]) -> Result<Bytes, BoxError> {
179        (**self).encode_named_results(results)
180    }
181}