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