platform_value/converter/
ciborium.rs

1use crate::value_map::ValueMap;
2use crate::{Error, Value, ValueMapHelper};
3use ciborium::value::Integer;
4use ciborium::Value as CborValue;
5
6impl Value {
7    pub fn convert_from_cbor_map<I, R>(map: I) -> Result<R, Error>
8    where
9        I: IntoIterator<Item = (String, CborValue)>,
10        R: FromIterator<(String, Value)>,
11    {
12        map.into_iter()
13            .map(|(key, cbor_value)| Ok((key, cbor_value.try_into()?)))
14            .collect()
15    }
16
17    pub fn convert_to_cbor_map<I, R>(map: I) -> Result<R, Error>
18    where
19        I: IntoIterator<Item = (String, Value)>,
20        R: FromIterator<(String, CborValue)>,
21    {
22        map.into_iter()
23            .map(|(key, value)| Ok((key, value.try_into()?)))
24            .collect()
25    }
26
27    pub fn to_cbor_buffer(&self) -> Result<Vec<u8>, Error> {
28        let mut buffer: Vec<u8> = Vec::new();
29        ciborium::ser::into_writer(self, &mut buffer)
30            .map_err(|e| Error::CborSerializationError(e.to_string()))?;
31
32        Ok(buffer)
33    }
34}
35
36impl TryFrom<CborValue> for Value {
37    type Error = Error;
38
39    fn try_from(value: CborValue) -> Result<Self, Error> {
40        Ok(match value {
41            CborValue::Integer(integer) => Self::I128(integer.into()),
42            CborValue::Bytes(bytes) => Self::Bytes(bytes),
43            CborValue::Float(float) => Self::Float(float),
44            CborValue::Text(string) => Self::Text(string),
45            CborValue::Bool(value) => Self::Bool(value),
46            CborValue::Null => Self::Null,
47            CborValue::Tag(_, _) => {
48                return Err(Error::Unsupported(
49                    "conversion from cbor tags are currently not supported".to_string(),
50                ))
51            }
52            CborValue::Array(array) => {
53                let len = array.len();
54                if len > 10
55                    && array.iter().all(|v| {
56                        let Some(int) = v.as_integer() else {
57                            return false;
58                        };
59                        int.le(&Integer::from(u8::MAX)) && int.ge(&Integer::from(0))
60                    })
61                {
62                    //this is an array of bytes
63                    Self::Bytes(
64                        array
65                            .into_iter()
66                            .map(|v| v.into_integer().unwrap().try_into().unwrap())
67                            .collect(),
68                    )
69                } else {
70                    Self::Array(
71                        array
72                            .into_iter()
73                            .map(|v| v.try_into())
74                            .collect::<Result<Vec<Value>, Error>>()?,
75                    )
76                }
77            }
78            CborValue::Map(map) => Self::Map(
79                map.into_iter()
80                    .map(|(k, v)| Ok((k.try_into()?, v.try_into()?)))
81                    .collect::<Result<ValueMap, Error>>()?,
82            ),
83            _ => panic!("unsupported"),
84        })
85    }
86}
87
88impl TryInto<CborValue> for Value {
89    type Error = Error;
90
91    fn try_into(self) -> Result<CborValue, Self::Error> {
92        Ok(match self {
93            Value::U128(i) => CborValue::Integer((i as u64).into()),
94            Value::I128(i) => CborValue::Integer((i as i64).into()),
95            Value::U64(i) => CborValue::Integer(i.into()),
96            Value::I64(i) => CborValue::Integer(i.into()),
97            Value::U32(i) => CborValue::Integer(i.into()),
98            Value::I32(i) => CborValue::Integer(i.into()),
99            Value::U16(i) => CborValue::Integer(i.into()),
100            Value::I16(i) => CborValue::Integer(i.into()),
101            Value::U8(i) => CborValue::Integer(i.into()),
102            Value::I8(i) => CborValue::Integer(i.into()),
103            Value::Bytes(bytes) => CborValue::Bytes(bytes),
104            Value::Bytes20(bytes) => CborValue::Bytes(bytes.to_vec()),
105            Value::Bytes32(bytes) => CborValue::Bytes(bytes.to_vec()),
106            Value::Bytes36(bytes) => CborValue::Bytes(bytes.to_vec()),
107            Value::Float(float) => CborValue::Float(float),
108            Value::Text(string) => CborValue::Text(string),
109            Value::Bool(value) => CborValue::Bool(value),
110            Value::Null => CborValue::Null,
111            Value::Array(array) => CborValue::Array(
112                array
113                    .into_iter()
114                    .map(|value| value.try_into())
115                    .collect::<Result<Vec<CborValue>, Error>>()?,
116            ),
117            Value::Map(mut map) => {
118                map.sort_by_keys();
119                CborValue::Map(
120                    map.into_iter()
121                        .map(|(k, v)| Ok((k.try_into()?, v.try_into()?)))
122                        .collect::<Result<Vec<(CborValue, CborValue)>, Error>>()?,
123                )
124            }
125            Value::Identifier(bytes) => CborValue::Bytes(bytes.to_vec()),
126            Value::EnumU8(_) => {
127                return Err(Error::Unsupported(
128                    "No support for conversion of EnumU8 to JSONValue".to_string(),
129                ))
130            }
131            Value::EnumString(_) => {
132                return Err(Error::Unsupported(
133                    "No support for conversion of EnumString to JSONValue".to_string(),
134                ))
135            }
136        })
137    }
138}
139
140impl TryInto<Box<CborValue>> for Box<Value> {
141    type Error = Error;
142    fn try_into(self) -> Result<Box<CborValue>, Self::Error> {
143        (*self).try_into().map(Box::new)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use crate::{Error, Value};
150    use ciborium::value::Integer;
151    use ciborium::Value as CborValue;
152
153    // -----------------------------------------------------------------------
154    // Round-trip: Value -> CborValue -> Value for basic types
155    // -----------------------------------------------------------------------
156
157    #[test]
158    fn round_trip_null() {
159        let original = Value::Null;
160        let cbor: CborValue = original.clone().try_into().unwrap();
161        assert_eq!(cbor, CborValue::Null);
162        let back: Value = cbor.try_into().unwrap();
163        assert_eq!(back, Value::Null);
164    }
165
166    #[test]
167    fn round_trip_bool_true() {
168        let original = Value::Bool(true);
169        let cbor: CborValue = original.clone().try_into().unwrap();
170        assert_eq!(cbor, CborValue::Bool(true));
171        let back: Value = cbor.try_into().unwrap();
172        // Comes back as I128(1) since CBOR integers are unified
173        assert_eq!(back, Value::Bool(true));
174    }
175
176    #[test]
177    fn round_trip_bool_false() {
178        let original = Value::Bool(false);
179        let cbor: CborValue = original.clone().try_into().unwrap();
180        let back: Value = cbor.try_into().unwrap();
181        assert_eq!(back, Value::Bool(false));
182    }
183
184    #[test]
185    fn round_trip_text() {
186        let original = Value::Text("hello world".into());
187        let cbor: CborValue = original.clone().try_into().unwrap();
188        assert_eq!(cbor, CborValue::Text("hello world".into()));
189        let back: Value = cbor.try_into().unwrap();
190        assert_eq!(back, original);
191    }
192
193    #[test]
194    fn round_trip_float() {
195        let original = Value::Float(3.14);
196        let cbor: CborValue = original.clone().try_into().unwrap();
197        assert_eq!(cbor, CborValue::Float(3.14));
198        let back: Value = cbor.try_into().unwrap();
199        assert_eq!(back, original);
200    }
201
202    #[test]
203    fn round_trip_bytes() {
204        let original = Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
205        let cbor: CborValue = original.clone().try_into().unwrap();
206        assert_eq!(cbor, CborValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]));
207        let back: Value = cbor.try_into().unwrap();
208        assert_eq!(back, original);
209    }
210
211    #[test]
212    fn round_trip_u64() {
213        let original = Value::U64(42);
214        let cbor: CborValue = original.clone().try_into().unwrap();
215        // Comes back as Integer
216        let back: Value = cbor.try_into().unwrap();
217        // CBOR integers come back as I128
218        assert_eq!(back, Value::I128(42));
219    }
220
221    #[test]
222    fn round_trip_i64_negative() {
223        let original = Value::I64(-99);
224        let cbor: CborValue = original.clone().try_into().unwrap();
225        let back: Value = cbor.try_into().unwrap();
226        assert_eq!(back, Value::I128(-99));
227    }
228
229    // -----------------------------------------------------------------------
230    // Tag rejection in TryFrom<CborValue>
231    // -----------------------------------------------------------------------
232
233    #[test]
234    fn cbor_tag_rejected() {
235        let tagged = CborValue::Tag(42, Box::new(CborValue::Null));
236        let result: Result<Value, Error> = tagged.try_into();
237        assert!(matches!(result, Err(Error::Unsupported(_))));
238    }
239
240    #[test]
241    fn cbor_tag_rejection_message() {
242        let tagged = CborValue::Tag(0, Box::new(CborValue::Text("date".into())));
243        let err = Value::try_from(tagged).unwrap_err();
244        match err {
245            Error::Unsupported(msg) => {
246                assert!(msg.contains("tag"), "error message should mention tags");
247            }
248            _ => panic!("expected Unsupported error"),
249        }
250    }
251
252    // -----------------------------------------------------------------------
253    // Byte-array heuristic boundary (10 vs 11 integer elements)
254    // Note: the CBOR heuristic uses > 10 (strictly greater), unlike JSON's >= 10
255    // -----------------------------------------------------------------------
256
257    #[test]
258    fn cbor_array_10_integers_stays_array() {
259        // Exactly 10 elements -> stays as Array (boundary: > 10 needed for bytes)
260        let arr: Vec<CborValue> = (0..10)
261            .map(|i| CborValue::Integer(Integer::from(i as u8)))
262            .collect();
263        let cbor = CborValue::Array(arr);
264        let val: Value = cbor.try_into().unwrap();
265        assert!(
266            matches!(val, Value::Array(_)),
267            "10 elements should stay as Array in CBOR heuristic"
268        );
269    }
270
271    #[test]
272    fn cbor_array_11_integers_becomes_bytes() {
273        // 11 elements, all in u8 range -> becomes Bytes
274        let arr: Vec<CborValue> = (0..11)
275            .map(|i| CborValue::Integer(Integer::from(i as u8)))
276            .collect();
277        let cbor = CborValue::Array(arr);
278        let val: Value = cbor.try_into().unwrap();
279        assert!(
280            matches!(val, Value::Bytes(_)),
281            "11 elements of u8-range integers should become Bytes"
282        );
283        if let Value::Bytes(bytes) = val {
284            assert_eq!(bytes.len(), 11);
285            assert_eq!(bytes[0], 0);
286            assert_eq!(bytes[10], 10);
287        }
288    }
289
290    #[test]
291    fn cbor_array_mixed_types_stays_array() {
292        // 12 elements but mixed types -> stays as Array
293        let mut arr: Vec<CborValue> = (0..11)
294            .map(|i| CborValue::Integer(Integer::from(i as u8)))
295            .collect();
296        arr.push(CborValue::Text("not an int".into()));
297        let cbor = CborValue::Array(arr);
298        let val: Value = cbor.try_into().unwrap();
299        assert!(matches!(val, Value::Array(_)));
300    }
301
302    #[test]
303    fn cbor_array_negative_values_stays_array() {
304        // Negative values are not in 0..=u8::MAX range
305        let arr: Vec<CborValue> = (0..12)
306            .map(|i| CborValue::Integer(Integer::from(-(i as i64))))
307            .collect();
308        let cbor = CborValue::Array(arr);
309        let val: Value = cbor.try_into().unwrap();
310        // First element is 0 which is fine, but most are negative -> fails the ge(0) check
311        assert!(matches!(val, Value::Array(_)));
312    }
313
314    // -----------------------------------------------------------------------
315    // Map key sorting in TryInto<CborValue>
316    // -----------------------------------------------------------------------
317
318    #[test]
319    fn map_keys_sorted_in_cbor_output() {
320        // Keys inserted in reverse order should be sorted in output
321        let map = vec![
322            (Value::Text("z".into()), Value::U64(1)),
323            (Value::Text("a".into()), Value::U64(2)),
324            (Value::Text("m".into()), Value::U64(3)),
325        ];
326        let val = Value::Map(map);
327        let cbor: CborValue = val.try_into().unwrap();
328        if let CborValue::Map(pairs) = cbor {
329            let keys: Vec<String> = pairs
330                .iter()
331                .map(|(k, _)| {
332                    if let CborValue::Text(s) = k {
333                        s.clone()
334                    } else {
335                        panic!("expected text key")
336                    }
337                })
338                .collect();
339            assert_eq!(keys, vec!["a", "m", "z"]);
340        } else {
341            panic!("expected CborValue::Map");
342        }
343    }
344
345    // -----------------------------------------------------------------------
346    // EnumU8 / EnumString error paths
347    // -----------------------------------------------------------------------
348
349    #[test]
350    fn enum_u8_to_cbor_error() {
351        let val = Value::EnumU8(vec![1, 2, 3]);
352        let result: Result<CborValue, Error> = val.try_into();
353        assert!(matches!(result, Err(Error::Unsupported(_))));
354    }
355
356    #[test]
357    fn enum_string_to_cbor_error() {
358        let val = Value::EnumString(vec!["a".into(), "b".into()]);
359        let result: Result<CborValue, Error> = val.try_into();
360        assert!(matches!(result, Err(Error::Unsupported(_))));
361    }
362
363    // -----------------------------------------------------------------------
364    // U128 / I128 narrowing in TryInto<CborValue>
365    // -----------------------------------------------------------------------
366
367    #[test]
368    fn u128_narrowed_to_u64_in_cbor() {
369        // U128 is cast to u64 when converting to CborValue::Integer
370        let val = Value::U128(42);
371        let cbor: CborValue = val.try_into().unwrap();
372        assert_eq!(cbor, CborValue::Integer(42u64.into()));
373    }
374
375    #[test]
376    fn i128_narrowed_to_i64_in_cbor() {
377        // I128 is cast to i64 when converting to CborValue::Integer
378        let val = Value::I128(-99);
379        let cbor: CborValue = val.try_into().unwrap();
380        assert_eq!(cbor, CborValue::Integer((-99i64).into()));
381    }
382
383    // -----------------------------------------------------------------------
384    // Integer variant from CBOR -> Value
385    // -----------------------------------------------------------------------
386
387    #[test]
388    fn cbor_positive_integer_to_i128() {
389        let cbor = CborValue::Integer(Integer::from(255u8));
390        let val: Value = cbor.try_into().unwrap();
391        assert_eq!(val, Value::I128(255));
392    }
393
394    #[test]
395    fn cbor_negative_integer_to_i128() {
396        let cbor = CborValue::Integer(Integer::from(-1i64));
397        let val: Value = cbor.try_into().unwrap();
398        assert_eq!(val, Value::I128(-1));
399    }
400
401    #[test]
402    fn cbor_zero_integer_to_i128() {
403        let cbor = CborValue::Integer(Integer::from(0));
404        let val: Value = cbor.try_into().unwrap();
405        assert_eq!(val, Value::I128(0));
406    }
407
408    // -----------------------------------------------------------------------
409    // Bytes20 / Bytes32 / Bytes36 / Identifier -> CborValue::Bytes
410    // -----------------------------------------------------------------------
411
412    #[test]
413    fn bytes20_to_cbor_bytes() {
414        let bytes = [0xAAu8; 20];
415        let val = Value::Bytes20(bytes);
416        let cbor: CborValue = val.try_into().unwrap();
417        assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
418    }
419
420    #[test]
421    fn bytes32_to_cbor_bytes() {
422        let bytes = [0xBBu8; 32];
423        let val = Value::Bytes32(bytes);
424        let cbor: CborValue = val.try_into().unwrap();
425        assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
426    }
427
428    #[test]
429    fn bytes36_to_cbor_bytes() {
430        let bytes = [0xCCu8; 36];
431        let val = Value::Bytes36(bytes);
432        let cbor: CborValue = val.try_into().unwrap();
433        assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
434    }
435
436    #[test]
437    fn identifier_to_cbor_bytes() {
438        let bytes = [0x01u8; 32];
439        let val = Value::Identifier(bytes);
440        let cbor: CborValue = val.try_into().unwrap();
441        assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
442    }
443
444    // -----------------------------------------------------------------------
445    // Integer types round through CBOR
446    // -----------------------------------------------------------------------
447
448    #[test]
449    fn all_integer_types_to_cbor() {
450        let cases: Vec<Value> = vec![
451            Value::U8(255),
452            Value::I8(-128),
453            Value::U16(65535),
454            Value::I16(-32768),
455            Value::U32(u32::MAX),
456            Value::I32(i32::MIN),
457            Value::U64(u64::MAX),
458            Value::I64(i64::MIN),
459        ];
460        for val in cases {
461            let cbor: CborValue = val.clone().try_into().unwrap();
462            assert!(
463                matches!(cbor, CborValue::Integer(_)),
464                "expected Integer for {:?}",
465                val
466            );
467        }
468    }
469
470    // -----------------------------------------------------------------------
471    // Box<Value> TryInto<Box<CborValue>>
472    // -----------------------------------------------------------------------
473
474    #[test]
475    fn boxed_value_to_boxed_cbor() {
476        let val = Box::new(Value::Text("boxed".into()));
477        let cbor: Box<CborValue> = val.try_into().unwrap();
478        assert_eq!(*cbor, CborValue::Text("boxed".into()));
479    }
480
481    // -----------------------------------------------------------------------
482    // CBOR Map conversion
483    // -----------------------------------------------------------------------
484
485    #[test]
486    fn cbor_map_to_value_map() {
487        let cbor = CborValue::Map(vec![
488            (
489                CborValue::Text("key".into()),
490                CborValue::Integer(42u64.into()),
491            ),
492            (CborValue::Text("flag".into()), CborValue::Bool(true)),
493        ]);
494        let val: Value = cbor.try_into().unwrap();
495        assert!(val.is_map());
496    }
497
498    // -----------------------------------------------------------------------
499    // convert_from_cbor_map / convert_to_cbor_map
500    // -----------------------------------------------------------------------
501
502    #[test]
503    fn convert_from_cbor_map_basic() {
504        let pairs = vec![
505            ("a".to_string(), CborValue::Bool(true)),
506            ("b".to_string(), CborValue::Text("hello".into())),
507        ];
508        let result: std::collections::BTreeMap<String, Value> =
509            Value::convert_from_cbor_map(pairs).unwrap();
510        assert_eq!(result.get("a"), Some(&Value::Bool(true)));
511        assert_eq!(result.get("b"), Some(&Value::Text("hello".into())));
512    }
513
514    #[test]
515    fn convert_to_cbor_map_basic() {
516        let pairs = vec![
517            ("x".to_string(), Value::U64(10)),
518            ("y".to_string(), Value::Bool(false)),
519        ];
520        let result: std::collections::BTreeMap<String, CborValue> =
521            Value::convert_to_cbor_map(pairs).unwrap();
522        assert_eq!(result.get("x"), Some(&CborValue::Integer(10u64.into())));
523        assert_eq!(result.get("y"), Some(&CborValue::Bool(false)));
524    }
525
526    // -----------------------------------------------------------------------
527    // to_cbor_buffer
528    // -----------------------------------------------------------------------
529
530    #[test]
531    fn to_cbor_buffer_roundtrip() {
532        let val = Value::Text("cbor buffer test".into());
533        let buf = val.to_cbor_buffer().unwrap();
534        assert!(!buf.is_empty());
535    }
536
537    // -----------------------------------------------------------------------
538    // CBOR array with nested values
539    // -----------------------------------------------------------------------
540
541    #[test]
542    fn cbor_array_with_nested_map() {
543        let cbor = CborValue::Array(vec![
544            CborValue::Text("item".into()),
545            CborValue::Map(vec![(
546                CborValue::Text("inner".into()),
547                CborValue::Bool(true),
548            )]),
549        ]);
550        let val: Value = cbor.try_into().unwrap();
551        assert!(matches!(val, Value::Array(_)));
552        if let Value::Array(arr) = &val {
553            assert_eq!(arr.len(), 2);
554            assert!(arr[1].is_map());
555        }
556    }
557}