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)]
148mod tests {
149 use crate::{Error, Value};
150 use ciborium::value::Integer;
151 use ciborium::Value as CborValue;
152
153 #[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 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 let back: Value = cbor.try_into().unwrap();
217 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 #[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 #[test]
258 fn cbor_array_10_integers_stays_array() {
259 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 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 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 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 assert!(matches!(val, Value::Array(_)));
312 }
313
314 #[test]
319 fn map_keys_sorted_in_cbor_output() {
320 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 #[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 #[test]
368 fn u128_narrowed_to_u64_in_cbor() {
369 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 let val = Value::I128(-99);
379 let cbor: CborValue = val.try_into().unwrap();
380 assert_eq!(cbor, CborValue::Integer((-99i64).into()));
381 }
382
383 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}