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 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) => {
94 CborValue::Integer(Integer::try_from(i).map_err(|_| Error::IntegerSizeError)?)
95 }
96 Value::I128(i) => {
97 CborValue::Integer(Integer::try_from(i).map_err(|_| Error::IntegerSizeError)?)
98 }
99 Value::U64(i) => CborValue::Integer(i.into()),
100 Value::I64(i) => CborValue::Integer(i.into()),
101 Value::U32(i) => CborValue::Integer(i.into()),
102 Value::I32(i) => CborValue::Integer(i.into()),
103 Value::U16(i) => CborValue::Integer(i.into()),
104 Value::I16(i) => CborValue::Integer(i.into()),
105 Value::U8(i) => CborValue::Integer(i.into()),
106 Value::I8(i) => CborValue::Integer(i.into()),
107 Value::Bytes(bytes) => CborValue::Bytes(bytes),
108 Value::Bytes20(bytes) => CborValue::Bytes(bytes.to_vec()),
109 Value::Bytes32(bytes) => CborValue::Bytes(bytes.to_vec()),
110 Value::Bytes36(bytes) => CborValue::Bytes(bytes.to_vec()),
111 Value::Float(float) => CborValue::Float(float),
112 Value::Text(string) => CborValue::Text(string),
113 Value::Bool(value) => CborValue::Bool(value),
114 Value::Null => CborValue::Null,
115 Value::Array(array) => CborValue::Array(
116 array
117 .into_iter()
118 .map(|value| value.try_into())
119 .collect::<Result<Vec<CborValue>, Error>>()?,
120 ),
121 Value::Map(mut map) => {
122 map.sort_by_keys();
123 CborValue::Map(
124 map.into_iter()
125 .map(|(k, v)| Ok((k.try_into()?, v.try_into()?)))
126 .collect::<Result<Vec<(CborValue, CborValue)>, Error>>()?,
127 )
128 }
129 Value::Identifier(bytes) => CborValue::Bytes(bytes.to_vec()),
130 Value::EnumU8(_) => {
131 return Err(Error::Unsupported(
132 "No support for conversion of EnumU8 to JSONValue".to_string(),
133 ))
134 }
135 Value::EnumString(_) => {
136 return Err(Error::Unsupported(
137 "No support for conversion of EnumString to JSONValue".to_string(),
138 ))
139 }
140 })
141 }
142}
143
144impl TryInto<Box<CborValue>> for Box<Value> {
145 type Error = Error;
146 fn try_into(self) -> Result<Box<CborValue>, Self::Error> {
147 (*self).try_into().map(Box::new)
148 }
149}
150
151#[cfg(test)]
152#[allow(clippy::approx_constant)]
153mod tests {
154 use crate::{Error, Value};
155 use ciborium::value::Integer;
156 use ciborium::Value as CborValue;
157
158 const MIN_NATIVE_CBOR_INTEGER: i128 = -(u64::MAX as i128) - 1;
159
160 #[test]
165 fn round_trip_null() {
166 let original = Value::Null;
167 let cbor: CborValue = original.clone().try_into().unwrap();
168 assert_eq!(cbor, CborValue::Null);
169 let back: Value = cbor.try_into().unwrap();
170 assert_eq!(back, Value::Null);
171 }
172
173 #[test]
174 fn round_trip_bool_true() {
175 let original = Value::Bool(true);
176 let cbor: CborValue = original.clone().try_into().unwrap();
177 assert_eq!(cbor, CborValue::Bool(true));
178 let back: Value = cbor.try_into().unwrap();
179 assert_eq!(back, Value::Bool(true));
181 }
182
183 #[test]
184 fn round_trip_bool_false() {
185 let original = Value::Bool(false);
186 let cbor: CborValue = original.clone().try_into().unwrap();
187 let back: Value = cbor.try_into().unwrap();
188 assert_eq!(back, Value::Bool(false));
189 }
190
191 #[test]
192 fn round_trip_text() {
193 let original = Value::Text("hello world".into());
194 let cbor: CborValue = original.clone().try_into().unwrap();
195 assert_eq!(cbor, CborValue::Text("hello world".into()));
196 let back: Value = cbor.try_into().unwrap();
197 assert_eq!(back, original);
198 }
199
200 #[test]
201 fn round_trip_float() {
202 let original = Value::Float(3.14);
203 let cbor: CborValue = original.clone().try_into().unwrap();
204 assert_eq!(cbor, CborValue::Float(3.14));
205 let back: Value = cbor.try_into().unwrap();
206 assert_eq!(back, original);
207 }
208
209 #[test]
210 fn round_trip_bytes() {
211 let original = Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
212 let cbor: CborValue = original.clone().try_into().unwrap();
213 assert_eq!(cbor, CborValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]));
214 let back: Value = cbor.try_into().unwrap();
215 assert_eq!(back, original);
216 }
217
218 #[test]
219 fn round_trip_u64() {
220 let original = Value::U64(42);
221 let cbor: CborValue = original.clone().try_into().unwrap();
222 let back: Value = cbor.try_into().unwrap();
224 assert_eq!(back, Value::I128(42));
226 }
227
228 #[test]
229 fn round_trip_i64_negative() {
230 let original = Value::I64(-99);
231 let cbor: CborValue = original.clone().try_into().unwrap();
232 let back: Value = cbor.try_into().unwrap();
233 assert_eq!(back, Value::I128(-99));
234 }
235
236 #[test]
241 fn cbor_tag_rejected() {
242 let tagged = CborValue::Tag(42, Box::new(CborValue::Null));
243 let result: Result<Value, Error> = tagged.try_into();
244 assert!(matches!(result, Err(Error::Unsupported(_))));
245 }
246
247 #[test]
248 fn cbor_tag_rejection_message() {
249 let tagged = CborValue::Tag(0, Box::new(CborValue::Text("date".into())));
250 let err = Value::try_from(tagged).unwrap_err();
251 match err {
252 Error::Unsupported(msg) => {
253 assert!(msg.contains("tag"), "error message should mention tags");
254 }
255 _ => panic!("expected Unsupported error"),
256 }
257 }
258
259 #[test]
265 fn cbor_array_10_integers_stays_array() {
266 let arr: Vec<CborValue> = (0..10)
268 .map(|i| CborValue::Integer(Integer::from(i as u8)))
269 .collect();
270 let cbor = CborValue::Array(arr);
271 let val: Value = cbor.try_into().unwrap();
272 assert!(
273 matches!(val, Value::Array(_)),
274 "10 elements should stay as Array in CBOR heuristic"
275 );
276 }
277
278 #[test]
279 fn cbor_array_11_integers_becomes_bytes() {
280 let arr: Vec<CborValue> = (0..11)
282 .map(|i| CborValue::Integer(Integer::from(i as u8)))
283 .collect();
284 let cbor = CborValue::Array(arr);
285 let val: Value = cbor.try_into().unwrap();
286 assert!(
287 matches!(val, Value::Bytes(_)),
288 "11 elements of u8-range integers should become Bytes"
289 );
290 if let Value::Bytes(bytes) = val {
291 assert_eq!(bytes.len(), 11);
292 assert_eq!(bytes[0], 0);
293 assert_eq!(bytes[10], 10);
294 }
295 }
296
297 #[test]
298 fn cbor_array_mixed_types_stays_array() {
299 let mut arr: Vec<CborValue> = (0..11)
301 .map(|i| CborValue::Integer(Integer::from(i as u8)))
302 .collect();
303 arr.push(CborValue::Text("not an int".into()));
304 let cbor = CborValue::Array(arr);
305 let val: Value = cbor.try_into().unwrap();
306 assert!(matches!(val, Value::Array(_)));
307 }
308
309 #[test]
310 fn cbor_array_negative_values_stays_array() {
311 let arr: Vec<CborValue> = (0..12)
313 .map(|i| CborValue::Integer(Integer::from(-(i as i64))))
314 .collect();
315 let cbor = CborValue::Array(arr);
316 let val: Value = cbor.try_into().unwrap();
317 assert!(matches!(val, Value::Array(_)));
319 }
320
321 #[test]
326 fn map_keys_sorted_in_cbor_output() {
327 let map = vec![
329 (Value::Text("z".into()), Value::U64(1)),
330 (Value::Text("a".into()), Value::U64(2)),
331 (Value::Text("m".into()), Value::U64(3)),
332 ];
333 let val = Value::Map(map);
334 let cbor: CborValue = val.try_into().unwrap();
335 if let CborValue::Map(pairs) = cbor {
336 let keys: Vec<String> = pairs
337 .iter()
338 .map(|(k, _)| {
339 if let CborValue::Text(s) = k {
340 s.clone()
341 } else {
342 panic!("expected text key")
343 }
344 })
345 .collect();
346 assert_eq!(keys, vec!["a", "m", "z"]);
347 } else {
348 panic!("expected CborValue::Map");
349 }
350 }
351
352 #[test]
357 fn enum_u8_to_cbor_error() {
358 let val = Value::EnumU8(vec![1, 2, 3]);
359 let result: Result<CborValue, Error> = val.try_into();
360 assert!(matches!(result, Err(Error::Unsupported(_))));
361 }
362
363 #[test]
364 fn enum_string_to_cbor_error() {
365 let val = Value::EnumString(vec!["a".into(), "b".into()]);
366 let result: Result<CborValue, Error> = val.try_into();
367 assert!(matches!(result, Err(Error::Unsupported(_))));
368 }
369
370 #[test]
375 fn u128_in_native_range_converts_to_cbor() {
376 let val = Value::U128(42);
377 let cbor: CborValue = val.try_into().unwrap();
378 assert_eq!(cbor, CborValue::Integer(42u64.into()));
379
380 let max_native = Value::U128(u64::MAX as u128);
381 let cbor: CborValue = max_native.try_into().unwrap();
382 assert_eq!(cbor, CborValue::Integer(u64::MAX.into()));
383 }
384
385 #[test]
386 fn i128_in_native_range_converts_to_cbor() {
387 let val = Value::I128(-99);
388 let cbor: CborValue = val.try_into().unwrap();
389 assert_eq!(cbor, CborValue::Integer((-99i64).into()));
390 }
391
392 #[test]
393 fn u128_rejects_values_outside_the_native_cbor_integer_range() {
394 for value in [u64::MAX as u128 + 1, u128::MAX] {
395 let result: Result<CborValue, Error> = Value::U128(value).try_into();
396 assert_eq!(result.unwrap_err(), Error::IntegerSizeError);
397 }
398 }
399
400 #[test]
401 fn i128_preserves_the_full_native_cbor_integer_range() {
402 for value in [
403 MIN_NATIVE_CBOR_INTEGER,
404 i64::MIN as i128,
405 i64::MAX as i128,
406 u64::MAX as i128,
407 ] {
408 let cbor: CborValue = Value::I128(value).try_into().unwrap();
409 let CborValue::Integer(integer) = cbor else {
410 panic!("expected CBOR integer");
411 };
412 assert_eq!(i128::from(integer), value);
413 }
414 }
415
416 #[test]
417 fn i128_rejects_values_outside_the_native_cbor_integer_range() {
418 for value in [
419 MIN_NATIVE_CBOR_INTEGER - 1,
420 u64::MAX as i128 + 1,
421 i128::MIN,
422 i128::MAX,
423 ] {
424 let result: Result<CborValue, Error> = Value::I128(value).try_into();
425 assert_eq!(result.unwrap_err(), Error::IntegerSizeError);
426 }
427 }
428
429 #[test]
430 fn nested_out_of_range_integer_fails_the_entire_conversion() {
431 let value = Value::Array(vec![Value::Map(vec![(
432 Value::Text("amount".to_string()),
433 Value::U128(u64::MAX as u128 + 1),
434 )])]);
435
436 let result: Result<CborValue, Error> = value.try_into();
437 assert_eq!(result.unwrap_err(), Error::IntegerSizeError);
438 }
439
440 #[test]
445 fn cbor_positive_integer_to_i128() {
446 let cbor = CborValue::Integer(Integer::from(255u8));
447 let val: Value = cbor.try_into().unwrap();
448 assert_eq!(val, Value::I128(255));
449 }
450
451 #[test]
452 fn cbor_negative_integer_to_i128() {
453 let cbor = CborValue::Integer(Integer::from(-1i64));
454 let val: Value = cbor.try_into().unwrap();
455 assert_eq!(val, Value::I128(-1));
456 }
457
458 #[test]
459 fn cbor_zero_integer_to_i128() {
460 let cbor = CborValue::Integer(Integer::from(0));
461 let val: Value = cbor.try_into().unwrap();
462 assert_eq!(val, Value::I128(0));
463 }
464
465 #[test]
470 fn bytes20_to_cbor_bytes() {
471 let bytes = [0xAAu8; 20];
472 let val = Value::Bytes20(bytes);
473 let cbor: CborValue = val.try_into().unwrap();
474 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
475 }
476
477 #[test]
478 fn bytes32_to_cbor_bytes() {
479 let bytes = [0xBBu8; 32];
480 let val = Value::Bytes32(bytes);
481 let cbor: CborValue = val.try_into().unwrap();
482 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
483 }
484
485 #[test]
486 fn bytes36_to_cbor_bytes() {
487 let bytes = [0xCCu8; 36];
488 let val = Value::Bytes36(bytes);
489 let cbor: CborValue = val.try_into().unwrap();
490 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
491 }
492
493 #[test]
494 fn identifier_to_cbor_bytes() {
495 let bytes = [0x01u8; 32];
496 let val = Value::Identifier(bytes);
497 let cbor: CborValue = val.try_into().unwrap();
498 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
499 }
500
501 #[test]
506 fn all_integer_types_to_cbor() {
507 let cases: Vec<Value> = vec![
508 Value::U8(255),
509 Value::I8(-128),
510 Value::U16(65535),
511 Value::I16(-32768),
512 Value::U32(u32::MAX),
513 Value::I32(i32::MIN),
514 Value::U64(u64::MAX),
515 Value::I64(i64::MIN),
516 ];
517 for val in cases {
518 let cbor: CborValue = val.clone().try_into().unwrap();
519 assert!(
520 matches!(cbor, CborValue::Integer(_)),
521 "expected Integer for {:?}",
522 val
523 );
524 }
525 }
526
527 #[test]
532 fn boxed_value_to_boxed_cbor() {
533 let val = Box::new(Value::Text("boxed".into()));
534 let cbor: Box<CborValue> = val.try_into().unwrap();
535 assert_eq!(*cbor, CborValue::Text("boxed".into()));
536 }
537
538 #[test]
543 fn cbor_map_to_value_map() {
544 let cbor = CborValue::Map(vec![
545 (
546 CborValue::Text("key".into()),
547 CborValue::Integer(42u64.into()),
548 ),
549 (CborValue::Text("flag".into()), CborValue::Bool(true)),
550 ]);
551 let val: Value = cbor.try_into().unwrap();
552 assert!(val.is_map());
553 }
554
555 #[test]
560 fn convert_from_cbor_map_basic() {
561 let pairs = vec![
562 ("a".to_string(), CborValue::Bool(true)),
563 ("b".to_string(), CborValue::Text("hello".into())),
564 ];
565 let result: std::collections::BTreeMap<String, Value> =
566 Value::convert_from_cbor_map(pairs).unwrap();
567 assert_eq!(result.get("a"), Some(&Value::Bool(true)));
568 assert_eq!(result.get("b"), Some(&Value::Text("hello".into())));
569 }
570
571 #[test]
572 fn convert_to_cbor_map_basic() {
573 let pairs = vec![
574 ("x".to_string(), Value::U64(10)),
575 ("y".to_string(), Value::Bool(false)),
576 ];
577 let result: std::collections::BTreeMap<String, CborValue> =
578 Value::convert_to_cbor_map(pairs).unwrap();
579 assert_eq!(result.get("x"), Some(&CborValue::Integer(10u64.into())));
580 assert_eq!(result.get("y"), Some(&CborValue::Bool(false)));
581 }
582
583 #[test]
588 fn to_cbor_buffer_roundtrip() {
589 let val = Value::Text("cbor buffer test".into());
590 let buf = val.to_cbor_buffer().unwrap();
591 assert!(!buf.is_empty());
592 }
593
594 #[test]
595 fn to_cbor_buffer_roundtrips_native_cbor_integer_boundaries() {
596 for value in [
597 MIN_NATIVE_CBOR_INTEGER,
598 i64::MIN as i128 - 1,
599 i64::MAX as i128 + 1,
600 u64::MAX as i128,
601 ] {
602 let buf = Value::I128(value).to_cbor_buffer().unwrap();
603 let cbor: CborValue = ciborium::de::from_reader(buf.as_slice()).unwrap();
604 let decoded: Value = cbor.try_into().unwrap();
605 assert_eq!(decoded, Value::I128(value));
606 }
607 }
608
609 #[test]
614 fn cbor_array_with_nested_map() {
615 let cbor = CborValue::Array(vec![
616 CborValue::Text("item".into()),
617 CborValue::Map(vec![(
618 CborValue::Text("inner".into()),
619 CborValue::Bool(true),
620 )]),
621 ]);
622 let val: Value = cbor.try_into().unwrap();
623 assert!(matches!(val, Value::Array(_)));
624 if let Value::Array(arr) = &val {
625 assert_eq!(arr.len(), 2);
626 assert!(arr[1].is_map());
627 }
628 }
629}