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) => 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)]
148#[allow(clippy::approx_constant)]
149mod tests {
150 use crate::{Error, Value};
151 use ciborium::value::Integer;
152 use ciborium::Value as CborValue;
153
154 #[test]
159 fn round_trip_null() {
160 let original = Value::Null;
161 let cbor: CborValue = original.clone().try_into().unwrap();
162 assert_eq!(cbor, CborValue::Null);
163 let back: Value = cbor.try_into().unwrap();
164 assert_eq!(back, Value::Null);
165 }
166
167 #[test]
168 fn round_trip_bool_true() {
169 let original = Value::Bool(true);
170 let cbor: CborValue = original.clone().try_into().unwrap();
171 assert_eq!(cbor, CborValue::Bool(true));
172 let back: Value = cbor.try_into().unwrap();
173 assert_eq!(back, Value::Bool(true));
175 }
176
177 #[test]
178 fn round_trip_bool_false() {
179 let original = Value::Bool(false);
180 let cbor: CborValue = original.clone().try_into().unwrap();
181 let back: Value = cbor.try_into().unwrap();
182 assert_eq!(back, Value::Bool(false));
183 }
184
185 #[test]
186 fn round_trip_text() {
187 let original = Value::Text("hello world".into());
188 let cbor: CborValue = original.clone().try_into().unwrap();
189 assert_eq!(cbor, CborValue::Text("hello world".into()));
190 let back: Value = cbor.try_into().unwrap();
191 assert_eq!(back, original);
192 }
193
194 #[test]
195 fn round_trip_float() {
196 let original = Value::Float(3.14);
197 let cbor: CborValue = original.clone().try_into().unwrap();
198 assert_eq!(cbor, CborValue::Float(3.14));
199 let back: Value = cbor.try_into().unwrap();
200 assert_eq!(back, original);
201 }
202
203 #[test]
204 fn round_trip_bytes() {
205 let original = Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]);
206 let cbor: CborValue = original.clone().try_into().unwrap();
207 assert_eq!(cbor, CborValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]));
208 let back: Value = cbor.try_into().unwrap();
209 assert_eq!(back, original);
210 }
211
212 #[test]
213 fn round_trip_u64() {
214 let original = Value::U64(42);
215 let cbor: CborValue = original.clone().try_into().unwrap();
216 let back: Value = cbor.try_into().unwrap();
218 assert_eq!(back, Value::I128(42));
220 }
221
222 #[test]
223 fn round_trip_i64_negative() {
224 let original = Value::I64(-99);
225 let cbor: CborValue = original.clone().try_into().unwrap();
226 let back: Value = cbor.try_into().unwrap();
227 assert_eq!(back, Value::I128(-99));
228 }
229
230 #[test]
235 fn cbor_tag_rejected() {
236 let tagged = CborValue::Tag(42, Box::new(CborValue::Null));
237 let result: Result<Value, Error> = tagged.try_into();
238 assert!(matches!(result, Err(Error::Unsupported(_))));
239 }
240
241 #[test]
242 fn cbor_tag_rejection_message() {
243 let tagged = CborValue::Tag(0, Box::new(CborValue::Text("date".into())));
244 let err = Value::try_from(tagged).unwrap_err();
245 match err {
246 Error::Unsupported(msg) => {
247 assert!(msg.contains("tag"), "error message should mention tags");
248 }
249 _ => panic!("expected Unsupported error"),
250 }
251 }
252
253 #[test]
259 fn cbor_array_10_integers_stays_array() {
260 let arr: Vec<CborValue> = (0..10)
262 .map(|i| CborValue::Integer(Integer::from(i as u8)))
263 .collect();
264 let cbor = CborValue::Array(arr);
265 let val: Value = cbor.try_into().unwrap();
266 assert!(
267 matches!(val, Value::Array(_)),
268 "10 elements should stay as Array in CBOR heuristic"
269 );
270 }
271
272 #[test]
273 fn cbor_array_11_integers_becomes_bytes() {
274 let arr: Vec<CborValue> = (0..11)
276 .map(|i| CborValue::Integer(Integer::from(i as u8)))
277 .collect();
278 let cbor = CborValue::Array(arr);
279 let val: Value = cbor.try_into().unwrap();
280 assert!(
281 matches!(val, Value::Bytes(_)),
282 "11 elements of u8-range integers should become Bytes"
283 );
284 if let Value::Bytes(bytes) = val {
285 assert_eq!(bytes.len(), 11);
286 assert_eq!(bytes[0], 0);
287 assert_eq!(bytes[10], 10);
288 }
289 }
290
291 #[test]
292 fn cbor_array_mixed_types_stays_array() {
293 let mut arr: Vec<CborValue> = (0..11)
295 .map(|i| CborValue::Integer(Integer::from(i as u8)))
296 .collect();
297 arr.push(CborValue::Text("not an int".into()));
298 let cbor = CborValue::Array(arr);
299 let val: Value = cbor.try_into().unwrap();
300 assert!(matches!(val, Value::Array(_)));
301 }
302
303 #[test]
304 fn cbor_array_negative_values_stays_array() {
305 let arr: Vec<CborValue> = (0..12)
307 .map(|i| CborValue::Integer(Integer::from(-(i as i64))))
308 .collect();
309 let cbor = CborValue::Array(arr);
310 let val: Value = cbor.try_into().unwrap();
311 assert!(matches!(val, Value::Array(_)));
313 }
314
315 #[test]
320 fn map_keys_sorted_in_cbor_output() {
321 let map = vec![
323 (Value::Text("z".into()), Value::U64(1)),
324 (Value::Text("a".into()), Value::U64(2)),
325 (Value::Text("m".into()), Value::U64(3)),
326 ];
327 let val = Value::Map(map);
328 let cbor: CborValue = val.try_into().unwrap();
329 if let CborValue::Map(pairs) = cbor {
330 let keys: Vec<String> = pairs
331 .iter()
332 .map(|(k, _)| {
333 if let CborValue::Text(s) = k {
334 s.clone()
335 } else {
336 panic!("expected text key")
337 }
338 })
339 .collect();
340 assert_eq!(keys, vec!["a", "m", "z"]);
341 } else {
342 panic!("expected CborValue::Map");
343 }
344 }
345
346 #[test]
351 fn enum_u8_to_cbor_error() {
352 let val = Value::EnumU8(vec![1, 2, 3]);
353 let result: Result<CborValue, Error> = val.try_into();
354 assert!(matches!(result, Err(Error::Unsupported(_))));
355 }
356
357 #[test]
358 fn enum_string_to_cbor_error() {
359 let val = Value::EnumString(vec!["a".into(), "b".into()]);
360 let result: Result<CborValue, Error> = val.try_into();
361 assert!(matches!(result, Err(Error::Unsupported(_))));
362 }
363
364 #[test]
369 fn u128_narrowed_to_u64_in_cbor() {
370 let val = Value::U128(42);
372 let cbor: CborValue = val.try_into().unwrap();
373 assert_eq!(cbor, CborValue::Integer(42u64.into()));
374 }
375
376 #[test]
377 fn i128_narrowed_to_i64_in_cbor() {
378 let val = Value::I128(-99);
380 let cbor: CborValue = val.try_into().unwrap();
381 assert_eq!(cbor, CborValue::Integer((-99i64).into()));
382 }
383
384 #[test]
389 fn cbor_positive_integer_to_i128() {
390 let cbor = CborValue::Integer(Integer::from(255u8));
391 let val: Value = cbor.try_into().unwrap();
392 assert_eq!(val, Value::I128(255));
393 }
394
395 #[test]
396 fn cbor_negative_integer_to_i128() {
397 let cbor = CborValue::Integer(Integer::from(-1i64));
398 let val: Value = cbor.try_into().unwrap();
399 assert_eq!(val, Value::I128(-1));
400 }
401
402 #[test]
403 fn cbor_zero_integer_to_i128() {
404 let cbor = CborValue::Integer(Integer::from(0));
405 let val: Value = cbor.try_into().unwrap();
406 assert_eq!(val, Value::I128(0));
407 }
408
409 #[test]
414 fn bytes20_to_cbor_bytes() {
415 let bytes = [0xAAu8; 20];
416 let val = Value::Bytes20(bytes);
417 let cbor: CborValue = val.try_into().unwrap();
418 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
419 }
420
421 #[test]
422 fn bytes32_to_cbor_bytes() {
423 let bytes = [0xBBu8; 32];
424 let val = Value::Bytes32(bytes);
425 let cbor: CborValue = val.try_into().unwrap();
426 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
427 }
428
429 #[test]
430 fn bytes36_to_cbor_bytes() {
431 let bytes = [0xCCu8; 36];
432 let val = Value::Bytes36(bytes);
433 let cbor: CborValue = val.try_into().unwrap();
434 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
435 }
436
437 #[test]
438 fn identifier_to_cbor_bytes() {
439 let bytes = [0x01u8; 32];
440 let val = Value::Identifier(bytes);
441 let cbor: CborValue = val.try_into().unwrap();
442 assert_eq!(cbor, CborValue::Bytes(bytes.to_vec()));
443 }
444
445 #[test]
450 fn all_integer_types_to_cbor() {
451 let cases: Vec<Value> = vec![
452 Value::U8(255),
453 Value::I8(-128),
454 Value::U16(65535),
455 Value::I16(-32768),
456 Value::U32(u32::MAX),
457 Value::I32(i32::MIN),
458 Value::U64(u64::MAX),
459 Value::I64(i64::MIN),
460 ];
461 for val in cases {
462 let cbor: CborValue = val.clone().try_into().unwrap();
463 assert!(
464 matches!(cbor, CborValue::Integer(_)),
465 "expected Integer for {:?}",
466 val
467 );
468 }
469 }
470
471 #[test]
476 fn boxed_value_to_boxed_cbor() {
477 let val = Box::new(Value::Text("boxed".into()));
478 let cbor: Box<CborValue> = val.try_into().unwrap();
479 assert_eq!(*cbor, CborValue::Text("boxed".into()));
480 }
481
482 #[test]
487 fn cbor_map_to_value_map() {
488 let cbor = CborValue::Map(vec![
489 (
490 CborValue::Text("key".into()),
491 CborValue::Integer(42u64.into()),
492 ),
493 (CborValue::Text("flag".into()), CborValue::Bool(true)),
494 ]);
495 let val: Value = cbor.try_into().unwrap();
496 assert!(val.is_map());
497 }
498
499 #[test]
504 fn convert_from_cbor_map_basic() {
505 let pairs = vec![
506 ("a".to_string(), CborValue::Bool(true)),
507 ("b".to_string(), CborValue::Text("hello".into())),
508 ];
509 let result: std::collections::BTreeMap<String, Value> =
510 Value::convert_from_cbor_map(pairs).unwrap();
511 assert_eq!(result.get("a"), Some(&Value::Bool(true)));
512 assert_eq!(result.get("b"), Some(&Value::Text("hello".into())));
513 }
514
515 #[test]
516 fn convert_to_cbor_map_basic() {
517 let pairs = vec![
518 ("x".to_string(), Value::U64(10)),
519 ("y".to_string(), Value::Bool(false)),
520 ];
521 let result: std::collections::BTreeMap<String, CborValue> =
522 Value::convert_to_cbor_map(pairs).unwrap();
523 assert_eq!(result.get("x"), Some(&CborValue::Integer(10u64.into())));
524 assert_eq!(result.get("y"), Some(&CborValue::Bool(false)));
525 }
526
527 #[test]
532 fn to_cbor_buffer_roundtrip() {
533 let val = Value::Text("cbor buffer test".into());
534 let buf = val.to_cbor_buffer().unwrap();
535 assert!(!buf.is_empty());
536 }
537
538 #[test]
543 fn cbor_array_with_nested_map() {
544 let cbor = CborValue::Array(vec![
545 CborValue::Text("item".into()),
546 CborValue::Map(vec![(
547 CborValue::Text("inner".into()),
548 CborValue::Bool(true),
549 )]),
550 ]);
551 let val: Value = cbor.try_into().unwrap();
552 assert!(matches!(val, Value::Array(_)));
553 if let Value::Array(arr) = &val {
554 assert_eq!(arr.len(), 2);
555 assert!(arr[1].is_map());
556 }
557 }
558}