aura_core/util/
serialization.rs1use crate::crypto::hash;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16#[derive(Debug, thiserror::Error)]
18pub enum SerializationError {
19 #[error("DAG-CBOR error: {0}")]
21 DagCbor(String),
22
23 #[error("Invalid format: {0}")]
25 InvalidFormat(String),
26}
27
28pub type Result<T> = std::result::Result<T, SerializationError>;
30
31pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>> {
33 serde_ipld_dagcbor::to_vec(value).map_err(|e| {
34 SerializationError::InvalidFormat(format!("Failed to serialize to DAG-CBOR: {e}"))
35 })
36}
37
38pub fn from_slice<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T> {
40 serde_ipld_dagcbor::from_slice(bytes).map_err(|e| SerializationError::DagCbor(e.to_string()))
41}
42
43pub fn hash_canonical<T: Serialize>(value: &T) -> Result<[u8; 32]> {
45 let bytes = to_vec(value)?;
46 Ok(hash::hash(&bytes))
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct SemanticVersion {
52 pub major: u16,
54 pub minor: u16,
56 pub patch: u16,
58}
59
60impl SemanticVersion {
61 pub fn new(major: u16, minor: u16, patch: u16) -> Self {
63 Self {
64 major,
65 minor,
66 patch,
67 }
68 }
69
70 pub fn is_compatible(&self, other: &Self) -> bool {
72 self.major == other.major
74 }
75
76 pub fn is_newer(&self, other: &Self) -> bool {
78 (self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
79 }
80}
81
82impl std::fmt::Display for SemanticVersion {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct VersionedMessage<T> {
91 pub version: SemanticVersion,
93 pub payload: T,
95 #[serde(skip_serializing_if = "HashMap::is_empty")]
97 pub metadata: HashMap<String, String>,
98}
99
100impl<T> VersionedMessage<T> {
101 pub fn new(payload: T, version: SemanticVersion) -> Self {
103 Self {
104 version,
105 payload,
106 metadata: HashMap::new(),
107 }
108 }
109
110 #[must_use]
112 pub fn with_metadata(mut self, key: String, value: String) -> Self {
113 self.metadata.insert(key, value);
114 self
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use serde::{Deserialize, Serialize};
122
123 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124 struct TestData {
125 id: u64,
126 name: String,
127 tags: Vec<String>,
128 }
129
130 #[test]
131 fn test_dag_cbor_roundtrip() {
132 let data = TestData {
133 id: 42,
134 name: "test".to_string(),
135 tags: vec!["tag1".to_string(), "tag2".to_string()],
136 };
137
138 let bytes = to_vec(&data).unwrap();
139 let decoded: TestData = from_slice(&bytes).unwrap();
140
141 assert_eq!(data, decoded);
142 }
143
144 #[test]
145 fn test_canonical_hash() {
146 let data1 = TestData {
147 id: 42,
148 name: "test".to_string(),
149 tags: vec!["tag1".to_string(), "tag2".to_string()],
150 };
151
152 let data2 = TestData {
153 id: 42,
154 name: "test".to_string(),
155 tags: vec!["tag1".to_string(), "tag2".to_string()],
156 };
157
158 let hash1 = hash_canonical(&data1).unwrap();
159 let hash2 = hash_canonical(&data2).unwrap();
160
161 assert_eq!(hash1, hash2);
162 }
163
164 #[test]
165 fn test_semantic_version() {
166 let v1 = SemanticVersion::new(1, 0, 0);
167 let v2 = SemanticVersion::new(1, 1, 0);
168 let v3 = SemanticVersion::new(2, 0, 0);
169
170 assert!(v1.is_compatible(&v2));
171 assert!(!v1.is_compatible(&v3));
172 assert!(v2.is_newer(&v1));
173 assert!(v3.is_newer(&v2));
174 }
175
176 #[test]
177 fn test_versioned_message() {
178 let data = TestData {
179 id: 42,
180 name: "test".to_string(),
181 tags: vec!["tag1".to_string(), "tag2".to_string()],
182 };
183
184 let version = SemanticVersion::new(1, 0, 0);
185 let message = VersionedMessage::new(data.clone(), version)
186 .with_metadata("source".to_string(), "test".to_string());
187
188 let bytes = to_vec(&message).unwrap();
189 let decoded: VersionedMessage<TestData> = from_slice(&bytes).unwrap();
190
191 assert_eq!(decoded.payload, data);
192 assert_eq!(decoded.version.major, 1);
193 assert_eq!(decoded.metadata.get("source").unwrap(), "test");
194 }
195}