ipfrs_core/
codec_registry.rs1use crate::{Error, Ipld, Result};
26use std::collections::HashMap;
27use std::sync::{Arc, RwLock};
28
29pub trait Codec: Send + Sync {
33 fn encode(&self, data: &Ipld) -> Result<Vec<u8>>;
35
36 fn decode(&self, bytes: &[u8]) -> Result<Ipld>;
38
39 fn code(&self) -> u64;
41
42 fn name(&self) -> &str;
44}
45
46#[derive(Debug, Clone, Default)]
48pub struct DagCborCodec;
49
50impl Codec for DagCborCodec {
51 fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
52 data.to_dag_cbor()
53 }
54
55 fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
56 Ipld::from_dag_cbor(bytes)
57 }
58
59 fn code(&self) -> u64 {
60 crate::cid::codec::DAG_CBOR
61 }
62
63 fn name(&self) -> &str {
64 "dag-cbor"
65 }
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct DagJsonCodec;
71
72impl Codec for DagJsonCodec {
73 fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
74 let json_str = data.to_dag_json()?;
75 Ok(json_str.into_bytes())
76 }
77
78 fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
79 let json_str = std::str::from_utf8(bytes)
80 .map_err(|e| Error::Deserialization(format!("Invalid UTF-8: {}", e)))?;
81 Ipld::from_dag_json(json_str)
82 }
83
84 fn code(&self) -> u64 {
85 crate::cid::codec::DAG_JSON
86 }
87
88 fn name(&self) -> &str {
89 "dag-json"
90 }
91}
92
93#[derive(Debug, Clone, Default)]
95pub struct RawCodec;
96
97impl Codec for RawCodec {
98 fn encode(&self, data: &Ipld) -> Result<Vec<u8>> {
99 match data {
100 Ipld::Bytes(bytes) => Ok(bytes.clone()),
101 _ => Err(Error::Serialization(
102 "RAW codec requires Ipld::Bytes".to_string(),
103 )),
104 }
105 }
106
107 fn decode(&self, bytes: &[u8]) -> Result<Ipld> {
108 Ok(Ipld::Bytes(bytes.to_vec()))
109 }
110
111 fn code(&self) -> u64 {
112 crate::cid::codec::RAW
113 }
114
115 fn name(&self) -> &str {
116 "raw"
117 }
118}
119
120#[derive(Clone)]
125pub struct CodecRegistry {
126 codecs: Arc<RwLock<HashMap<u64, Arc<dyn Codec>>>>,
127}
128
129impl Default for CodecRegistry {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl CodecRegistry {
136 pub fn new() -> Self {
143 let registry = Self {
144 codecs: Arc::new(RwLock::new(HashMap::new())),
145 };
146
147 registry.register(Arc::new(RawCodec));
149 registry.register(Arc::new(DagCborCodec));
150 registry.register(Arc::new(DagJsonCodec));
151
152 registry
153 }
154
155 pub fn register(&self, codec: Arc<dyn Codec>) {
159 let code = codec.code();
160 let mut codecs = self.codecs.write().unwrap();
161 codecs.insert(code, codec);
162 }
163
164 pub fn get(&self, code: u64) -> Option<Arc<dyn Codec>> {
166 let codecs = self.codecs.read().unwrap();
167 codecs.get(&code).cloned()
168 }
169
170 pub fn has_codec(&self, code: u64) -> bool {
172 let codecs = self.codecs.read().unwrap();
173 codecs.contains_key(&code)
174 }
175
176 pub fn list_codecs(&self) -> Vec<u64> {
178 let codecs = self.codecs.read().unwrap();
179 codecs.keys().copied().collect()
180 }
181
182 pub fn get_name(&self, code: u64) -> Option<String> {
184 self.get(code).map(|c| c.name().to_string())
185 }
186
187 pub fn encode(&self, codec_code: u64, data: &Ipld) -> Result<Vec<u8>> {
189 let codec = self.get(codec_code).ok_or_else(|| {
190 Error::Serialization(format!("Codec 0x{:x} not registered", codec_code))
191 })?;
192 codec.encode(data)
193 }
194
195 pub fn decode(&self, codec_code: u64, bytes: &[u8]) -> Result<Ipld> {
197 let codec = self.get(codec_code).ok_or_else(|| {
198 Error::Deserialization(format!("Codec 0x{:x} not registered", codec_code))
199 })?;
200 codec.decode(bytes)
201 }
202}
203
204static GLOBAL_CODEC_REGISTRY: std::sync::OnceLock<CodecRegistry> = std::sync::OnceLock::new();
206
207pub fn global_codec_registry() -> &'static CodecRegistry {
221 GLOBAL_CODEC_REGISTRY.get_or_init(CodecRegistry::new)
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use crate::cid::codec;
228 use std::collections::BTreeMap;
229
230 #[test]
231 fn test_raw_codec() {
232 let codec = RawCodec;
233 let data = Ipld::Bytes(b"hello".to_vec());
234
235 let encoded = codec.encode(&data).unwrap();
236 assert_eq!(encoded, b"hello");
237
238 let decoded = codec.decode(&encoded).unwrap();
239 assert_eq!(decoded, data);
240 }
241
242 #[test]
243 fn test_raw_codec_requires_bytes() {
244 let codec = RawCodec;
245 let data = Ipld::String("hello".to_string());
246
247 assert!(codec.encode(&data).is_err());
248 }
249
250 #[test]
251 fn test_dag_cbor_codec() {
252 let codec = DagCborCodec;
253 let mut map = BTreeMap::new();
254 map.insert("key".to_string(), Ipld::Integer(42));
255 let data = Ipld::Map(map);
256
257 let encoded = codec.encode(&data).unwrap();
258 let decoded = codec.decode(&encoded).unwrap();
259 assert_eq!(decoded, data);
260 }
261
262 #[test]
263 fn test_dag_json_codec() {
264 let codec = DagJsonCodec;
265 let data = Ipld::String("hello".to_string());
266
267 let encoded = codec.encode(&data).unwrap();
268 let decoded = codec.decode(&encoded).unwrap();
269 assert_eq!(decoded, data);
270 }
271
272 #[test]
273 fn test_codec_registry() {
274 let registry = CodecRegistry::new();
275
276 assert!(registry.has_codec(codec::RAW));
278 assert!(registry.has_codec(codec::DAG_CBOR));
279 assert!(registry.has_codec(codec::DAG_JSON));
280
281 let cbor_codec = registry.get(codec::DAG_CBOR).unwrap();
283 assert_eq!(cbor_codec.code(), codec::DAG_CBOR);
284 assert_eq!(cbor_codec.name(), "dag-cbor");
285 }
286
287 #[test]
288 fn test_registry_encode_decode() {
289 let registry = CodecRegistry::new();
290 let data = Ipld::Integer(12345);
291
292 let encoded = registry.encode(codec::DAG_CBOR, &data).unwrap();
294
295 let decoded = registry.decode(codec::DAG_CBOR, &encoded).unwrap();
297 assert_eq!(decoded, data);
298 }
299
300 #[test]
301 fn test_registry_list_codecs() {
302 let registry = CodecRegistry::new();
303 let codecs = registry.list_codecs();
304
305 assert!(codecs.contains(&codec::RAW));
306 assert!(codecs.contains(&codec::DAG_CBOR));
307 assert!(codecs.contains(&codec::DAG_JSON));
308 assert_eq!(codecs.len(), 3);
309 }
310
311 #[test]
312 fn test_registry_get_name() {
313 let registry = CodecRegistry::new();
314
315 assert_eq!(registry.get_name(codec::RAW).unwrap(), "raw");
316 assert_eq!(registry.get_name(codec::DAG_CBOR).unwrap(), "dag-cbor");
317 assert_eq!(registry.get_name(codec::DAG_JSON).unwrap(), "dag-json");
318 assert!(registry.get_name(0x9999).is_none());
319 }
320
321 #[test]
322 fn test_unregistered_codec_error() {
323 let registry = CodecRegistry::new();
324 let data = Ipld::Integer(42);
325
326 let result = registry.encode(0x9999, &data);
327 assert!(result.is_err());
328 assert!(result.unwrap_err().to_string().contains("not registered"));
329 }
330
331 #[test]
332 fn test_global_registry() {
333 let registry = global_codec_registry();
334
335 assert!(registry.has_codec(codec::RAW));
336 assert!(registry.has_codec(codec::DAG_CBOR));
337 assert!(registry.has_codec(codec::DAG_JSON));
338 }
339
340 #[test]
341 fn test_codec_replacement() {
342 let registry = CodecRegistry::new();
343
344 registry.register(Arc::new(RawCodec));
346
347 assert!(registry.has_codec(codec::RAW));
349 }
350
351 #[test]
352 fn test_codec_codes() {
353 assert_eq!(RawCodec.code(), codec::RAW);
354 assert_eq!(DagCborCodec.code(), codec::DAG_CBOR);
355 assert_eq!(DagJsonCodec.code(), codec::DAG_JSON);
356 }
357
358 #[test]
359 fn test_codec_names() {
360 assert_eq!(RawCodec.name(), "raw");
361 assert_eq!(DagCborCodec.name(), "dag-cbor");
362 assert_eq!(DagJsonCodec.name(), "dag-json");
363 }
364}