Skip to main content

idemio/
exchange.rs

1
2use fnv::FnvHasher;
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::hash::{Hash, Hasher};
6use thiserror::Error;
7use uuid::Uuid;
8
9/// A generic exchange container that manages input/output data flow with metadata and attachments.
10///
11/// # Type Parameters
12/// - `I`: Input data type that must implement `Send + Sync`
13/// - `O`: Output data type that must implement `Send + Sync`  
14/// - `M`: Metadata type that must implement `Send + Sync`
15///
16/// # Behavior
17/// Provides a structured way to handle data exchange with support for:
18/// - Input/output data management with optional buffering
19/// - Metadata attachment for contextual information
20/// - Listener callbacks for data processing
21/// - Type-safe attachments collection
22/// - Unique identification via UUID
23pub struct Exchange<I, O, M>
24where
25    I: Send + Sync,
26    O: Send + Sync,
27    M: Send + Sync,
28{
29    uuid: Uuid,
30    metadata: Option<M>,
31    input: Option<I>,
32    output: Option<O>,
33    input_listeners: Vec<Callback<I>>,
34    output_listeners: Vec<Callback<O>>,
35    attachments: Attachments,
36}
37
38impl<I, O, M> Exchange<I, O, M>
39where
40    I: Send + Sync,
41    O: Send + Sync,
42    M: Send + Sync,
43{
44    /// Creates a new exchange instance with a randomly generated UUID.
45    ///
46    /// # Returns
47    /// A new `Self` instance with empty input, output, metadata, and listeners,
48    /// plus a new attachments collection and auto-generated UUID.
49    ///
50    /// # Examples
51    /// ```rust
52    /// use idemio::exchange::Exchange;
53    /// use hyper::body::Bytes;
54    ///
55    /// let exchange: Exchange<Bytes, Bytes, String> = Exchange::new();
56    /// ```
57    ///
58    /// # Behavior
59    /// Initializes all optional fields to `None` and creates empty listener vectors.
60    /// Each exchange gets a unique V4 UUID for identification.
61    pub fn new() -> Self {
62        Self {
63            uuid: Uuid::new_v4(),
64            metadata: None,
65            input: None,
66            output: None,
67            input_listeners: Vec::new(),
68            output_listeners: Vec::new(),
69            attachments: Attachments::new(),
70        }
71    }
72
73    /// Creates a new exchange instance with a specific UUID.
74    ///
75    /// # Parameters
76    /// - `uuid`: The UUID to assign to this exchange instance
77    ///
78    /// # Returns
79    /// A new `Self` instance with the provided UUID and empty data fields.
80    ///
81    /// # Examples
82    /// ```rust
83    /// use idemio::exchange::Exchange;
84    /// use uuid::Uuid;
85    /// use hyper::body::Bytes;
86    ///
87    /// let uuid = Uuid::new_v4();
88    /// let exchange: Exchange<Bytes, Bytes, String> = Exchange::new_with_uuid(uuid);
89    /// ```
90    pub fn new_with_uuid(uuid: Uuid) -> Self {
91        Self {
92            uuid,
93            metadata: None,
94            input: None,
95            output: None,
96            input_listeners: Vec::new(),
97            output_listeners: Vec::new(),
98            attachments: Attachments::new(),
99        }
100    }
101
102    /// Sets the metadata for this exchange.
103    ///
104    /// # Parameters
105    /// - `metadata`: The metadata value of type `M` to store
106    pub fn set_metadata(&mut self, metadata: M) {
107        self.metadata = Some(metadata);
108    }
109
110    /// Retrieves a reference to the stored metadata.
111    ///
112    /// # Returns
113    /// `Result<&M, ExchangeError>` where:
114    /// - `Ok(&M)` contains a reference to the metadata
115    /// - `Err(ExchangeError)` if no metadata has been set
116    ///
117    /// # Errors
118    /// Returns `ExchangeError::Read` if metadata has not been set.
119    pub fn metadata(&self) -> Result<&M, ExchangeError> {
120        match &self.metadata {
121            None => Err(ExchangeError::read_error(
122                &self.uuid,
123                "Metadata has not been set",
124            )),
125            Some(metadata) => Ok(metadata),
126        }
127    }
128
129    /// Returns a reference to the attachments' collection.
130    pub fn attachments(&self) -> &Attachments {
131        &self.attachments
132    }
133
134    /// Returns a mutable reference to the attachments' collection.
135    pub fn attachments_mut(&mut self) -> &mut Attachments {
136        &mut self.attachments
137    }
138
139    /// Adds a callback listener for input processing.
140    ///
141    /// # Parameters
142    /// - `callback`: A closure that takes `&mut I` and `&mut Attachments` and implements
143    ///   `FnMut + Send + Sync + 'static`
144    pub fn add_input_listener(
145        &mut self,
146        callback: impl FnMut(&mut I, &mut Attachments) + Send + Sync + 'static,
147    ) {
148        self.input_listeners.push(Callback::new(callback));
149    }
150
151    /// Adds a callback listener for output processing.
152    ///
153    /// # Parameters
154    /// - `callback`: A closure that takes `&mut O` and `&mut Attachments` and implements
155    ///   `FnMut + Send + Sync + 'static`
156    pub fn add_output_listener(
157        &mut self,
158        callback: impl FnMut(&mut O, &mut Attachments) + Send + Sync + 'static,
159    ) {
160        self.output_listeners.push(Callback::new(callback));
161    }
162
163    /// Returns a reference to the exchange's unique identifier.
164    pub fn uuid(&self) -> &Uuid {
165        &self.uuid
166    }
167
168    /// Sets the input data for this exchange.
169    pub fn set_input(&mut self, input: I) {
170        self.input = Some(input);
171    }
172
173    /// Retrieves a reference to the stored input data.
174    pub async fn input(&self) -> Result<&I, ExchangeError> {
175        match &self.input {
176            Some(val) => Ok(val),
177            None => Err(ExchangeError::read_error(&self.uuid, "No input available")),
178        }
179    }
180
181    /// Retrieves mutable reference to the stored input data.
182    pub async fn input_mut(&mut self) -> Result<&mut I, ExchangeError> {
183        match &mut self.input {
184            Some(val) => Ok(val),
185            None => Err(ExchangeError::read_error(&self.uuid, "No input available"))
186        }
187    }
188
189    /// Consumes and returns the stored input data, executing all input listeners.
190    pub async fn take_input(&mut self) -> Result<I, ExchangeError> {
191        match self.input.take() {
192            Some(mut val) => {
193                for mut callback in &mut self.input_listeners.drain(..) {
194                    callback.invoke(&mut val, &mut self.attachments);
195                }
196                Ok(val)
197            }
198            None => Err(ExchangeError::take_error(
199                &self.uuid,
200                "No input available to take",
201            )),
202        }
203    }
204
205    /// Sets the output data for this exchange.
206    pub fn set_output(&mut self, output: O) {
207        self.output = Some(output);
208    }
209
210    /// Retrieves a reference to the stored output data.
211    pub async fn output(&self) -> Result<&O, ExchangeError> {
212        match &self.output {
213            Some(val) => Ok(val),
214            None => Err(ExchangeError::read_error(&self.uuid, "No output available")),
215        }
216    }
217
218    /// Retrieves mutable reference to the stored output data.
219    pub async fn output_mut(&mut self) -> Result<&mut O, ExchangeError> {
220        match &mut self.output {
221            Some(val) => Ok(val),
222            None => Err(ExchangeError::read_error(&self.uuid, "No output available")),
223        }
224    }
225
226    /// Consumes and returns the stored output data, executing all output listeners.
227    pub async fn take_output(&mut self) -> Result<O, ExchangeError> {
228        match self.output.take() {
229            Some(mut val) => {
230                // Execute callbacks
231                for mut callback in &mut self.output_listeners.drain(..) {
232                    callback.invoke(&mut val, &mut self.attachments);
233                }
234                Ok(val)
235            }
236            None => Err(ExchangeError::take_error(
237                &self.uuid,
238                "No output available to take",
239            )),
240        }
241    }
242
243    /// Checks if input data is currently available.
244    pub fn has_input(&self) -> bool {
245        self.input.is_some()
246    }
247
248    /// Checks if output data is currently available.
249    pub fn has_output(&self) -> bool {
250        self.output.is_some()
251    }
252}
253
254pub struct Attachments {
255    attachments: HashMap<AttachmentKey, Box<dyn Any + Send + Sync>, fnv::FnvBuildHasher>,
256}
257
258impl Attachments {
259    /// Creates a new empty attachments' collection.
260    pub fn new() -> Self {
261        Self {
262            attachments: HashMap::with_hasher(fnv::FnvBuildHasher::default()),
263        }
264    }
265
266    /// Adds a typed value to the attachment collection.
267    ///
268    /// # Parameters
269    /// - `key`: A string-like key that can be converted via `AsRef<str>`
270    /// - `value`: A value of type `K` that implements `Send + Sync + 'static`
271    ///
272    /// # Examples
273    /// ```rust
274    /// use idemio::exchange::Attachments;
275    ///
276    /// let mut attachments = Attachments::new();
277    /// attachments.add::<u32>("user_id", 123u32);
278    /// attachments.add::<String>("username", "alice".to_string());
279    /// ```
280    pub fn add<K>(&mut self, key: impl AsRef<str>, value: K)
281    where
282        K: Send + Sync + 'static,
283    {
284        let type_id = TypeId::of::<K>();
285        self.attachments
286            .insert(AttachmentKey::new(key, type_id), Box::new(value));
287    }
288
289    /// Retrieves a reference to a typed value.
290    ///
291    /// # Examples
292    /// ```rust
293    /// use idemio::exchange::Attachments;
294    ///
295    /// let mut attachments = Attachments::new();
296    /// attachments.add::<u32>("user_id", 123u32);
297    ///
298    /// let user_id: Option<&u32> = attachments.get("user_id");
299    /// assert_eq!(user_id, Some(&123));
300    /// ```
301    pub fn get<K>(&self, key: impl AsRef<str>) -> Option<&K>
302    where
303        K: Send + 'static,
304    {
305        let type_id = TypeId::of::<K>();
306        if let Some(option_any) = self.attachments.get(&AttachmentKey::new(key, type_id)) {
307            option_any.downcast_ref::<K>()
308        } else {
309            None
310        }
311    }
312
313    /// Retrieves a mutable reference to a typed value.
314    pub fn get_mut<K>(&mut self, key: impl AsRef<str>) -> Option<&mut K>
315    where
316        K: Send + 'static,
317    {
318        let type_id = TypeId::of::<K>();
319        if let Some(option_any) = self.attachments.get_mut(&AttachmentKey::new(key, type_id)) {
320            option_any.downcast_mut::<K>()
321        } else {
322            None
323        }
324    }
325}
326
327#[derive(Error, Debug)]
328pub enum ExchangeError {
329    #[error("Exchange '{uuid}' has already been completed")]
330    Completed { uuid: Uuid },
331
332    #[error("Read error occurred for exchange '{uuid}'. {message}")]
333    Read { uuid: Uuid, message: String },
334
335    #[error("Take error occurred for exchange '{uuid}'. {message}")]
336    Take { uuid: Uuid, message: String },
337
338    #[error("Callback error occurred for exchange '{uuid}'. {message}")]
339    Callback { uuid: Uuid, message: String },
340}
341
342impl ExchangeError {
343    #[inline]
344    pub const fn exchange_completed(uuid: &Uuid) -> Self {
345        ExchangeError::Completed { uuid: *uuid }
346    }
347
348    #[inline]
349    pub fn read_error(uuid: &Uuid, msg: impl Into<String>) -> Self {
350        ExchangeError::Read {
351            uuid: *uuid,
352            message: msg.into(),
353        }
354    }
355
356    #[inline]
357    pub(crate) fn take_error(uuid: &Uuid, msg: impl Into<String>) -> Self {
358        ExchangeError::Take {
359            uuid: *uuid,
360            message: msg.into(),
361        }
362    }
363
364    #[inline]
365    pub(crate) fn callback_error(uuid: &Uuid, msg: impl Into<String>) -> Self {
366        ExchangeError::Callback {
367            uuid: *uuid,
368            message: msg.into(),
369        }
370    }
371}
372
373#[derive(PartialOrd, PartialEq, Hash, Eq)]
374pub struct AttachmentKey {
375    key_hash: u64,
376    type_hash: u64,
377}
378
379impl AttachmentKey {
380    pub fn new(key: impl AsRef<str>, type_id: TypeId) -> Self {
381        let key = key.as_ref();
382        let mut key_hasher = FnvHasher::default();
383        key.hash(&mut key_hasher);
384        let key_hash = key_hasher.finish();
385
386        let mut type_hasher = FnvHasher::default();
387        type_id.hash(&mut type_hasher);
388        let type_hash = type_hasher.finish();
389
390        Self {
391            key_hash,
392            type_hash,
393        }
394    }
395}
396
397pub struct Callback<T> {
398    callback: Box<dyn FnMut(&mut T, &mut Attachments) + Send + Sync>,
399}
400
401impl<T> Callback<T>
402where
403    T: Send,
404{
405    pub fn new(callback: impl FnMut(&mut T, &mut Attachments) + Send + Sync + 'static) -> Self {
406        Self {
407            callback: Box::new(callback),
408        }
409    }
410
411    pub fn invoke(&mut self, write: &mut T, attachments: &mut Attachments) {
412        (self.callback)(write, attachments);
413    }
414}
415
416#[cfg(test)]
417mod test {
418    use super::*;
419    use crate::exchange::Attachments;
420    use hyper::body::Bytes;
421
422    struct TestStruct;
423
424    #[test]
425    fn test_attachments() {
426        let mut attachments = Attachments::new();
427        let key1 = "test_key1";
428        let key2 = "test_key2";
429        let key3 = "test_key3";
430        let key4 = "test_key4";
431        {
432            attachments.add::<u64>(key1, 1);
433            attachments.add::<String>(key2, String::from("test"));
434            attachments.add::<bool>(key3, true);
435            let test_struct = TestStruct;
436            attachments.add::<TestStruct>(key4, test_struct);
437        }
438
439        {
440            assert!(attachments.get::<u64>(key1).is_some());
441            assert!(attachments.get::<String>(key2).is_some());
442            assert!(attachments.get::<bool>(key3).is_some());
443            assert!(attachments.get::<TestStruct>(key4).is_some());
444        }
445    }
446
447    #[tokio::test]
448    async fn test_unified_exchange_buffered() {
449        let mut exchange: Exchange<Bytes, Bytes, ()> = Exchange::new();
450
451        // Test buffered input
452        exchange.set_input(Bytes::from("test input"));
453        assert!(exchange.has_input());
454
455        // Test buffered output
456        exchange.set_output(Bytes::from("test output"));
457        assert!(exchange.has_output());
458
459        let output = exchange.take_output().await.unwrap();
460        assert_eq!(output, Bytes::from("test output"));
461    }
462
463    #[tokio::test]
464    async fn test_unified_exchange_callbacks() {
465        let mut exchange: Exchange<Bytes, Bytes, ()> = Exchange::new();
466
467        exchange.add_input_listener(|input: &mut Bytes, _| {
468            let mut new_data = b"prefix:".to_vec();
469            new_data.extend_from_slice(input);
470            *input = Bytes::from(new_data);
471        });
472
473        exchange.set_input(Bytes::from("test"));
474        let input = exchange.take_input().await.unwrap();
475        assert_eq!(input, Bytes::from("prefix:test"));
476    }
477
478    #[tokio::test]
479    async fn test_unified_exchange_metadata() {
480        let mut exchange: Exchange<(), (), String> = Exchange::new();
481
482        exchange.set_metadata("test metadata".to_string());
483        let metadata = exchange.metadata().unwrap();
484        assert_eq!(metadata, "test metadata");
485    }
486}