dpp/util/cbor_value/
map.rs

1use ciborium::value::Value as CborValue;
2use std::borrow::Borrow;
3use std::convert::TryFrom;
4use std::iter::FromIterator;
5use std::{collections::BTreeMap, convert::TryInto};
6
7use crate::util::cbor_value::value_to_hash;
8use crate::ProtocolError;
9
10pub trait CborBTreeMapHelper {
11    fn get_optional_identifier(&self, key: &str) -> Result<Option<[u8; 32]>, ProtocolError>;
12    fn get_identifier(&self, key: &str) -> Result<[u8; 32], ProtocolError>;
13    fn get_optional_string(&self, key: &str) -> Result<Option<String>, ProtocolError>;
14    fn get_string(&self, key: &str) -> Result<String, ProtocolError>;
15    fn get_optional_str(&self, key: &str) -> Result<Option<&str>, ProtocolError>;
16    fn get_str(&self, key: &str) -> Result<&str, ProtocolError>;
17    fn get_optional_integer<T: TryFrom<i128>>(&self, key: &str)
18        -> Result<Option<T>, ProtocolError>;
19    fn get_integer<T: TryFrom<i128>>(&self, key: &str) -> Result<T, ProtocolError>;
20    fn get_optional_bool(&self, key: &str) -> Result<Option<bool>, ProtocolError>;
21    fn get_bool(&self, key: &str) -> Result<bool, ProtocolError>;
22    fn get_optional_inner_value_array<'a, I: FromIterator<&'a CborValue>>(
23        &'a self,
24        key: &str,
25    ) -> Result<Option<I>, ProtocolError>;
26    fn get_inner_value_array<'a, I: FromIterator<&'a CborValue>>(
27        &'a self,
28        key: &str,
29    ) -> Result<I, ProtocolError>;
30    fn get_optional_inner_string_array<I: FromIterator<String>>(
31        &self,
32        key: &str,
33    ) -> Result<Option<I>, ProtocolError>;
34    fn get_inner_string_array<I: FromIterator<String>>(
35        &self,
36        key: &str,
37    ) -> Result<I, ProtocolError>;
38    fn get_optional_inner_borrowed_str_value_map<'a, I: FromIterator<(String, &'a CborValue)>>(
39        &'a self,
40        key: &str,
41    ) -> Result<Option<I>, ProtocolError>;
42    fn get_optional_inner_borrowed_map(
43        &self,
44        key: &str,
45    ) -> Result<Option<&Vec<(CborValue, CborValue)>>, ProtocolError>;
46    fn get_inner_borrowed_str_value_map<'a, I: FromIterator<(String, &'a CborValue)>>(
47        &'a self,
48        key: &str,
49    ) -> Result<I, ProtocolError>;
50
51    fn remove_optional_integer<T: TryFrom<i128>>(
52        &mut self,
53        key: &str,
54    ) -> Result<Option<T>, ProtocolError>;
55    fn remove_integer<T: TryFrom<i128>>(&mut self, key: &str) -> Result<T, ProtocolError>;
56}
57
58pub trait CborMapExtension {
59    fn as_u16(&self, key: &str, error_message: &str) -> Result<u16, ProtocolError>;
60    fn as_u8(&self, key: &str, error_message: &str) -> Result<u8, ProtocolError>;
61    fn as_bool(&self, key: &str, error_message: &str) -> Result<bool, ProtocolError>;
62    fn as_bytes(&self, key: &str, error_message: &str) -> Result<Vec<u8>, ProtocolError>;
63    fn as_string(&self, key: &str, error_message: &str) -> Result<String, ProtocolError>;
64    fn as_u64(&self, key: &str, error_message: &str) -> Result<u64, ProtocolError>;
65}
66
67impl<V> CborBTreeMapHelper for BTreeMap<String, V>
68where
69    V: Borrow<CborValue>,
70{
71    fn get_optional_identifier(&self, key: &str) -> Result<Option<[u8; 32]>, ProtocolError> {
72        self.get(key).map(|i| value_to_hash(i.borrow())).transpose()
73    }
74
75    fn get_identifier(&self, key: &str) -> Result<[u8; 32], ProtocolError> {
76        self.get_optional_identifier(key)?.ok_or_else(|| {
77            ProtocolError::DecodingError(format!("unable to get identifier property {key}"))
78        })
79    }
80
81    fn get_optional_string(&self, key: &str) -> Result<Option<String>, ProtocolError> {
82        self.get(key)
83            .map(|v| {
84                v.borrow()
85                    .as_text()
86                    .map(|str| str.to_string())
87                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a string")))
88            })
89            .transpose()
90    }
91
92    fn get_string(&self, key: &str) -> Result<String, ProtocolError> {
93        self.get_optional_string(key)?.ok_or_else(|| {
94            ProtocolError::DecodingError(format!("unable to get string property {key}"))
95        })
96    }
97
98    fn get_optional_str(&self, key: &str) -> Result<Option<&str>, ProtocolError> {
99        self.get(key)
100            .map(|v| {
101                v.borrow()
102                    .as_text()
103                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a string")))
104            })
105            .transpose()
106    }
107
108    fn get_str(&self, key: &str) -> Result<&str, ProtocolError> {
109        self.get_optional_str(key)?.ok_or_else(|| {
110            ProtocolError::DecodingError(format!("unable to get str property {key}"))
111        })
112    }
113
114    fn get_optional_integer<T: TryFrom<i128>>(
115        &self,
116        key: &str,
117    ) -> Result<Option<T>, ProtocolError> {
118        self.get(key)
119            .map(|v| {
120                if v.borrow().is_null() {
121                    Ok::<Option<Result<T, ProtocolError>>, ProtocolError>(None)
122                } else {
123                    Ok(Some(
124                        i128::from(v.borrow().as_integer().ok_or_else(|| {
125                            ProtocolError::DecodingError(format!("{key} must be an integer"))
126                        })?)
127                        .try_into()
128                        .map_err(|_| {
129                            ProtocolError::DecodingError(format!("{key} is out of required bounds"))
130                        }),
131                    ))
132                }
133            })
134            .transpose()?
135            .flatten()
136            .transpose()
137    }
138
139    fn get_integer<T: TryFrom<i128>>(&self, key: &str) -> Result<T, ProtocolError> {
140        self.get_optional_integer(key)?.ok_or_else(|| {
141            ProtocolError::DecodingError(format!("unable to get integer property {key}"))
142        })
143    }
144
145    fn get_optional_bool(&self, key: &str) -> Result<Option<bool>, ProtocolError> {
146        self.get(key)
147            .map(|v| {
148                v.borrow()
149                    .as_bool()
150                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a bool")))
151            })
152            .transpose()
153    }
154
155    fn get_bool(&self, key: &str) -> Result<bool, ProtocolError> {
156        self.get_optional_bool(key)?.ok_or_else(|| {
157            ProtocolError::DecodingError(format!("unable to get bool property {key}"))
158        })
159    }
160
161    fn remove_optional_integer<T: TryFrom<i128>>(
162        &mut self,
163        key: &str,
164    ) -> Result<Option<T>, ProtocolError> {
165        self.remove(key)
166            .map(|v| {
167                if v.borrow().is_null() {
168                    Ok::<Option<Result<T, ProtocolError>>, ProtocolError>(None)
169                } else {
170                    Ok(Some(
171                        i128::from(v.borrow().as_integer().ok_or_else(|| {
172                            ProtocolError::DecodingError(format!("{key} must be an integer"))
173                        })?)
174                        .try_into()
175                        .map_err(|_| {
176                            ProtocolError::DecodingError(format!("{key} is out of required bounds"))
177                        }),
178                    ))
179                }
180            })
181            .transpose()?
182            .flatten()
183            .transpose()
184    }
185
186    fn remove_integer<T: TryFrom<i128>>(&mut self, key: &str) -> Result<T, ProtocolError> {
187        self.remove_optional_integer(key)?.ok_or_else(|| {
188            ProtocolError::DecodingError(format!("unable to remove integer property {key}"))
189        })
190    }
191
192    fn get_optional_inner_value_array<'a, I: FromIterator<&'a CborValue>>(
193        &'a self,
194        key: &str,
195    ) -> Result<Option<I>, ProtocolError> {
196        self.get(key)
197            .map(|v| {
198                v.borrow()
199                    .as_array()
200                    .map(|vec| vec.iter().collect())
201                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a bool")))
202            })
203            .transpose()
204    }
205
206    fn get_inner_value_array<'a, I: FromIterator<&'a CborValue>>(
207        &'a self,
208        key: &str,
209    ) -> Result<I, ProtocolError> {
210        self.get_optional_inner_value_array(key)?.ok_or_else(|| {
211            ProtocolError::DecodingError(format!("unable to get inner value array property {key}"))
212        })
213    }
214
215    fn get_optional_inner_string_array<I: FromIterator<String>>(
216        &self,
217        key: &str,
218    ) -> Result<Option<I>, ProtocolError> {
219        self.get(key)
220            .map(|v| {
221                v.borrow()
222                    .as_array()
223                    .map(|inner| {
224                        inner
225                            .iter()
226                            .map(|v| {
227                                let Some(str) = v.as_text() else {
228                                    return Err(ProtocolError::DecodingError(format!(
229                                        "{key} must be an string"
230                                    )));
231                                };
232                                Ok(str.to_string())
233                            })
234                            .collect::<Result<I, ProtocolError>>()
235                    })
236                    .transpose()?
237                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a bool")))
238            })
239            .transpose()
240    }
241
242    fn get_inner_string_array<I: FromIterator<String>>(
243        &self,
244        key: &str,
245    ) -> Result<I, ProtocolError> {
246        self.get_optional_inner_string_array(key)?.ok_or_else(|| {
247            ProtocolError::DecodingError(format!("unable to get inner string property {key}"))
248        })
249    }
250
251    fn get_optional_inner_borrowed_str_value_map<
252        'a,
253        I: FromIterator<(String, &'a ciborium::Value)>,
254    >(
255        &'a self,
256        key: &str,
257    ) -> Result<Option<I>, ProtocolError> {
258        self.get(key)
259            .map(|v| {
260                v.borrow()
261                    .as_map()
262                    .map(|inner| {
263                        inner
264                            .iter()
265                            .map(|(k, v)| {
266                                let Some(str) = k.as_text() else {
267                                    return Err(ProtocolError::DecodingError(format!(
268                                        "{key} must be an string"
269                                    )));
270                                };
271                                Ok((str.to_string(), v))
272                            })
273                            .collect::<Result<I, ProtocolError>>()
274                    })
275                    .transpose()?
276                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a bool")))
277            })
278            .transpose()
279    }
280
281    fn get_optional_inner_borrowed_map(
282        &self,
283        key: &str,
284    ) -> Result<Option<&Vec<(CborValue, CborValue)>>, ProtocolError> {
285        self.get(key)
286            .map(|v| {
287                v.borrow()
288                    .as_map()
289                    .ok_or_else(|| ProtocolError::DecodingError(format!("{key} must be a map")))
290            })
291            .transpose()
292    }
293
294    fn get_inner_borrowed_str_value_map<'a, I: FromIterator<(String, &'a ciborium::Value)>>(
295        &'a self,
296        key: &str,
297    ) -> Result<I, ProtocolError> {
298        self.get_optional_inner_borrowed_str_value_map(key)?
299            .ok_or_else(|| {
300                ProtocolError::DecodingError(format!(
301                    "unable to get borrowed str value map property {key}"
302                ))
303            })
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use ciborium::value::Value as CborValue;
311    use std::collections::BTreeMap;
312
313    fn make_map(pairs: Vec<(&str, CborValue)>) -> BTreeMap<String, CborValue> {
314        pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
315    }
316
317    // --- get_optional_string / get_string ---
318
319    #[test]
320    fn get_optional_string_present() {
321        let map = make_map(vec![("name", CborValue::Text("Alice".to_string()))]);
322        let result = map.get_optional_string("name").unwrap();
323        assert_eq!(result, Some("Alice".to_string()));
324    }
325
326    #[test]
327    fn get_optional_string_absent() {
328        let map: BTreeMap<String, CborValue> = BTreeMap::new();
329        let result = map.get_optional_string("name").unwrap();
330        assert_eq!(result, None);
331    }
332
333    #[test]
334    fn get_optional_string_wrong_type() {
335        let map = make_map(vec![("name", CborValue::Integer(42.into()))]);
336        let result = map.get_optional_string("name");
337        assert!(result.is_err());
338    }
339
340    #[test]
341    fn get_string_present() {
342        let map = make_map(vec![("name", CborValue::Text("Bob".to_string()))]);
343        let result = map.get_string("name").unwrap();
344        assert_eq!(result, "Bob");
345    }
346
347    #[test]
348    fn get_string_absent() {
349        let map: BTreeMap<String, CborValue> = BTreeMap::new();
350        let result = map.get_string("name");
351        assert!(result.is_err());
352    }
353
354    // --- get_optional_str / get_str ---
355
356    #[test]
357    fn get_optional_str_present() {
358        let map = make_map(vec![("key", CborValue::Text("value".to_string()))]);
359        let result = map.get_optional_str("key").unwrap();
360        assert_eq!(result, Some("value"));
361    }
362
363    #[test]
364    fn get_optional_str_absent() {
365        let map: BTreeMap<String, CborValue> = BTreeMap::new();
366        let result = map.get_optional_str("key").unwrap();
367        assert_eq!(result, None);
368    }
369
370    #[test]
371    fn get_str_present() {
372        let map = make_map(vec![("key", CborValue::Text("val".to_string()))]);
373        let result = map.get_str("key").unwrap();
374        assert_eq!(result, "val");
375    }
376
377    #[test]
378    fn get_str_absent() {
379        let map: BTreeMap<String, CborValue> = BTreeMap::new();
380        let result = map.get_str("key");
381        assert!(result.is_err());
382    }
383
384    // --- get_optional_integer / get_integer ---
385
386    #[test]
387    fn get_optional_integer_present() {
388        let map = make_map(vec![("count", CborValue::Integer(42.into()))]);
389        let result: Option<i64> = map.get_optional_integer("count").unwrap();
390        assert_eq!(result, Some(42));
391    }
392
393    #[test]
394    fn get_optional_integer_absent() {
395        let map: BTreeMap<String, CborValue> = BTreeMap::new();
396        let result: Option<i64> = map.get_optional_integer("count").unwrap();
397        assert_eq!(result, None);
398    }
399
400    #[test]
401    fn get_optional_integer_null_value() {
402        let map = make_map(vec![("count", CborValue::Null)]);
403        let result: Option<i64> = map.get_optional_integer("count").unwrap();
404        assert_eq!(result, None);
405    }
406
407    #[test]
408    fn get_optional_integer_wrong_type() {
409        let map = make_map(vec![("count", CborValue::Text("not_int".to_string()))]);
410        let result: Result<Option<i64>, _> = map.get_optional_integer("count");
411        assert!(result.is_err());
412    }
413
414    #[test]
415    fn get_integer_present() {
416        let map = make_map(vec![("count", CborValue::Integer(100.into()))]);
417        let result: i64 = map.get_integer("count").unwrap();
418        assert_eq!(result, 100);
419    }
420
421    #[test]
422    fn get_integer_absent() {
423        let map: BTreeMap<String, CborValue> = BTreeMap::new();
424        let result: Result<i64, _> = map.get_integer("count");
425        assert!(result.is_err());
426    }
427
428    // --- get_optional_bool / get_bool ---
429
430    #[test]
431    fn get_optional_bool_present() {
432        let map = make_map(vec![("flag", CborValue::Bool(true))]);
433        let result = map.get_optional_bool("flag").unwrap();
434        assert_eq!(result, Some(true));
435    }
436
437    #[test]
438    fn get_optional_bool_absent() {
439        let map: BTreeMap<String, CborValue> = BTreeMap::new();
440        let result = map.get_optional_bool("flag").unwrap();
441        assert_eq!(result, None);
442    }
443
444    #[test]
445    fn get_optional_bool_wrong_type() {
446        let map = make_map(vec![("flag", CborValue::Integer(1.into()))]);
447        let result = map.get_optional_bool("flag");
448        assert!(result.is_err());
449    }
450
451    #[test]
452    fn get_bool_present() {
453        let map = make_map(vec![("flag", CborValue::Bool(false))]);
454        let result = map.get_bool("flag").unwrap();
455        assert!(!result);
456    }
457
458    #[test]
459    fn get_bool_absent() {
460        let map: BTreeMap<String, CborValue> = BTreeMap::new();
461        let result = map.get_bool("flag");
462        assert!(result.is_err());
463    }
464
465    // --- get_optional_identifier / get_identifier ---
466
467    #[test]
468    fn get_optional_identifier_with_bytes() {
469        let id_bytes = [7u8; 32];
470        let map = make_map(vec![("id", CborValue::Bytes(id_bytes.to_vec()))]);
471        let result = map.get_optional_identifier("id").unwrap();
472        assert_eq!(result, Some(id_bytes));
473    }
474
475    #[test]
476    fn get_optional_identifier_absent() {
477        let map: BTreeMap<String, CborValue> = BTreeMap::new();
478        let result = map.get_optional_identifier("id").unwrap();
479        assert_eq!(result, None);
480    }
481
482    #[test]
483    fn get_identifier_present() {
484        let id_bytes = [3u8; 32];
485        let map = make_map(vec![("id", CborValue::Bytes(id_bytes.to_vec()))]);
486        let result = map.get_identifier("id").unwrap();
487        assert_eq!(result, id_bytes);
488    }
489
490    #[test]
491    fn get_identifier_absent() {
492        let map: BTreeMap<String, CborValue> = BTreeMap::new();
493        let result = map.get_identifier("id");
494        assert!(result.is_err());
495    }
496
497    // --- remove_optional_integer / remove_integer ---
498
499    #[test]
500    fn remove_optional_integer_present() {
501        let mut map = make_map(vec![("val", CborValue::Integer(99.into()))]);
502        let result: Option<i64> = map.remove_optional_integer("val").unwrap();
503        assert_eq!(result, Some(99));
504        assert!(!map.contains_key("val"));
505    }
506
507    #[test]
508    fn remove_optional_integer_absent() {
509        let mut map: BTreeMap<String, CborValue> = BTreeMap::new();
510        let result: Option<i64> = map.remove_optional_integer("val").unwrap();
511        assert_eq!(result, None);
512    }
513
514    #[test]
515    fn remove_optional_integer_null() {
516        let mut map = make_map(vec![("val", CborValue::Null)]);
517        let result: Option<i64> = map.remove_optional_integer("val").unwrap();
518        assert_eq!(result, None);
519    }
520
521    #[test]
522    fn remove_integer_present() {
523        let mut map = make_map(vec![("val", CborValue::Integer(50.into()))]);
524        let result: i64 = map.remove_integer("val").unwrap();
525        assert_eq!(result, 50);
526    }
527
528    #[test]
529    fn remove_integer_absent() {
530        let mut map: BTreeMap<String, CborValue> = BTreeMap::new();
531        let result: Result<i64, _> = map.remove_integer("val");
532        assert!(result.is_err());
533    }
534
535    // --- get_optional_inner_value_array / get_inner_value_array ---
536
537    #[test]
538    fn get_optional_inner_value_array_present() {
539        let array = vec![CborValue::Integer(1.into()), CborValue::Integer(2.into())];
540        let map = make_map(vec![("arr", CborValue::Array(array))]);
541        let result: Option<Vec<&CborValue>> = map.get_optional_inner_value_array("arr").unwrap();
542        assert!(result.is_some());
543        assert_eq!(result.unwrap().len(), 2);
544    }
545
546    #[test]
547    fn get_optional_inner_value_array_absent() {
548        let map: BTreeMap<String, CborValue> = BTreeMap::new();
549        let result: Option<Vec<&CborValue>> = map.get_optional_inner_value_array("arr").unwrap();
550        assert!(result.is_none());
551    }
552
553    #[test]
554    fn get_inner_value_array_present() {
555        let array = vec![CborValue::Bool(true)];
556        let map = make_map(vec![("arr", CborValue::Array(array))]);
557        let result: Vec<&CborValue> = map.get_inner_value_array("arr").unwrap();
558        assert_eq!(result.len(), 1);
559    }
560
561    #[test]
562    fn get_inner_value_array_absent() {
563        let map: BTreeMap<String, CborValue> = BTreeMap::new();
564        let result: Result<Vec<&CborValue>, _> = map.get_inner_value_array("arr");
565        assert!(result.is_err());
566    }
567
568    // --- get_optional_inner_string_array / get_inner_string_array ---
569
570    #[test]
571    fn get_optional_inner_string_array_present() {
572        let array = vec![
573            CborValue::Text("hello".to_string()),
574            CborValue::Text("world".to_string()),
575        ];
576        let map = make_map(vec![("strs", CborValue::Array(array))]);
577        let result: Option<Vec<String>> = map.get_optional_inner_string_array("strs").unwrap();
578        assert_eq!(result, Some(vec!["hello".to_string(), "world".to_string()]));
579    }
580
581    #[test]
582    fn get_optional_inner_string_array_with_non_string_element() {
583        let array = vec![
584            CborValue::Text("hello".to_string()),
585            CborValue::Integer(42.into()),
586        ];
587        let map = make_map(vec![("strs", CborValue::Array(array))]);
588        let result: Result<Option<Vec<String>>, _> = map.get_optional_inner_string_array("strs");
589        assert!(result.is_err());
590    }
591
592    #[test]
593    fn get_inner_string_array_absent() {
594        let map: BTreeMap<String, CborValue> = BTreeMap::new();
595        let result: Result<Vec<String>, _> = map.get_inner_string_array("strs");
596        assert!(result.is_err());
597    }
598
599    // --- get_optional_inner_borrowed_map ---
600
601    #[test]
602    fn get_optional_inner_borrowed_map_present() {
603        let inner_map = vec![(
604            CborValue::Text("k".to_string()),
605            CborValue::Integer(1.into()),
606        )];
607        let map = make_map(vec![("m", CborValue::Map(inner_map))]);
608        let result = map.get_optional_inner_borrowed_map("m").unwrap();
609        assert!(result.is_some());
610        assert_eq!(result.unwrap().len(), 1);
611    }
612
613    #[test]
614    fn get_optional_inner_borrowed_map_absent() {
615        let map: BTreeMap<String, CborValue> = BTreeMap::new();
616        let result = map.get_optional_inner_borrowed_map("m").unwrap();
617        assert!(result.is_none());
618    }
619
620    #[test]
621    fn get_optional_inner_borrowed_map_wrong_type() {
622        let map = make_map(vec![("m", CborValue::Integer(1.into()))]);
623        let result = map.get_optional_inner_borrowed_map("m");
624        assert!(result.is_err());
625    }
626
627    // --- get_optional_inner_borrowed_str_value_map / get_inner_borrowed_str_value_map ---
628
629    #[test]
630    fn get_optional_inner_borrowed_str_value_map_present() {
631        let inner_map = vec![(
632            CborValue::Text("key".to_string()),
633            CborValue::Integer(42.into()),
634        )];
635        let map = make_map(vec![("m", CborValue::Map(inner_map))]);
636        let result: Option<BTreeMap<String, &CborValue>> =
637            map.get_optional_inner_borrowed_str_value_map("m").unwrap();
638        assert!(result.is_some());
639        let inner = result.unwrap();
640        assert_eq!(inner.len(), 1);
641        assert!(inner.contains_key("key"));
642    }
643
644    #[test]
645    fn get_inner_borrowed_str_value_map_absent() {
646        let map: BTreeMap<String, CborValue> = BTreeMap::new();
647        let result: Result<BTreeMap<String, &CborValue>, _> =
648            map.get_inner_borrowed_str_value_map("m");
649        assert!(result.is_err());
650    }
651}