ferriclink_core/
serializable.rs

1//! Serializable trait for FerricLink objects
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::errors::{FerricLinkError, Result};
7
8/// Trait for objects that can be serialized and deserialized
9///
10/// This trait provides a standardized way to serialize FerricLink objects
11/// to and from various formats, similar to LangChain's Serializable interface.
12pub trait Serializable: Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static {
13    /// Get the namespace for this serializable object
14    ///
15    /// The namespace is used to identify the type of object when deserializing.
16    /// It should be a hierarchical path like ["ferriclink", "messages", "human"].
17    fn namespace() -> Vec<String>;
18
19    /// Check if this object is serializable
20    ///
21    /// By default, all objects implementing this trait are serializable.
22    /// Override this method to provide custom serialization logic.
23    fn is_serializable() -> bool {
24        true
25    }
26
27    /// Serialize this object to JSON
28    fn to_json(&self) -> Result<String> {
29        serde_json::to_string(self).map_err(FerricLinkError::from)
30    }
31
32    /// Serialize this object to a pretty-printed JSON string
33    fn to_json_pretty(&self) -> Result<String> {
34        serde_json::to_string_pretty(self).map_err(FerricLinkError::from)
35    }
36
37    /// Deserialize this object from JSON
38    fn from_json(json: &str) -> Result<Self>
39    where
40        Self: Sized,
41    {
42        serde_json::from_str(json).map_err(FerricLinkError::from)
43    }
44
45    /// Serialize this object to a dictionary (HashMap)
46    fn to_dict(&self) -> Result<HashMap<String, serde_json::Value>> {
47        let json = self.to_json()?;
48        serde_json::from_str(&json).map_err(FerricLinkError::from)
49    }
50
51    /// Deserialize this object from a dictionary (HashMap)
52    fn from_dict(dict: &HashMap<String, serde_json::Value>) -> Result<Self>
53    where
54        Self: Sized,
55    {
56        let json = serde_json::to_string(dict)?;
57        Self::from_json(&json)
58    }
59
60    /// Get the type name for this serializable object
61    ///
62    /// This is used for type identification during serialization/deserialization.
63    fn type_name() -> &'static str {
64        std::any::type_name::<Self>()
65    }
66}
67
68/// Helper macro to implement Serializable for a type
69///
70/// This macro provides a default implementation of the Serializable trait
71/// for types that derive Serialize and Deserialize.
72#[macro_export]
73macro_rules! impl_serializable {
74    ($type:ty, $namespace:expr) => {
75        impl $crate::serializable::Serializable for $type {
76            fn namespace() -> Vec<String> {
77                $namespace.into_iter().map(|s| s.to_string()).collect()
78            }
79        }
80    };
81}
82
83/// Trait for objects that can be loaded from serialized data
84pub trait Loadable: Serializable {
85    /// Load this object from serialized data
86    fn load(data: &[u8]) -> Result<Self>
87    where
88        Self: Sized,
89    {
90        let json = String::from_utf8(data.to_vec())
91            .map_err(|e| FerricLinkError::generic(format!("Invalid UTF-8: {e}")))?;
92        Self::from_json(&json)
93    }
94
95    /// Save this object to serialized data
96    fn save(&self) -> Result<Vec<u8>> {
97        let json = self.to_json()?;
98        Ok(json.into_bytes())
99    }
100}
101
102/// Helper macro to implement Loadable for a type
103#[macro_export]
104macro_rules! impl_loadable {
105    ($type:ty) => {
106        impl $crate::serializable::Loadable for $type {}
107    };
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use serde::{Deserialize, Serialize};
114
115    #[derive(Serialize, Deserialize, Debug, PartialEq)]
116    struct TestMessage {
117        content: String,
118        message_type: String,
119    }
120
121    impl_serializable!(TestMessage, ["ferriclink", "messages", "test"]);
122    impl_loadable!(TestMessage);
123
124    #[test]
125    fn test_serialization() {
126        let msg = TestMessage {
127            content: "Hello, world!".to_string(),
128            message_type: "test".to_string(),
129        };
130
131        // Test JSON serialization
132        let json = msg.to_json().unwrap();
133        let deserialized: TestMessage = TestMessage::from_json(&json).unwrap();
134        assert_eq!(msg, deserialized);
135
136        // Test pretty JSON
137        let pretty_json = msg.to_json_pretty().unwrap();
138        assert!(pretty_json.contains("Hello, world!"));
139
140        // Test dictionary serialization
141        let dict = msg.to_dict().unwrap();
142        let from_dict: TestMessage = TestMessage::from_dict(&dict).unwrap();
143        assert_eq!(msg, from_dict);
144    }
145
146    #[test]
147    fn test_namespace() {
148        let namespace = TestMessage::namespace();
149        assert_eq!(namespace, vec!["ferriclink", "messages", "test"]);
150    }
151
152    #[test]
153    fn test_type_name() {
154        let type_name = TestMessage::type_name();
155        assert!(type_name.contains("TestMessage"));
156    }
157
158    #[test]
159    fn test_loadable() {
160        let msg = TestMessage {
161            content: "Test content".to_string(),
162            message_type: "test".to_string(),
163        };
164
165        let data = msg.save().unwrap();
166        let loaded: TestMessage = TestMessage::load(&data).unwrap();
167        assert_eq!(msg, loaded);
168    }
169}