Skip to main content

dpp/util/cbor_value/
canonical.rs

1use std::{
2    cmp::Ordering,
3    collections::BTreeMap,
4    convert::{TryFrom, TryInto},
5};
6
7use ciborium::value::Value as CborValue;
8
9use serde::Serialize;
10
11use crate::ProtocolError;
12
13use super::{
14    convert::convert_to, get_from_cbor_map, to_path_of_cbors, FieldType, ReplacePaths,
15    ValuesCollection,
16};
17
18#[derive(Default, Clone, Debug)]
19pub struct CborCanonicalMap {
20    inner: Vec<(CborValue, CborValue)>,
21}
22
23impl CborCanonicalMap {
24    pub fn new() -> Self {
25        Self { inner: vec![] }
26    }
27
28    pub fn from_serializable<T>(value: &T) -> Result<Self, ProtocolError>
29    where
30        T: Serialize,
31    {
32        let cbor = ciborium::value::Value::serialized(&value)
33            .map_err(|e| ProtocolError::EncodingError(e.to_string()))?;
34        CborCanonicalMap::try_from(cbor).map_err(|e| ProtocolError::EncodingError(e.to_string()))
35    }
36
37    pub fn from_vector(vec: Vec<(CborValue, CborValue)>) -> Self {
38        let mut map = Self::new();
39        map.inner = vec;
40        map
41    }
42
43    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<CborValue>) {
44        self.inner.push((CborValue::Text(key.into()), value.into()));
45    }
46
47    pub fn remove(&mut self, key_to_remove: impl Into<CborValue>) {
48        let key_to_compare: CborValue = key_to_remove.into();
49        if let Some(index) = self
50            .inner
51            .iter()
52            .position(|(key, _)| key == &key_to_compare)
53        {
54            self.inner.remove(index);
55        }
56    }
57
58    pub fn get_mut(&mut self, key: &CborValue) -> Option<&mut CborValue> {
59        if let Some(index) = self.inner.iter().position(|(el_key, _)| el_key == key) {
60            Some(&mut self.inner.get_mut(index)?.1)
61        } else {
62            None
63        }
64    }
65
66    pub fn replace_paths<I, C>(&mut self, paths: I, from: FieldType, to: FieldType)
67    where
68        I: IntoIterator<Item = C>,
69        C: AsRef<str>,
70    {
71        for path in paths.into_iter() {
72            self.replace_path(path.as_ref(), from, to);
73        }
74    }
75
76    pub fn replace_path(&mut self, path: &str, from: FieldType, to: FieldType) -> Option<()> {
77        let cbor_value = self.get_path_mut(path)?;
78        let replace_with = convert_to(cbor_value, from, to)?;
79
80        *cbor_value = replace_with;
81
82        Some(())
83    }
84
85    pub fn set(&mut self, key: &CborValue, replace_with: CborValue) -> Option<()> {
86        if let Some(index) = self.inner.iter().position(|(el_key, _)| el_key == key) {
87            if let Some(key_value) = self.inner.get_mut(index) {
88                key_value.1 = replace_with;
89                Some(())
90            } else {
91                None
92            }
93        } else {
94            None
95        }
96    }
97
98    /// From the CBOR RFC on how to sort the keys:
99    /// *  If two keys have different lengths, the shorter one sorts
100    ///    earlier;
101    ///
102    /// *  If two keys have the same length, the one with the lower value
103    ///    in (byte-wise) lexical order sorts earlier.
104    ///
105    /// https://datatracker.ietf.org/doc/html/rfc7049#section-3.9
106    pub fn sort_canonical(&mut self) {
107        recursively_sort_canonical_cbor_map(&mut self.inner)
108    }
109
110    pub fn to_bytes(mut self) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
111        self.sort_canonical();
112
113        let mut bytes = Vec::<u8>::new();
114
115        let map = CborValue::Map(self.inner);
116
117        ciborium::ser::into_writer(&map, &mut bytes)?;
118
119        Ok(bytes)
120    }
121
122    pub fn to_value_unsorted(&self) -> CborValue {
123        CborValue::Map(self.inner.clone())
124    }
125
126    pub fn to_value_sorted(mut self) -> CborValue {
127        self.sort_canonical();
128
129        CborValue::Map(self.inner)
130    }
131
132    pub fn to_value_clone(&mut self) -> CborValue {
133        self.sort_canonical();
134
135        CborValue::Map(self.inner.clone())
136    }
137}
138
139impl ValuesCollection for CborCanonicalMap {
140    type Key = CborValue;
141    type Value = CborValue;
142
143    fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
144        if let Some(index) = self.inner.iter().position(|(el_key, _)| el_key == key) {
145            Some(&self.inner.get(index)?.1)
146        } else {
147            None
148        }
149    }
150
151    fn get_mut(&mut self, key: &CborValue) -> Option<&mut CborValue> {
152        if let Some(index) = self.inner.iter().position(|(el_key, _)| el_key == key) {
153            Some(&mut self.inner.get_mut(index)?.1)
154        } else {
155            None
156        }
157    }
158
159    fn remove(&mut self, key_to_remove: impl Into<CborValue>) -> Option<Self::Value> {
160        let key_to_compare: CborValue = key_to_remove.into();
161        if let Some(index) = self
162            .inner
163            .iter()
164            .position(|(key, _)| key == &key_to_compare)
165        {
166            let (_, v) = self.inner.remove(index);
167            Some(v)
168        } else {
169            None
170        }
171    }
172}
173
174impl ReplacePaths for CborCanonicalMap {
175    type Value = CborValue;
176
177    fn replace_paths<I, C>(&mut self, paths: I, from: FieldType, to: FieldType)
178    where
179        I: IntoIterator<Item = C>,
180        C: AsRef<str>,
181    {
182        for path in paths.into_iter() {
183            self.replace_path(path.as_ref(), from, to);
184        }
185    }
186
187    fn replace_path(&mut self, path: &str, from: FieldType, to: FieldType) -> Option<()> {
188        let cbor_value = self.get_path_mut(path)?;
189        let replace_with = convert_to(cbor_value, from, to)?;
190
191        *cbor_value = replace_with;
192
193        Some(())
194    }
195
196    fn get_path_mut(&mut self, path: &str) -> Option<&mut CborValue> {
197        let cbor_path = to_path_of_cbors(path).ok()?;
198        if cbor_path.is_empty() {
199            return None;
200        }
201        if cbor_path.len() == 1 {
202            return self.get_mut(&cbor_path[0]);
203        }
204
205        let mut current_level: &mut CborValue = self.get_mut(&cbor_path[0])?;
206        for step in cbor_path.iter().skip(1) {
207            match current_level {
208                CborValue::Map(ref mut cbor_map) => {
209                    current_level = get_from_cbor_map(cbor_map, step)?
210                }
211                CborValue::Array(ref mut cbor_array) => {
212                    let idx = step.as_integer()?;
213                    let id: usize = idx.try_into().ok()?;
214                    current_level = cbor_array.get_mut(id)?
215                }
216                _ => {
217                    // do nothing if it's not a container type
218                }
219            }
220        }
221        Some(current_level)
222    }
223}
224
225impl TryFrom<CborValue> for CborCanonicalMap {
226    type Error = ProtocolError;
227
228    fn try_from(value: CborValue) -> Result<Self, Self::Error> {
229        if let CborValue::Map(map) = value {
230            Ok(Self::from_vector(map))
231        } else {
232            Err(ProtocolError::ParsingError(
233                "Expected map to be a map".into(),
234            ))
235        }
236    }
237}
238
239impl From<Vec<(CborValue, CborValue)>> for CborCanonicalMap {
240    fn from(vec: Vec<(CborValue, CborValue)>) -> Self {
241        Self::from_vector(vec)
242    }
243}
244
245impl From<&Vec<(CborValue, CborValue)>> for CborCanonicalMap {
246    fn from(vec: &Vec<(CborValue, CborValue)>) -> Self {
247        Self::from_vector(vec.clone())
248    }
249}
250
251impl<T> From<&BTreeMap<String, T>> for CborCanonicalMap
252where
253    T: Into<CborValue> + Clone,
254{
255    fn from(map: &BTreeMap<String, T>) -> Self {
256        let vec = map
257            .iter()
258            .map(|(key, value)| (key.clone().into(), value.clone().into()))
259            .collect::<Vec<(CborValue, CborValue)>>();
260
261        Self::from(vec)
262    }
263}
264
265fn recursively_sort_canonical_cbor_map(cbor_map: &mut [(CborValue, CborValue)]) {
266    for (_, value) in cbor_map.iter_mut() {
267        if let CborValue::Map(map) = value {
268            recursively_sort_canonical_cbor_map(map)
269        }
270        if let CborValue::Array(array) = value {
271            for item in array.iter_mut() {
272                if let CborValue::Map(map) = item {
273                    recursively_sort_canonical_cbor_map(map)
274                }
275            }
276        }
277    }
278
279    cbor_map.sort_by(|a, b| {
280        // We now for sure that the keys are always text, since `insert()`
281        // methods accepts only types that can be converted into a string
282        let key_a = a.0.as_text().unwrap().as_bytes();
283        let key_b = b.0.as_text().unwrap().as_bytes();
284
285        let len_comparison = key_a.len().cmp(&key_b.len());
286
287        match len_comparison {
288            Ordering::Less => Ordering::Less,
289            Ordering::Equal => key_a.cmp(key_b),
290            Ordering::Greater => Ordering::Greater,
291        }
292    });
293}
294
295//todo: explain why this returns an option?
296pub fn value_to_bytes(value: &CborValue) -> Result<Option<Vec<u8>>, ProtocolError> {
297    match value {
298        CborValue::Bytes(bytes) => Ok(Some(bytes.clone())),
299        CborValue::Text(text) => match bs58::decode(text).into_vec() {
300            Ok(data) => Ok(Some(data)),
301            Err(_) => Ok(None),
302        },
303        CborValue::Array(array) => array
304            .iter()
305            .map(|byte| match byte {
306                CborValue::Integer(int) => {
307                    let value_as_u8: u8 = (*int).try_into().map_err(|_| {
308                        ProtocolError::DecodingError(String::from("expected u8 value"))
309                    })?;
310                    Ok(Some(value_as_u8))
311                }
312                _ => Err(ProtocolError::DecodingError(String::from(
313                    "not an array of integers",
314                ))),
315            })
316            .collect::<Result<Option<Vec<u8>>, ProtocolError>>(),
317        _ => Err(ProtocolError::DecodingError(String::from(
318            "system value is incorrect type",
319        ))),
320    }
321}
322
323pub fn value_to_hash(value: &CborValue) -> Result<[u8; 32], ProtocolError> {
324    match value {
325        CborValue::Bytes(bytes) => bytes
326            .clone()
327            .try_into()
328            .map_err(|_| ProtocolError::DecodingError("expected 32 bytes".to_string())),
329        CborValue::Text(text) => match bs58::decode(text).into_vec() {
330            Ok(bytes) => bytes
331                .try_into()
332                .map_err(|_| ProtocolError::DecodingError("expected 32 bytes".to_string())),
333            Err(_) => Err(ProtocolError::DecodingError(
334                "expected 32 bytes".to_string(),
335            )),
336        },
337        CborValue::Array(array) => array
338            .iter()
339            .map(|byte| match byte {
340                CborValue::Integer(int) => {
341                    let value_as_u8: u8 = (*int).try_into().map_err(|_| {
342                        ProtocolError::DecodingError(String::from("expected u8 value"))
343                    })?;
344                    Ok(value_as_u8)
345                }
346                _ => Err(ProtocolError::DecodingError(String::from(
347                    "not an array of integers",
348                ))),
349            })
350            .collect::<Result<Vec<u8>, ProtocolError>>()?
351            .try_into()
352            .map_err(|_| ProtocolError::DecodingError("expected 32 bytes".to_string())),
353        _ => Err(ProtocolError::DecodingError(String::from(
354            "system value is incorrect type",
355        ))),
356    }
357}
358
359#[cfg(test)]
360mod test {
361    use std::collections::BTreeMap;
362    use std::convert::TryFrom;
363    use std::convert::TryInto;
364
365    use crate::util::cbor_value::{ReplacePaths, ValuesCollection};
366
367    use super::{value_to_bytes, value_to_hash, CborCanonicalMap, CborValue, FieldType};
368    use ciborium::cbor;
369
370    // ------- Existing tests -------
371
372    #[test]
373    fn should_get_path_to_property_from_cbor() {
374        let cbor_value = cbor!( {
375            "alpha"  =>  {
376                "bravo" =>  "bravo_value",
377            }
378        })
379        .expect("valid cbor");
380        let mut canonical: CborCanonicalMap = cbor_value.try_into().expect("valid canonical");
381        let result = canonical.get_path_mut("alpha.bravo").expect("bravo value");
382        assert_eq!(&mut CborValue::Text(String::from("bravo_value")), result);
383    }
384
385    #[test]
386    fn should_get_paths_to_array_from_cbor() {
387        let cbor_value = cbor!( {
388            "alpha"  =>  {
389                "bravo" => ["bravo_first_item", "bravo_second_item" ],
390            }
391        })
392        .expect("valid cbor");
393        let mut canonical: CborCanonicalMap = cbor_value.try_into().expect("valid canonical");
394        let result = canonical
395            .get_path_mut("alpha.bravo[0]")
396            .expect("first item from bravo");
397        assert_eq!(
398            &mut CborValue::Text(String::from("bravo_first_item")),
399            result
400        );
401    }
402
403    #[test]
404    fn should_return_non_when_path_not_exist() {
405        let cbor_value = cbor!( {
406            "alpha"  =>  {
407                "bravo" => ["bravo_first_item", "bravo_second_item" ],
408            }
409        })
410        .expect("valid cbor");
411        let mut canonical: CborCanonicalMap = cbor_value.try_into().expect("valid canonical");
412        let path = "alpha.bravo[-1]";
413
414        assert!(canonical.get_path_mut(path).is_none())
415    }
416
417    #[test]
418    fn should_replace_cbor_value() {
419        let cbor_value = cbor!({
420            "alpha"  =>  {
421                "array_value" => vec![0_u8;32]
422
423            }
424        })
425        .expect("cbor should be created");
426
427        let mut canonical: CborCanonicalMap = cbor_value.try_into().expect("valid canonical");
428        canonical.replace_path(
429            "alpha.array_value",
430            FieldType::ArrayInt,
431            FieldType::StringBase58,
432        );
433
434        let replaced = canonical
435            .get_path_mut("alpha.array_value")
436            .expect("value should be returned");
437
438        assert_eq!(
439            &mut CborValue::Text(bs58::encode(vec![0_u8; 32]).into_string()),
440            replaced
441        );
442    }
443
444    // ------- New coverage tests -------
445
446    // CborCanonicalMap construction and basic operations
447
448    #[test]
449    fn new_creates_empty_map() {
450        let map = CborCanonicalMap::new();
451        let value = map.to_value_unsorted();
452        assert_eq!(value, CborValue::Map(vec![]));
453    }
454
455    #[test]
456    fn default_creates_empty_map() {
457        let map = CborCanonicalMap::default();
458        let value = map.to_value_unsorted();
459        assert_eq!(value, CborValue::Map(vec![]));
460    }
461
462    #[test]
463    fn insert_adds_key_value_pair() {
464        let mut map = CborCanonicalMap::new();
465        map.insert("hello", CborValue::Text("world".to_string()));
466
467        let val = ValuesCollection::get(&map, &CborValue::Text("hello".to_string()));
468        assert_eq!(val, Some(&CborValue::Text("world".to_string())));
469    }
470
471    #[test]
472    fn insert_multiple_keys() {
473        let mut map = CborCanonicalMap::new();
474        map.insert("a", CborValue::Integer(1.into()));
475        map.insert("b", CborValue::Integer(2.into()));
476        map.insert("c", CborValue::Integer(3.into()));
477
478        assert_eq!(
479            ValuesCollection::get(&map, &CborValue::Text("a".to_string())),
480            Some(&CborValue::Integer(1.into()))
481        );
482        assert_eq!(
483            ValuesCollection::get(&map, &CborValue::Text("b".to_string())),
484            Some(&CborValue::Integer(2.into()))
485        );
486        assert_eq!(
487            ValuesCollection::get(&map, &CborValue::Text("c".to_string())),
488            Some(&CborValue::Integer(3.into()))
489        );
490    }
491
492    #[test]
493    fn remove_existing_key() {
494        let mut map = CborCanonicalMap::new();
495        map.insert("key1", CborValue::Bool(true));
496        map.insert("key2", CborValue::Bool(false));
497
498        map.remove("key1");
499
500        assert!(ValuesCollection::get(&map, &CborValue::Text("key1".to_string())).is_none());
501        assert_eq!(
502            ValuesCollection::get(&map, &CborValue::Text("key2".to_string())),
503            Some(&CborValue::Bool(false))
504        );
505    }
506
507    #[test]
508    fn remove_nonexistent_key_is_noop() {
509        let mut map = CborCanonicalMap::new();
510        map.insert("key1", CborValue::Bool(true));
511
512        // Should not panic or change anything
513        map.remove("nonexistent");
514
515        assert_eq!(
516            ValuesCollection::get(&map, &CborValue::Text("key1".to_string())),
517            Some(&CborValue::Bool(true))
518        );
519    }
520
521    #[test]
522    fn get_mut_returns_mutable_reference() {
523        let mut map = CborCanonicalMap::new();
524        map.insert("key", CborValue::Integer(10.into()));
525
526        let val = map.get_mut(&CborValue::Text("key".to_string()));
527        assert!(val.is_some());
528        *val.unwrap() = CborValue::Integer(20.into());
529
530        assert_eq!(
531            ValuesCollection::get(&map, &CborValue::Text("key".to_string())),
532            Some(&CborValue::Integer(20.into()))
533        );
534    }
535
536    #[test]
537    fn get_mut_returns_none_for_missing_key() {
538        let mut map = CborCanonicalMap::new();
539        assert!(map
540            .get_mut(&CborValue::Text("missing".to_string()))
541            .is_none());
542    }
543
544    #[test]
545    fn set_replaces_existing_value() {
546        let mut map = CborCanonicalMap::new();
547        map.insert("key", CborValue::Integer(1.into()));
548
549        let result = map.set(
550            &CborValue::Text("key".to_string()),
551            CborValue::Integer(99.into()),
552        );
553        assert!(result.is_some());
554
555        assert_eq!(
556            ValuesCollection::get(&map, &CborValue::Text("key".to_string())),
557            Some(&CborValue::Integer(99.into()))
558        );
559    }
560
561    #[test]
562    fn set_returns_none_for_missing_key() {
563        let mut map = CborCanonicalMap::new();
564
565        let result = map.set(
566            &CborValue::Text("missing".to_string()),
567            CborValue::Integer(1.into()),
568        );
569        assert!(result.is_none());
570    }
571
572    #[test]
573    fn from_vector_creates_map() {
574        let vec = vec![
575            (
576                CborValue::Text("a".to_string()),
577                CborValue::Integer(1.into()),
578            ),
579            (
580                CborValue::Text("b".to_string()),
581                CborValue::Integer(2.into()),
582            ),
583        ];
584
585        let map = CborCanonicalMap::from_vector(vec);
586
587        assert_eq!(
588            ValuesCollection::get(&map, &CborValue::Text("a".to_string())),
589            Some(&CborValue::Integer(1.into()))
590        );
591        assert_eq!(
592            ValuesCollection::get(&map, &CborValue::Text("b".to_string())),
593            Some(&CborValue::Integer(2.into()))
594        );
595    }
596
597    #[test]
598    fn from_serializable_with_btreemap() {
599        let mut btree = BTreeMap::new();
600        btree.insert("name".to_string(), "test".to_string());
601
602        let map =
603            CborCanonicalMap::from_serializable(&btree).expect("should serialize from BTreeMap");
604
605        assert!(ValuesCollection::get(&map, &CborValue::Text("name".to_string())).is_some());
606    }
607
608    #[test]
609    fn from_serializable_with_non_map_value_fails() {
610        // A plain string serializes as CborValue::Text, not a Map
611        let result = CborCanonicalMap::from_serializable(&"just a string");
612        assert!(result.is_err());
613    }
614
615    // Canonical sorting
616
617    #[test]
618    fn sort_canonical_orders_by_key_length_then_lexicographic() {
619        let mut map = CborCanonicalMap::new();
620        // Longer key first
621        map.insert("beta", CborValue::Integer(2.into()));
622        map.insert("a", CborValue::Integer(1.into()));
623        map.insert("cc", CborValue::Integer(3.into()));
624        map.insert("bb", CborValue::Integer(4.into()));
625
626        map.sort_canonical();
627
628        let sorted = map.to_value_unsorted();
629        if let CborValue::Map(pairs) = sorted {
630            let keys: Vec<&str> = pairs.iter().map(|(k, _)| k.as_text().unwrap()).collect();
631            // "a" (len 1) < "bb" (len 2) < "cc" (len 2, but bb < cc) < "beta" (len 4)
632            assert_eq!(keys, vec!["a", "bb", "cc", "beta"]);
633        } else {
634            panic!("Expected map");
635        }
636    }
637
638    #[test]
639    fn sort_canonical_recursively_sorts_nested_maps() {
640        let mut map = CborCanonicalMap::new();
641        // Create a nested map with unsorted keys
642        let nested = CborValue::Map(vec![
643            (
644                CborValue::Text("zz".to_string()),
645                CborValue::Integer(1.into()),
646            ),
647            (
648                CborValue::Text("a".to_string()),
649                CborValue::Integer(2.into()),
650            ),
651        ]);
652        map.insert("outer", nested);
653
654        map.sort_canonical();
655
656        let sorted = map.to_value_unsorted();
657        if let CborValue::Map(pairs) = sorted {
658            if let CborValue::Map(inner_pairs) = &pairs[0].1 {
659                let keys: Vec<&str> = inner_pairs
660                    .iter()
661                    .map(|(k, _)| k.as_text().unwrap())
662                    .collect();
663                // "a" (len 1) should come before "zz" (len 2)
664                assert_eq!(keys, vec!["a", "zz"]);
665            } else {
666                panic!("Expected nested map");
667            }
668        }
669    }
670
671    #[test]
672    fn sort_canonical_recursively_sorts_maps_inside_arrays() {
673        let mut map = CborCanonicalMap::new();
674        let nested_map_in_array = CborValue::Array(vec![CborValue::Map(vec![
675            (
676                CborValue::Text("zz".to_string()),
677                CborValue::Integer(1.into()),
678            ),
679            (
680                CborValue::Text("a".to_string()),
681                CborValue::Integer(2.into()),
682            ),
683        ])]);
684        map.insert("items", nested_map_in_array);
685
686        map.sort_canonical();
687
688        let sorted = map.to_value_unsorted();
689        if let CborValue::Map(pairs) = sorted {
690            if let CborValue::Array(arr) = &pairs[0].1 {
691                if let CborValue::Map(inner_pairs) = &arr[0] {
692                    let keys: Vec<&str> = inner_pairs
693                        .iter()
694                        .map(|(k, _)| k.as_text().unwrap())
695                        .collect();
696                    assert_eq!(keys, vec!["a", "zz"]);
697                } else {
698                    panic!("Expected map inside array");
699                }
700            } else {
701                panic!("Expected array");
702            }
703        }
704    }
705
706    // Serialization and value conversion
707
708    #[test]
709    fn to_bytes_produces_valid_cbor() {
710        let mut map = CborCanonicalMap::new();
711        map.insert("key", CborValue::Text("value".to_string()));
712
713        let bytes = map.to_bytes().expect("should serialize to bytes");
714        assert!(!bytes.is_empty());
715
716        // Deserialize back
717        let deserialized: CborValue =
718            ciborium::de::from_reader(&bytes[..]).expect("should deserialize");
719        if let CborValue::Map(pairs) = deserialized {
720            assert_eq!(pairs.len(), 1);
721            assert_eq!(
722                pairs[0],
723                (
724                    CborValue::Text("key".to_string()),
725                    CborValue::Text("value".to_string())
726                )
727            );
728        } else {
729            panic!("Expected map after deserialization");
730        }
731    }
732
733    #[test]
734    fn to_bytes_sorts_before_serializing() {
735        let mut map = CborCanonicalMap::new();
736        map.insert("beta", CborValue::Integer(2.into()));
737        map.insert("a", CborValue::Integer(1.into()));
738
739        let bytes = map.to_bytes().expect("should serialize");
740        let deserialized: CborValue =
741            ciborium::de::from_reader(&bytes[..]).expect("should deserialize");
742
743        if let CborValue::Map(pairs) = deserialized {
744            let keys: Vec<&str> = pairs.iter().map(|(k, _)| k.as_text().unwrap()).collect();
745            assert_eq!(keys, vec!["a", "beta"]);
746        }
747    }
748
749    #[test]
750    fn to_value_unsorted_preserves_insertion_order() {
751        let mut map = CborCanonicalMap::new();
752        map.insert("z", CborValue::Integer(1.into()));
753        map.insert("a", CborValue::Integer(2.into()));
754
755        let value = map.to_value_unsorted();
756        if let CborValue::Map(pairs) = value {
757            assert_eq!(pairs[0].0, CborValue::Text("z".to_string()));
758            assert_eq!(pairs[1].0, CborValue::Text("a".to_string()));
759        }
760    }
761
762    #[test]
763    fn to_value_sorted_returns_sorted_map() {
764        let mut map = CborCanonicalMap::new();
765        map.insert("beta", CborValue::Integer(2.into()));
766        map.insert("a", CborValue::Integer(1.into()));
767
768        let value = map.to_value_sorted();
769        if let CborValue::Map(pairs) = value {
770            assert_eq!(pairs[0].0, CborValue::Text("a".to_string()));
771            assert_eq!(pairs[1].0, CborValue::Text("beta".to_string()));
772        }
773    }
774
775    #[test]
776    fn to_value_clone_returns_sorted_clone() {
777        let mut map = CborCanonicalMap::new();
778        map.insert("beta", CborValue::Integer(2.into()));
779        map.insert("a", CborValue::Integer(1.into()));
780
781        let value = map.to_value_clone();
782        if let CborValue::Map(pairs) = value {
783            assert_eq!(pairs[0].0, CborValue::Text("a".to_string()));
784            assert_eq!(pairs[1].0, CborValue::Text("beta".to_string()));
785        }
786
787        // Original map should still be accessible (it was sorted in place but not consumed)
788        let val = ValuesCollection::get(&map, &CborValue::Text("a".to_string()));
789        assert!(val.is_some());
790    }
791
792    // TryFrom and From impls
793
794    #[test]
795    fn try_from_cbor_map_succeeds() {
796        let cbor = CborValue::Map(vec![(
797            CborValue::Text("k".to_string()),
798            CborValue::Bool(true),
799        )]);
800
801        let map = CborCanonicalMap::try_from(cbor).expect("should convert from map");
802        assert_eq!(
803            ValuesCollection::get(&map, &CborValue::Text("k".to_string())),
804            Some(&CborValue::Bool(true))
805        );
806    }
807
808    #[test]
809    fn try_from_non_map_fails() {
810        let cbor = CborValue::Text("not a map".to_string());
811        let result = CborCanonicalMap::try_from(cbor);
812        assert!(result.is_err());
813    }
814
815    #[test]
816    fn from_vec_creates_canonical_map() {
817        let vec = vec![(
818            CborValue::Text("x".to_string()),
819            CborValue::Integer(42.into()),
820        )];
821
822        let map: CborCanonicalMap = vec.into();
823        assert_eq!(
824            ValuesCollection::get(&map, &CborValue::Text("x".to_string())),
825            Some(&CborValue::Integer(42.into()))
826        );
827    }
828
829    #[test]
830    fn from_ref_vec_creates_canonical_map() {
831        let vec = vec![(
832            CborValue::Text("y".to_string()),
833            CborValue::Integer(7.into()),
834        )];
835
836        let map: CborCanonicalMap = (&vec).into();
837        assert_eq!(
838            ValuesCollection::get(&map, &CborValue::Text("y".to_string())),
839            Some(&CborValue::Integer(7.into()))
840        );
841    }
842
843    #[test]
844    fn from_btreemap_string_creates_canonical_map() {
845        let mut btree = BTreeMap::new();
846        btree.insert("alpha".to_string(), CborValue::Integer(1.into()));
847        btree.insert("beta".to_string(), CborValue::Integer(2.into()));
848
849        let map: CborCanonicalMap = (&btree).into();
850        assert_eq!(
851            ValuesCollection::get(&map, &CborValue::Text("alpha".to_string())),
852            Some(&CborValue::Integer(1.into()))
853        );
854        assert_eq!(
855            ValuesCollection::get(&map, &CborValue::Text("beta".to_string())),
856            Some(&CborValue::Integer(2.into()))
857        );
858    }
859
860    // ValuesCollection trait impl
861
862    #[test]
863    fn values_collection_get_returns_value() {
864        let map = CborCanonicalMap::from_vector(vec![(
865            CborValue::Text("k".to_string()),
866            CborValue::Text("v".to_string()),
867        )]);
868
869        let result = ValuesCollection::get(&map, &CborValue::Text("k".to_string()));
870        assert_eq!(result, Some(&CborValue::Text("v".to_string())));
871    }
872
873    #[test]
874    fn values_collection_get_returns_none_for_missing() {
875        let map = CborCanonicalMap::new();
876        let result = ValuesCollection::get(&map, &CborValue::Text("missing".to_string()));
877        assert!(result.is_none());
878    }
879
880    #[test]
881    fn values_collection_remove_returns_removed_value() {
882        let mut map = CborCanonicalMap::from_vector(vec![
883            (
884                CborValue::Text("a".to_string()),
885                CborValue::Integer(1.into()),
886            ),
887            (
888                CborValue::Text("b".to_string()),
889                CborValue::Integer(2.into()),
890            ),
891        ]);
892
893        let removed = ValuesCollection::remove(&mut map, "a");
894        assert_eq!(removed, Some(CborValue::Integer(1.into())));
895        assert!(ValuesCollection::get(&map, &CborValue::Text("a".to_string())).is_none());
896    }
897
898    #[test]
899    fn values_collection_remove_returns_none_for_missing() {
900        let mut map = CborCanonicalMap::new();
901        let removed = ValuesCollection::remove(&mut map, "nonexistent");
902        assert!(removed.is_none());
903    }
904
905    // replace_paths (ReplacePaths trait)
906
907    #[test]
908    fn replace_paths_converts_multiple_paths() {
909        let cbor_value = cbor!({
910            "field1" => vec![0_u8; 32],
911            "field2" => vec![1_u8; 32]
912        })
913        .expect("valid cbor");
914
915        let mut canonical: CborCanonicalMap = cbor_value.try_into().expect("valid canonical");
916        ReplacePaths::replace_paths(
917            &mut canonical,
918            vec!["field1", "field2"],
919            FieldType::ArrayInt,
920            FieldType::Bytes,
921        );
922
923        let v1 = ValuesCollection::get(&canonical, &CborValue::Text("field1".to_string()));
924        assert!(matches!(v1, Some(CborValue::Bytes(_))));
925        let v2 = ValuesCollection::get(&canonical, &CborValue::Text("field2".to_string()));
926        assert!(matches!(v2, Some(CborValue::Bytes(_))));
927    }
928
929    #[test]
930    fn replace_path_returns_none_for_nonexistent_path() {
931        let mut map = CborCanonicalMap::new();
932        map.insert("exists", CborValue::Text("value".to_string()));
933
934        let result =
935            ReplacePaths::replace_path(&mut map, "nonexistent", FieldType::Bytes, FieldType::Bytes);
936        assert!(result.is_none());
937    }
938
939    #[test]
940    fn get_path_mut_with_empty_path_returns_none() {
941        let mut map = CborCanonicalMap::new();
942        map.insert("key", CborValue::Integer(1.into()));
943
944        // Empty string results in a path with a single empty-string key
945        // which won't match any real keys typically
946        let result = ReplacePaths::get_path_mut(&mut map, "");
947        // An empty path string still produces a single Key("") step,
948        // which won't match any inserted key
949        assert!(result.is_none());
950    }
951
952    // value_to_bytes tests
953
954    #[test]
955    fn value_to_bytes_from_bytes() {
956        let val = CborValue::Bytes(vec![1, 2, 3, 4]);
957        let result = value_to_bytes(&val).expect("should succeed");
958        assert_eq!(result, Some(vec![1, 2, 3, 4]));
959    }
960
961    #[test]
962    fn value_to_bytes_from_valid_base58_text() {
963        let original = vec![1, 2, 3, 4, 5];
964        let encoded = bs58::encode(&original).into_string();
965        let val = CborValue::Text(encoded);
966
967        let result = value_to_bytes(&val).expect("should succeed");
968        assert_eq!(result, Some(original));
969    }
970
971    #[test]
972    fn value_to_bytes_from_invalid_base58_text_returns_none() {
973        // "0OIl" contains characters invalid in base58
974        let val = CborValue::Text("0OIl!!!".to_string());
975        let result = value_to_bytes(&val).expect("should succeed");
976        assert_eq!(result, None);
977    }
978
979    #[test]
980    fn value_to_bytes_from_integer_array() {
981        let val = CborValue::Array(vec![
982            CborValue::Integer(10.into()),
983            CborValue::Integer(20.into()),
984            CborValue::Integer(30.into()),
985        ]);
986
987        let result = value_to_bytes(&val).expect("should succeed");
988        assert_eq!(result, Some(vec![10, 20, 30]));
989    }
990
991    #[test]
992    fn value_to_bytes_from_array_with_non_integer_fails() {
993        let val = CborValue::Array(vec![
994            CborValue::Integer(1.into()),
995            CborValue::Text("not an int".to_string()),
996        ]);
997
998        let result = value_to_bytes(&val);
999        assert!(result.is_err());
1000    }
1001
1002    #[test]
1003    fn value_to_bytes_from_bool_fails() {
1004        let val = CborValue::Bool(true);
1005        let result = value_to_bytes(&val);
1006        assert!(result.is_err());
1007    }
1008
1009    #[test]
1010    fn value_to_bytes_from_null_fails() {
1011        let val = CborValue::Null;
1012        let result = value_to_bytes(&val);
1013        assert!(result.is_err());
1014    }
1015
1016    // value_to_hash tests
1017
1018    #[test]
1019    fn value_to_hash_from_32_bytes() {
1020        let bytes = vec![42u8; 32];
1021        let val = CborValue::Bytes(bytes.clone());
1022
1023        let result = value_to_hash(&val).expect("should succeed");
1024        assert_eq!(result, [42u8; 32]);
1025    }
1026
1027    #[test]
1028    fn value_to_hash_from_wrong_length_bytes_fails() {
1029        let val = CborValue::Bytes(vec![1u8; 16]);
1030        let result = value_to_hash(&val);
1031        assert!(result.is_err());
1032    }
1033
1034    #[test]
1035    fn value_to_hash_from_valid_base58_text_32_bytes() {
1036        let original = [7u8; 32];
1037        let encoded = bs58::encode(&original).into_string();
1038        let val = CborValue::Text(encoded);
1039
1040        let result = value_to_hash(&val).expect("should succeed");
1041        assert_eq!(result, original);
1042    }
1043
1044    #[test]
1045    fn value_to_hash_from_invalid_base58_text_fails() {
1046        let val = CborValue::Text("!!!invalid!!!".to_string());
1047        let result = value_to_hash(&val);
1048        assert!(result.is_err());
1049    }
1050
1051    #[test]
1052    fn value_to_hash_from_base58_text_wrong_length_fails() {
1053        // Valid base58 but only 4 bytes
1054        let encoded = bs58::encode(&[1u8; 4]).into_string();
1055        let val = CborValue::Text(encoded);
1056        let result = value_to_hash(&val);
1057        assert!(result.is_err());
1058    }
1059
1060    #[test]
1061    fn value_to_hash_from_integer_array_32_bytes() {
1062        let val = CborValue::Array((0..32).map(|i| CborValue::Integer(i.into())).collect());
1063
1064        let result = value_to_hash(&val).expect("should succeed");
1065        let expected: [u8; 32] = (0u8..32).collect::<Vec<u8>>().try_into().unwrap();
1066        assert_eq!(result, expected);
1067    }
1068
1069    #[test]
1070    fn value_to_hash_from_integer_array_wrong_length_fails() {
1071        let val = CborValue::Array(vec![CborValue::Integer(1.into()); 10]);
1072        let result = value_to_hash(&val);
1073        assert!(result.is_err());
1074    }
1075
1076    #[test]
1077    fn value_to_hash_from_array_with_non_integer_fails() {
1078        let mut arr: Vec<CborValue> = (0..31).map(|i| CborValue::Integer(i.into())).collect();
1079        arr.push(CborValue::Text("not_int".to_string()));
1080        let val = CborValue::Array(arr);
1081
1082        let result = value_to_hash(&val);
1083        assert!(result.is_err());
1084    }
1085
1086    #[test]
1087    fn value_to_hash_from_bool_fails() {
1088        let val = CborValue::Bool(false);
1089        let result = value_to_hash(&val);
1090        assert!(result.is_err());
1091    }
1092
1093    // Round-trip: to_bytes and back
1094
1095    #[test]
1096    fn round_trip_canonical_map_through_bytes() {
1097        let mut map = CborCanonicalMap::new();
1098        map.insert("name", CborValue::Text("Alice".to_string()));
1099        map.insert("age", CborValue::Integer(30.into()));
1100        map.insert("active", CborValue::Bool(true));
1101
1102        let bytes = map.to_bytes().expect("should serialize");
1103
1104        let decoded: CborValue = ciborium::de::from_reader(&bytes[..]).expect("should deserialize");
1105        let decoded_map = CborCanonicalMap::try_from(decoded).expect("should convert to map");
1106
1107        assert_eq!(
1108            ValuesCollection::get(&decoded_map, &CborValue::Text("name".to_string())),
1109            Some(&CborValue::Text("Alice".to_string()))
1110        );
1111        assert_eq!(
1112            ValuesCollection::get(&decoded_map, &CborValue::Text("age".to_string())),
1113            Some(&CborValue::Integer(30.into()))
1114        );
1115        assert_eq!(
1116            ValuesCollection::get(&decoded_map, &CborValue::Text("active".to_string())),
1117            Some(&CborValue::Bool(true))
1118        );
1119    }
1120
1121    #[test]
1122    fn canonical_sort_with_same_length_keys_uses_lexicographic_order() {
1123        let mut map = CborCanonicalMap::new();
1124        map.insert("cc", CborValue::Integer(1.into()));
1125        map.insert("bb", CborValue::Integer(2.into()));
1126        map.insert("aa", CborValue::Integer(3.into()));
1127
1128        map.sort_canonical();
1129
1130        let value = map.to_value_unsorted();
1131        if let CborValue::Map(pairs) = value {
1132            let keys: Vec<&str> = pairs.iter().map(|(k, _)| k.as_text().unwrap()).collect();
1133            assert_eq!(keys, vec!["aa", "bb", "cc"]);
1134        }
1135    }
1136
1137    #[test]
1138    fn canonical_sort_shorter_keys_come_first() {
1139        let mut map = CborCanonicalMap::new();
1140        map.insert("zzz", CborValue::Integer(1.into()));
1141        map.insert("a", CborValue::Integer(2.into()));
1142        map.insert("bb", CborValue::Integer(3.into()));
1143
1144        map.sort_canonical();
1145
1146        let value = map.to_value_unsorted();
1147        if let CborValue::Map(pairs) = value {
1148            let keys: Vec<&str> = pairs.iter().map(|(k, _)| k.as_text().unwrap()).collect();
1149            assert_eq!(keys, vec!["a", "bb", "zzz"]);
1150        }
1151    }
1152}