ipfrs_core/
ipld.rs

1//! IPLD (InterPlanetary Linked Data) support
2//!
3//! This module provides IPLD data structure support for IPFRS with proper
4//! DAG-CBOR and DAG-JSON codec implementations.
5
6use crate::cid::Cid;
7use crate::error::{Error, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10
11/// CBOR tag for CID links in DAG-CBOR encoding (tag 42)
12const CID_TAG: u64 = 42;
13
14/// IPLD data model
15///
16/// Represents the core IPLD data types that can be stored and transferred
17/// across the IPFRS network.
18#[derive(Debug, Clone, PartialEq)]
19pub enum Ipld {
20    /// Null value
21    Null,
22    /// Boolean value
23    Bool(bool),
24    /// Integer value (supports full i128 range)
25    Integer(i128),
26    /// Float value (IEEE 754 double precision)
27    Float(f64),
28    /// String value (UTF-8)
29    String(String),
30    /// Bytes value (raw binary data)
31    Bytes(Vec<u8>),
32    /// List of IPLD values
33    List(Vec<Ipld>),
34    /// Map of string keys to IPLD values (keys are sorted)
35    Map(BTreeMap<String, Ipld>),
36    /// Link to another IPLD node via CID
37    Link(crate::cid::SerializableCid),
38}
39
40impl Ipld {
41    /// Create a link to a CID
42    pub fn link(cid: Cid) -> Self {
43        Ipld::Link(crate::cid::SerializableCid(cid))
44    }
45
46    /// Check if this is a link
47    pub fn is_link(&self) -> bool {
48        matches!(self, Ipld::Link(_))
49    }
50
51    /// Extract CID if this is a link
52    pub fn as_link(&self) -> Option<&Cid> {
53        match self {
54            Ipld::Link(cid) => Some(&cid.0),
55            _ => None,
56        }
57    }
58
59    /// Encode this IPLD value to DAG-CBOR format
60    ///
61    /// DAG-CBOR is a deterministic subset of CBOR with:
62    /// - Map keys sorted by byte ordering
63    /// - No indefinite-length items
64    /// - CID links encoded with tag 42
65    pub fn to_dag_cbor(&self) -> Result<Vec<u8>> {
66        let mut buffer = Vec::new();
67        encode_dag_cbor(self, &mut buffer)?;
68        Ok(buffer)
69    }
70
71    /// Decode IPLD value from DAG-CBOR format
72    pub fn from_dag_cbor(data: &[u8]) -> Result<Self> {
73        decode_dag_cbor(&mut &data[..])
74    }
75
76    /// Encode this IPLD value to DAG-JSON format
77    ///
78    /// DAG-JSON is a JSON encoding for IPLD with special handling for:
79    /// - Bytes (encoded as `{"/": {"bytes": "<base64>"}}`)
80    /// - Links (encoded as `{"/": "<cid-string>"}`)
81    pub fn to_dag_json(&self) -> Result<String> {
82        let json_value = ipld_to_dag_json(self)?;
83        serde_json::to_string_pretty(&json_value)
84            .map_err(|e| Error::Serialization(format!("Failed to serialize DAG-JSON: {}", e)))
85    }
86
87    /// Decode IPLD value from DAG-JSON format
88    pub fn from_dag_json(json: &str) -> Result<Self> {
89        let json_value: serde_json::Value = serde_json::from_str(json)
90            .map_err(|e| Error::Deserialization(format!("Failed to parse DAG-JSON: {}", e)))?;
91        dag_json_to_ipld(&json_value)
92    }
93
94    /// Encode this IPLD value to JSON format (simple, for debugging)
95    pub fn to_json(&self) -> Result<String> {
96        self.to_dag_json()
97    }
98
99    /// Decode IPLD value from JSON format
100    pub fn from_json(json: &str) -> Result<Self> {
101        Self::from_dag_json(json)
102    }
103
104    /// Get all CID links contained in this IPLD structure (recursively)
105    pub fn links(&self) -> Vec<Cid> {
106        let mut result = Vec::new();
107        self.collect_links(&mut result);
108        result
109    }
110
111    fn collect_links(&self, result: &mut Vec<Cid>) {
112        match self {
113            Ipld::Link(cid) => result.push(cid.0),
114            Ipld::List(list) => {
115                for item in list {
116                    item.collect_links(result);
117                }
118            }
119            Ipld::Map(map) => {
120                for value in map.values() {
121                    value.collect_links(result);
122                }
123            }
124            _ => {}
125        }
126    }
127
128    /// Check if this is a null value
129    #[inline]
130    pub const fn is_null(&self) -> bool {
131        matches!(self, Ipld::Null)
132    }
133
134    /// Check if this is a boolean value
135    #[inline]
136    pub const fn is_bool(&self) -> bool {
137        matches!(self, Ipld::Bool(_))
138    }
139
140    /// Check if this is an integer value
141    #[inline]
142    pub const fn is_integer(&self) -> bool {
143        matches!(self, Ipld::Integer(_))
144    }
145
146    /// Check if this is a float value
147    #[inline]
148    pub const fn is_float(&self) -> bool {
149        matches!(self, Ipld::Float(_))
150    }
151
152    /// Check if this is a string value
153    #[inline]
154    pub const fn is_string(&self) -> bool {
155        matches!(self, Ipld::String(_))
156    }
157
158    /// Check if this is a bytes value
159    #[inline]
160    pub const fn is_bytes(&self) -> bool {
161        matches!(self, Ipld::Bytes(_))
162    }
163
164    /// Check if this is a list value
165    #[inline]
166    pub const fn is_list(&self) -> bool {
167        matches!(self, Ipld::List(_))
168    }
169
170    /// Check if this is a map value
171    #[inline]
172    pub const fn is_map(&self) -> bool {
173        matches!(self, Ipld::Map(_))
174    }
175
176    /// Extract boolean value if this is a Bool
177    #[inline]
178    pub const fn as_bool(&self) -> Option<bool> {
179        match self {
180            Ipld::Bool(b) => Some(*b),
181            _ => None,
182        }
183    }
184
185    /// Extract integer value if this is an Integer
186    #[inline]
187    pub const fn as_integer(&self) -> Option<i128> {
188        match self {
189            Ipld::Integer(i) => Some(*i),
190            _ => None,
191        }
192    }
193
194    /// Extract float value if this is a Float
195    #[inline]
196    pub const fn as_float(&self) -> Option<f64> {
197        match self {
198            Ipld::Float(f) => Some(*f),
199            _ => None,
200        }
201    }
202
203    /// Extract string reference if this is a String
204    #[inline]
205    pub fn as_string(&self) -> Option<&str> {
206        match self {
207            Ipld::String(s) => Some(s.as_str()),
208            _ => None,
209        }
210    }
211
212    /// Extract bytes reference if this is Bytes
213    #[inline]
214    pub fn as_bytes(&self) -> Option<&[u8]> {
215        match self {
216            Ipld::Bytes(b) => Some(b.as_slice()),
217            _ => None,
218        }
219    }
220
221    /// Extract list reference if this is a List
222    #[inline]
223    pub fn as_list(&self) -> Option<&[Ipld]> {
224        match self {
225            Ipld::List(l) => Some(l.as_slice()),
226            _ => None,
227        }
228    }
229
230    /// Extract map reference if this is a Map
231    #[inline]
232    pub fn as_map(&self) -> Option<&BTreeMap<String, Ipld>> {
233        match self {
234            Ipld::Map(m) => Some(m),
235            _ => None,
236        }
237    }
238
239    /// Get a value from a map by key (if this is a Map)
240    #[inline]
241    pub fn get(&self, key: &str) -> Option<&Ipld> {
242        self.as_map()?.get(key)
243    }
244
245    /// Get a value from a list by index (if this is a List)
246    #[inline]
247    pub fn index(&self, idx: usize) -> Option<&Ipld> {
248        self.as_list()?.get(idx)
249    }
250
251    /// Get the size/length of this IPLD value
252    ///
253    /// - For List: number of elements
254    /// - For Map: number of key-value pairs
255    /// - For String: length in bytes
256    /// - For Bytes: length in bytes
257    /// - For other types: 0
258    pub fn len(&self) -> usize {
259        match self {
260            Ipld::List(l) => l.len(),
261            Ipld::Map(m) => m.len(),
262            Ipld::String(s) => s.len(),
263            Ipld::Bytes(b) => b.len(),
264            _ => 0,
265        }
266    }
267
268    /// Check if this IPLD value is empty
269    ///
270    /// - For List/Map/String/Bytes: checks if length is 0
271    /// - For Null: true
272    /// - For other types: false
273    pub fn is_empty(&self) -> bool {
274        match self {
275            Ipld::Null => true,
276            Ipld::List(l) => l.is_empty(),
277            Ipld::Map(m) => m.is_empty(),
278            Ipld::String(s) => s.is_empty(),
279            Ipld::Bytes(b) => b.is_empty(),
280            _ => false,
281        }
282    }
283
284    /// Get a human-readable type name for this IPLD value
285    pub const fn type_name(&self) -> &'static str {
286        match self {
287            Ipld::Null => "null",
288            Ipld::Bool(_) => "bool",
289            Ipld::Integer(_) => "integer",
290            Ipld::Float(_) => "float",
291            Ipld::String(_) => "string",
292            Ipld::Bytes(_) => "bytes",
293            Ipld::List(_) => "list",
294            Ipld::Map(_) => "map",
295            Ipld::Link(_) => "link",
296        }
297    }
298}
299
300// =============================================================================
301// DAG-CBOR Encoding
302// =============================================================================
303
304fn encode_dag_cbor(ipld: &Ipld, buffer: &mut Vec<u8>) -> Result<()> {
305    match ipld {
306        Ipld::Null => {
307            // CBOR simple value 22 (null)
308            buffer.push(0xf6);
309        }
310        Ipld::Bool(b) => {
311            // CBOR simple values 20 (false) and 21 (true)
312            buffer.push(if *b { 0xf5 } else { 0xf4 });
313        }
314        Ipld::Integer(i) => {
315            encode_cbor_integer(*i, buffer)?;
316        }
317        Ipld::Float(f) => {
318            // CBOR major type 7 with additional info 27 (64-bit float)
319            buffer.push(0xfb);
320            buffer.extend_from_slice(&f.to_be_bytes());
321        }
322        Ipld::String(s) => {
323            // CBOR major type 3 (text string)
324            encode_cbor_length(3, s.len() as u64, buffer);
325            buffer.extend_from_slice(s.as_bytes());
326        }
327        Ipld::Bytes(b) => {
328            // CBOR major type 2 (byte string)
329            encode_cbor_length(2, b.len() as u64, buffer);
330            buffer.extend_from_slice(b);
331        }
332        Ipld::List(list) => {
333            // CBOR major type 4 (array)
334            encode_cbor_length(4, list.len() as u64, buffer);
335            for item in list {
336                encode_dag_cbor(item, buffer)?;
337            }
338        }
339        Ipld::Map(map) => {
340            // CBOR major type 5 (map) - keys must be sorted by byte ordering
341            encode_cbor_length(5, map.len() as u64, buffer);
342            // BTreeMap already maintains sorted order
343            for (key, value) in map {
344                encode_cbor_length(3, key.len() as u64, buffer);
345                buffer.extend_from_slice(key.as_bytes());
346                encode_dag_cbor(value, buffer)?;
347            }
348        }
349        Ipld::Link(cid) => {
350            // DAG-CBOR uses tag 42 for CID links
351            encode_cbor_tag(CID_TAG, buffer);
352            // CID bytes with multibase identity prefix (0x00)
353            let cid_bytes = cid.0.to_bytes();
354            let mut prefixed = vec![0x00];
355            prefixed.extend_from_slice(&cid_bytes);
356            encode_cbor_length(2, prefixed.len() as u64, buffer);
357            buffer.extend_from_slice(&prefixed);
358        }
359    }
360    Ok(())
361}
362
363fn encode_cbor_integer(value: i128, buffer: &mut Vec<u8>) -> Result<()> {
364    if value >= 0 {
365        // Non-negative integers: CBOR major type 0
366        let val = value as u64;
367        encode_cbor_length(0, val, buffer);
368    } else {
369        // Negative integers: CBOR major type 1, encoded as -1-n
370        let val = (-1 - value) as u64;
371        encode_cbor_length(1, val, buffer);
372    }
373    Ok(())
374}
375
376fn encode_cbor_length(major_type: u8, length: u64, buffer: &mut Vec<u8>) {
377    let mt = major_type << 5;
378    if length < 24 {
379        buffer.push(mt | length as u8);
380    } else if length < 256 {
381        buffer.push(mt | 24);
382        buffer.push(length as u8);
383    } else if length < 65536 {
384        buffer.push(mt | 25);
385        buffer.extend_from_slice(&(length as u16).to_be_bytes());
386    } else if length < 4294967296 {
387        buffer.push(mt | 26);
388        buffer.extend_from_slice(&(length as u32).to_be_bytes());
389    } else {
390        buffer.push(mt | 27);
391        buffer.extend_from_slice(&length.to_be_bytes());
392    }
393}
394
395fn encode_cbor_tag(tag: u64, buffer: &mut Vec<u8>) {
396    // CBOR major type 6 (tag)
397    encode_cbor_length(6, tag, buffer);
398}
399
400// =============================================================================
401// DAG-CBOR Decoding
402// =============================================================================
403
404fn decode_dag_cbor<R: std::io::Read>(reader: &mut R) -> Result<Ipld> {
405    let mut first_byte = [0u8; 1];
406    reader
407        .read_exact(&mut first_byte)
408        .map_err(|e| Error::Deserialization(format!("Failed to read CBOR: {}", e)))?;
409
410    let major_type = first_byte[0] >> 5;
411    let additional_info = first_byte[0] & 0x1f;
412
413    match major_type {
414        0 => {
415            // Unsigned integer
416            let value = decode_cbor_uint(additional_info, reader)?;
417            Ok(Ipld::Integer(value as i128))
418        }
419        1 => {
420            // Negative integer
421            let value = decode_cbor_uint(additional_info, reader)?;
422            Ok(Ipld::Integer(-1 - value as i128))
423        }
424        2 => {
425            // Byte string
426            let len = decode_cbor_uint(additional_info, reader)? as usize;
427            let mut bytes = vec![0u8; len];
428            reader
429                .read_exact(&mut bytes)
430                .map_err(|e| Error::Deserialization(format!("Failed to read bytes: {}", e)))?;
431            Ok(Ipld::Bytes(bytes))
432        }
433        3 => {
434            // Text string
435            let len = decode_cbor_uint(additional_info, reader)? as usize;
436            let mut bytes = vec![0u8; len];
437            reader
438                .read_exact(&mut bytes)
439                .map_err(|e| Error::Deserialization(format!("Failed to read string: {}", e)))?;
440            let s = String::from_utf8(bytes)
441                .map_err(|e| Error::Deserialization(format!("Invalid UTF-8: {}", e)))?;
442            Ok(Ipld::String(s))
443        }
444        4 => {
445            // Array
446            let len = decode_cbor_uint(additional_info, reader)? as usize;
447            let mut list = Vec::with_capacity(len);
448            for _ in 0..len {
449                list.push(decode_dag_cbor(reader)?);
450            }
451            Ok(Ipld::List(list))
452        }
453        5 => {
454            // Map
455            let len = decode_cbor_uint(additional_info, reader)? as usize;
456            let mut map = BTreeMap::new();
457            for _ in 0..len {
458                let key = decode_dag_cbor(reader)?;
459                let key_str = match key {
460                    Ipld::String(s) => s,
461                    _ => {
462                        return Err(Error::Deserialization(
463                            "Map keys must be strings in IPLD".to_string(),
464                        ))
465                    }
466                };
467                let value = decode_dag_cbor(reader)?;
468                map.insert(key_str, value);
469            }
470            Ok(Ipld::Map(map))
471        }
472        6 => {
473            // Tag
474            let tag = decode_cbor_uint(additional_info, reader)?;
475            if tag == CID_TAG {
476                // CID link
477                let bytes_ipld = decode_dag_cbor(reader)?;
478                match bytes_ipld {
479                    Ipld::Bytes(mut bytes) => {
480                        // Remove the multibase identity prefix (0x00)
481                        if bytes.first() == Some(&0x00) {
482                            bytes.remove(0);
483                        }
484                        let cid = Cid::try_from(&bytes[..])
485                            .map_err(|e| Error::Deserialization(format!("Invalid CID: {}", e)))?;
486                        Ok(Ipld::Link(crate::cid::SerializableCid(cid)))
487                    }
488                    _ => Err(Error::Deserialization(
489                        "CID tag must wrap bytes".to_string(),
490                    )),
491                }
492            } else {
493                // Unknown tag, just decode the content
494                decode_dag_cbor(reader)
495            }
496        }
497        7 => {
498            // Simple values and floats
499            match additional_info {
500                20 => Ok(Ipld::Bool(false)),
501                21 => Ok(Ipld::Bool(true)),
502                22 => Ok(Ipld::Null),
503                25 => {
504                    // 16-bit float (not commonly used, convert to f64)
505                    let mut bytes = [0u8; 2];
506                    reader.read_exact(&mut bytes).map_err(|e| {
507                        Error::Deserialization(format!("Failed to read f16: {}", e))
508                    })?;
509                    let bits = u16::from_be_bytes(bytes);
510                    Ok(Ipld::Float(f16_to_f64(bits)))
511                }
512                26 => {
513                    // 32-bit float
514                    let mut bytes = [0u8; 4];
515                    reader.read_exact(&mut bytes).map_err(|e| {
516                        Error::Deserialization(format!("Failed to read f32: {}", e))
517                    })?;
518                    let f = f32::from_be_bytes(bytes);
519                    Ok(Ipld::Float(f as f64))
520                }
521                27 => {
522                    // 64-bit float
523                    let mut bytes = [0u8; 8];
524                    reader.read_exact(&mut bytes).map_err(|e| {
525                        Error::Deserialization(format!("Failed to read f64: {}", e))
526                    })?;
527                    let f = f64::from_be_bytes(bytes);
528                    Ok(Ipld::Float(f))
529                }
530                _ => Err(Error::Deserialization(format!(
531                    "Unknown simple value: {}",
532                    additional_info
533                ))),
534            }
535        }
536        _ => Err(Error::Deserialization(format!(
537            "Unknown CBOR major type: {}",
538            major_type
539        ))),
540    }
541}
542
543fn decode_cbor_uint<R: std::io::Read>(additional_info: u8, reader: &mut R) -> Result<u64> {
544    match additional_info {
545        0..=23 => Ok(additional_info as u64),
546        24 => {
547            let mut buf = [0u8; 1];
548            reader
549                .read_exact(&mut buf)
550                .map_err(|e| Error::Deserialization(format!("Failed to read u8: {}", e)))?;
551            Ok(buf[0] as u64)
552        }
553        25 => {
554            let mut buf = [0u8; 2];
555            reader
556                .read_exact(&mut buf)
557                .map_err(|e| Error::Deserialization(format!("Failed to read u16: {}", e)))?;
558            Ok(u16::from_be_bytes(buf) as u64)
559        }
560        26 => {
561            let mut buf = [0u8; 4];
562            reader
563                .read_exact(&mut buf)
564                .map_err(|e| Error::Deserialization(format!("Failed to read u32: {}", e)))?;
565            Ok(u32::from_be_bytes(buf) as u64)
566        }
567        27 => {
568            let mut buf = [0u8; 8];
569            reader
570                .read_exact(&mut buf)
571                .map_err(|e| Error::Deserialization(format!("Failed to read u64: {}", e)))?;
572            Ok(u64::from_be_bytes(buf))
573        }
574        _ => Err(Error::Deserialization(format!(
575            "Invalid additional info for integer: {}",
576            additional_info
577        ))),
578    }
579}
580
581/// Convert IEEE 754 half-precision (f16) to double-precision (f64)
582fn f16_to_f64(bits: u16) -> f64 {
583    let sign = ((bits >> 15) & 1) as u64;
584    let exp = ((bits >> 10) & 0x1f) as i32;
585    let frac = (bits & 0x3ff) as u64;
586
587    if exp == 0 {
588        // Subnormal or zero
589        if frac == 0 {
590            f64::from_bits(sign << 63)
591        } else {
592            // Subnormal, normalize it
593            let mut e = -14;
594            let mut f = frac;
595            while (f & 0x400) == 0 {
596                f <<= 1;
597                e -= 1;
598            }
599            let new_exp = (e + 1023) as u64;
600            let new_frac = (f & 0x3ff) << 42;
601            f64::from_bits((sign << 63) | (new_exp << 52) | new_frac)
602        }
603    } else if exp == 31 {
604        // Infinity or NaN
605        if frac == 0 {
606            f64::from_bits((sign << 63) | (0x7ff << 52))
607        } else {
608            f64::from_bits((sign << 63) | (0x7ff << 52) | (frac << 42))
609        }
610    } else {
611        // Normal number
612        let new_exp = ((exp - 15) + 1023) as u64;
613        let new_frac = frac << 42;
614        f64::from_bits((sign << 63) | (new_exp << 52) | new_frac)
615    }
616}
617
618// =============================================================================
619// DAG-JSON Encoding/Decoding
620// =============================================================================
621
622fn ipld_to_dag_json(ipld: &Ipld) -> Result<serde_json::Value> {
623    use serde_json::Value;
624
625    match ipld {
626        Ipld::Null => Ok(Value::Null),
627        Ipld::Bool(b) => Ok(Value::Bool(*b)),
628        Ipld::Integer(i) => {
629            // JSON numbers have limited precision, use number if safe, string otherwise
630            if *i >= i64::MIN as i128 && *i <= i64::MAX as i128 {
631                Ok(Value::Number((*i as i64).into()))
632            } else {
633                // Large integers: encode as string
634                Ok(Value::String(i.to_string()))
635            }
636        }
637        Ipld::Float(f) => serde_json::Number::from_f64(*f)
638            .map(Value::Number)
639            .ok_or_else(|| Error::Serialization("Cannot encode NaN/Inf as JSON".to_string())),
640        Ipld::String(s) => Ok(Value::String(s.clone())),
641        Ipld::Bytes(b) => {
642            // DAG-JSON encodes bytes as {"/": {"bytes": "<base64>"}}
643            use multibase::Base;
644            let encoded = multibase::encode(Base::Base64, b);
645            // multibase::encode includes the base prefix, we just want the data
646            let data = &encoded[1..]; // Skip the 'm' prefix for base64
647            let mut inner = serde_json::Map::new();
648            inner.insert("bytes".to_string(), Value::String(data.to_string()));
649            let mut outer = serde_json::Map::new();
650            outer.insert("/".to_string(), Value::Object(inner));
651            Ok(Value::Object(outer))
652        }
653        Ipld::List(list) => {
654            let arr: Result<Vec<Value>> = list.iter().map(ipld_to_dag_json).collect();
655            Ok(Value::Array(arr?))
656        }
657        Ipld::Map(map) => {
658            let mut obj = serde_json::Map::new();
659            for (k, v) in map {
660                obj.insert(k.clone(), ipld_to_dag_json(v)?);
661            }
662            Ok(Value::Object(obj))
663        }
664        Ipld::Link(cid) => {
665            // DAG-JSON encodes CID links as {"/": "<cid-string>"}
666            let mut obj = serde_json::Map::new();
667            obj.insert("/".to_string(), Value::String(cid.0.to_string()));
668            Ok(Value::Object(obj))
669        }
670    }
671}
672
673fn dag_json_to_ipld(value: &serde_json::Value) -> Result<Ipld> {
674    use serde_json::Value;
675
676    match value {
677        Value::Null => Ok(Ipld::Null),
678        Value::Bool(b) => Ok(Ipld::Bool(*b)),
679        Value::Number(n) => {
680            if let Some(i) = n.as_i64() {
681                Ok(Ipld::Integer(i as i128))
682            } else if let Some(f) = n.as_f64() {
683                Ok(Ipld::Float(f))
684            } else {
685                Err(Error::Deserialization("Invalid number".to_string()))
686            }
687        }
688        Value::String(s) => Ok(Ipld::String(s.clone())),
689        Value::Array(arr) => {
690            let list: Result<Vec<Ipld>> = arr.iter().map(dag_json_to_ipld).collect();
691            Ok(Ipld::List(list?))
692        }
693        Value::Object(obj) => {
694            // Check for special DAG-JSON encodings
695            if let Some(slash_value) = obj.get("/") {
696                if obj.len() == 1 {
697                    // Could be a link {"/": "<cid>"} or bytes {"/": {"bytes": "<base64>"}}
698                    match slash_value {
699                        Value::String(cid_str) => {
700                            // CID link
701                            let cid: Cid = cid_str.parse().map_err(|e| {
702                                Error::Deserialization(format!("Invalid CID: {}", e))
703                            })?;
704                            return Ok(Ipld::Link(crate::cid::SerializableCid(cid)));
705                        }
706                        Value::Object(inner) => {
707                            if let Some(Value::String(bytes_str)) = inner.get("bytes") {
708                                // Base64 encoded bytes
709                                let decoded = multibase::decode(format!("m{}", bytes_str))
710                                    .map_err(|e| {
711                                        Error::Deserialization(format!(
712                                            "Invalid base64 bytes: {}",
713                                            e
714                                        ))
715                                    })?
716                                    .1;
717                                return Ok(Ipld::Bytes(decoded));
718                            }
719                        }
720                        _ => {}
721                    }
722                }
723            }
724
725            // Regular map
726            let mut map = BTreeMap::new();
727            for (k, v) in obj {
728                map.insert(k.clone(), dag_json_to_ipld(v)?);
729            }
730            Ok(Ipld::Map(map))
731        }
732    }
733}
734
735// =============================================================================
736// Conversions
737// =============================================================================
738
739impl Serialize for Ipld {
740    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
741    where
742        S: serde::Serializer,
743    {
744        match self {
745            Ipld::Null => serializer.serialize_none(),
746            Ipld::Bool(b) => serializer.serialize_bool(*b),
747            Ipld::Integer(i) => {
748                // Serialize as i64 if within range, otherwise as i128
749                if *i >= i64::MIN as i128 && *i <= i64::MAX as i128 {
750                    serializer.serialize_i64(*i as i64)
751                } else {
752                    serializer.serialize_i128(*i)
753                }
754            }
755            Ipld::Float(f) => serializer.serialize_f64(*f),
756            Ipld::String(s) => serializer.serialize_str(s),
757            Ipld::Bytes(b) => serializer.serialize_bytes(b),
758            Ipld::List(list) => list.serialize(serializer),
759            Ipld::Map(map) => map.serialize(serializer),
760            Ipld::Link(cid) => cid.serialize(serializer),
761        }
762    }
763}
764
765impl<'de> Deserialize<'de> for Ipld {
766    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
767    where
768        D: serde::Deserializer<'de>,
769    {
770        use serde::de::{MapAccess, SeqAccess, Visitor};
771
772        struct IpldVisitor;
773
774        impl<'de> Visitor<'de> for IpldVisitor {
775            type Value = Ipld;
776
777            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
778                formatter.write_str("an IPLD value")
779            }
780
781            fn visit_bool<E>(self, value: bool) -> std::result::Result<Ipld, E> {
782                Ok(Ipld::Bool(value))
783            }
784
785            fn visit_i64<E>(self, value: i64) -> std::result::Result<Ipld, E> {
786                Ok(Ipld::Integer(value as i128))
787            }
788
789            fn visit_i128<E>(self, value: i128) -> std::result::Result<Ipld, E> {
790                Ok(Ipld::Integer(value))
791            }
792
793            fn visit_u64<E>(self, value: u64) -> std::result::Result<Ipld, E> {
794                Ok(Ipld::Integer(value as i128))
795            }
796
797            fn visit_f64<E>(self, value: f64) -> std::result::Result<Ipld, E> {
798                Ok(Ipld::Float(value))
799            }
800
801            fn visit_str<E>(self, value: &str) -> std::result::Result<Ipld, E>
802            where
803                E: serde::de::Error,
804            {
805                Ok(Ipld::String(value.to_string()))
806            }
807
808            fn visit_string<E>(self, value: String) -> std::result::Result<Ipld, E> {
809                Ok(Ipld::String(value))
810            }
811
812            fn visit_bytes<E>(self, value: &[u8]) -> std::result::Result<Ipld, E> {
813                Ok(Ipld::Bytes(value.to_vec()))
814            }
815
816            fn visit_byte_buf<E>(self, value: Vec<u8>) -> std::result::Result<Ipld, E> {
817                Ok(Ipld::Bytes(value))
818            }
819
820            fn visit_none<E>(self) -> std::result::Result<Ipld, E> {
821                Ok(Ipld::Null)
822            }
823
824            fn visit_unit<E>(self) -> std::result::Result<Ipld, E> {
825                Ok(Ipld::Null)
826            }
827
828            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Ipld, A::Error>
829            where
830                A: SeqAccess<'de>,
831            {
832                let mut list = Vec::new();
833                while let Some(elem) = seq.next_element()? {
834                    list.push(elem);
835                }
836                Ok(Ipld::List(list))
837            }
838
839            fn visit_map<A>(self, mut map: A) -> std::result::Result<Ipld, A::Error>
840            where
841                A: MapAccess<'de>,
842            {
843                let mut result = BTreeMap::new();
844                while let Some((key, value)) = map.next_entry()? {
845                    result.insert(key, value);
846                }
847                Ok(Ipld::Map(result))
848            }
849        }
850
851        deserializer.deserialize_any(IpldVisitor)
852    }
853}
854
855impl From<bool> for Ipld {
856    fn from(b: bool) -> Self {
857        Ipld::Bool(b)
858    }
859}
860
861impl From<i64> for Ipld {
862    fn from(i: i64) -> Self {
863        Ipld::Integer(i as i128)
864    }
865}
866
867impl From<i128> for Ipld {
868    fn from(i: i128) -> Self {
869        Ipld::Integer(i)
870    }
871}
872
873impl From<u64> for Ipld {
874    fn from(u: u64) -> Self {
875        Ipld::Integer(u as i128)
876    }
877}
878
879impl From<f64> for Ipld {
880    fn from(f: f64) -> Self {
881        Ipld::Float(f)
882    }
883}
884
885impl From<String> for Ipld {
886    fn from(s: String) -> Self {
887        Ipld::String(s)
888    }
889}
890
891impl From<&str> for Ipld {
892    fn from(s: &str) -> Self {
893        Ipld::String(s.to_string())
894    }
895}
896
897impl From<Vec<u8>> for Ipld {
898    fn from(bytes: Vec<u8>) -> Self {
899        Ipld::Bytes(bytes)
900    }
901}
902
903impl From<Cid> for Ipld {
904    fn from(cid: Cid) -> Self {
905        Ipld::Link(crate::cid::SerializableCid(cid))
906    }
907}
908
909#[cfg(test)]
910mod tests {
911    use super::*;
912
913    #[test]
914    fn test_dag_cbor_roundtrip_simple() {
915        let values = vec![
916            Ipld::Null,
917            Ipld::Bool(true),
918            Ipld::Bool(false),
919            Ipld::Integer(0),
920            Ipld::Integer(42),
921            Ipld::Integer(-1),
922            Ipld::Integer(-100),
923            Ipld::Float(2.5),
924            Ipld::String("hello".to_string()),
925            Ipld::Bytes(vec![1, 2, 3]),
926        ];
927
928        for value in values {
929            let encoded = value.to_dag_cbor().unwrap();
930            let decoded = Ipld::from_dag_cbor(&encoded).unwrap();
931            assert_eq!(value, decoded, "Failed roundtrip for {:?}", value);
932        }
933    }
934
935    #[test]
936    fn test_dag_cbor_roundtrip_complex() {
937        let mut map = BTreeMap::new();
938        map.insert("name".to_string(), Ipld::String("test".to_string()));
939        map.insert("count".to_string(), Ipld::Integer(42));
940
941        let value = Ipld::Map(map);
942        let encoded = value.to_dag_cbor().unwrap();
943        let decoded = Ipld::from_dag_cbor(&encoded).unwrap();
944        assert_eq!(value, decoded);
945    }
946
947    #[test]
948    fn test_dag_json_roundtrip() {
949        let mut map = BTreeMap::new();
950        map.insert("name".to_string(), Ipld::String("test".to_string()));
951        map.insert("count".to_string(), Ipld::Integer(42));
952
953        let value = Ipld::Map(map);
954        let json = value.to_dag_json().unwrap();
955        let decoded = Ipld::from_dag_json(&json).unwrap();
956        assert_eq!(value, decoded);
957    }
958
959    #[test]
960    fn test_dag_json_bytes_encoding() {
961        let value = Ipld::Bytes(vec![1, 2, 3, 4, 5]);
962        let json = value.to_dag_json().unwrap();
963        // Should be encoded as {"/": {"bytes": "..."}}
964        assert!(json.contains("\"/\""));
965        assert!(json.contains("\"bytes\""));
966
967        let decoded = Ipld::from_dag_json(&json).unwrap();
968        assert_eq!(value, decoded);
969    }
970}