Skip to main content

dcbor/
conveniences.rs

1//! # Conveniences for CBOR Values
2//!
3//! This module provides convenience functions for working with CBOR
4//! (Concise Binary Object Representation) values. It extends the `CBOR`
5//! type with a set of methods for creating, converting, and inspecting
6//! various CBOR data types.
7//!
8//! The convenience functions are organized into several categories:
9//!
10//! * **Byte Strings** - Methods for working with CBOR byte strings
11//! * **Tagged Values** - Methods for creating and extracting CBOR tagged values
12//! * **Text Strings** - Methods for working with CBOR text strings
13//! * **Arrays** - Methods for creating and manipulating CBOR arrays
14//! * **Maps** - Methods for creating and manipulating CBOR maps
15//! * **Simple Values** - Methods for working with CBOR simple values like
16//!   `true`, `false`, `null`
17//! * **Numeric Values** - Methods for working with CBOR numeric types
18//!
19//! These methods make it easier to work with CBOR values in a type-safe
20//! manner, providing clearer error messages and more intuitive conversions
21//! between Rust and CBOR types.
22
23import_stdlib!();
24
25use crate::{CBOR, CBORCase, Error, Map, Result, Simple, tag::Tag};
26
27/// Conveniences for byte strings.
28impl CBOR {
29    /// Creates a new CBOR value representing a byte string.
30    ///
31    /// This method creates a CBOR byte string from any type that
32    /// can be referenced as a byte slice.
33    ///
34    /// # Arguments
35    ///
36    /// * `data` - The bytes to include in the byte string, which can be any
37    ///   type that can be referenced as a byte slice (e.g., `Vec<u8>`, `&[u8]`,
38    ///   etc.)
39    ///
40    /// # Returns
41    ///
42    /// A new CBOR value representing the byte string.
43    ///
44    /// # Examples
45    ///
46    /// ```no_run
47    /// use dcbor::prelude::*;
48    ///
49    /// // Create a CBOR byte string from a byte slice
50    /// let bytes = vec![0x01, 0x02, 0x03];
51    /// let cbor = CBOR::to_byte_string(&bytes);
52    ///
53    /// // Encode to CBOR binary
54    /// let encoded = cbor.to_cbor_data();
55    /// assert_eq!(hex::encode(&encoded), "43010203");
56    ///
57    /// // Convert back to bytes
58    /// let recovered: Vec<u8> = cbor.try_into().unwrap();
59    /// assert_eq!(recovered, vec![0x01, 0x02, 0x03]);
60    /// ```
61    pub fn to_byte_string(data: impl AsRef<[u8]>) -> CBOR {
62        CBORCase::ByteString(data.as_ref().into()).into()
63    }
64
65    /// Creates a new CBOR value representing a byte string from a hexadecimal
66    /// string.
67    ///
68    /// This is a convenience method that converts a hexadecimal string to a
69    /// byte array and then creates a CBOR byte string value.
70    ///
71    /// # Arguments
72    ///
73    /// * `hex` - A string containing hexadecimal characters (no spaces or other
74    ///   characters)
75    ///
76    /// # Returns
77    ///
78    /// A new CBOR value representing the byte string.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use dcbor::prelude::*;
84    ///
85    /// // Create a CBOR byte string from a hex string
86    /// let cbor = CBOR::to_byte_string_from_hex("010203");
87    ///
88    /// // Get the diagnostic representation
89    /// assert_eq!(cbor.diagnostic(), "h'010203'");
90    /// ```
91    ///
92    /// # Panics
93    ///
94    /// This method will panic if the hex string is not well-formed hexadecimal
95    /// (contains non-hex characters or an odd number of digits).
96    pub fn to_byte_string_from_hex(hex: impl AsRef<str>) -> CBOR {
97        Self::to_byte_string(hex::decode(hex.as_ref()).unwrap())
98    }
99
100    /// Extract the CBOR value as a byte string.
101    ///
102    /// Returns `Ok` if the value is a byte string, `Err` otherwise.
103    pub fn try_into_byte_string(self) -> Result<Vec<u8>> {
104        match self.into_case() {
105            CBORCase::ByteString(b) => Ok(b.into()),
106            _ => Err(Error::WrongType),
107        }
108    }
109
110    /// Checks if a CBOR value is a byte string.
111    ///
112    /// # Arguments
113    ///
114    /// * `cbor` - A reference to a CBOR value
115    ///
116    /// # Returns
117    ///
118    /// * `true` if the value is a byte string
119    /// * `false` otherwise
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use dcbor::prelude::*;
125    ///
126    /// let bytes = CBOR::to_byte_string(vec![1, 2, 3]);
127    /// assert!(CBOR::is_byte_string(&bytes));
128    ///
129    /// let text: CBOR = "hello".into();
130    /// assert!(!CBOR::is_byte_string(&text));
131    /// ```
132    pub fn is_byte_string(&self) -> bool {
133        matches!(self.as_case(), CBORCase::ByteString(_))
134    }
135
136    /// Attempts to convert the CBOR value into a byte string.
137    ///
138    /// # Returns
139    ///
140    /// * `Some(Vec<u8>)` if the value is a byte string
141    /// * `None` otherwise
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use dcbor::prelude::*;
147    ///
148    /// // Create a CBOR byte string
149    /// let cbor = CBOR::to_byte_string(vec![1, 2, 3]);
150    /// assert_eq!(cbor.into_byte_string(), Some(vec![1, 2, 3]));
151    ///
152    /// // This will return None since the value is a text string, not a byte string
153    /// let text: CBOR = "hello".into();
154    /// assert_eq!(text.into_byte_string(), None);
155    /// ```
156    pub fn into_byte_string(self) -> Option<Vec<u8>> {
157        self.try_into_byte_string().ok()
158    }
159
160    /// Tries to convert a reference to a CBOR value into a byte string.
161    ///
162    /// # Arguments
163    ///
164    /// * `cbor` - A reference to a CBOR value
165    ///
166    /// # Returns
167    ///
168    /// * `Ok(Vec<u8>)` if the value is a byte string
169    /// * `Err(Error::WrongType)` otherwise
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use dcbor::prelude::*;
175    ///
176    /// let cbor = CBOR::to_byte_string(vec![1, 2, 3]);
177    /// let bytes = CBOR::try_byte_string(&cbor).unwrap();
178    /// assert_eq!(bytes, vec![1, 2, 3]);
179    /// ```
180    pub fn try_byte_string(&self) -> Result<Vec<u8>> {
181        self.clone().try_into_byte_string()
182    }
183
184    pub fn as_byte_string(&self) -> Option<&[u8]> {
185        match self.as_case() {
186            CBORCase::ByteString(b) => Some(b),
187            _ => None,
188        }
189    }
190}
191
192/// Conveniences for tagged values.
193impl CBOR {
194    /// Creates a new CBOR value representing a tagged value.
195    ///
196    /// This method creates a CBOR tagged value by applying a tag
197    /// to another CBOR value. Tags provide semantic information about how
198    /// the tagged data should be interpreted.
199    ///
200    /// # Arguments
201    ///
202    /// * `tag` - The tag to apply, which can be any type that can be converted
203    ///   to a `Tag`
204    /// * `item` - The CBOR value to tag, which can be any type that can be
205    ///   converted to `CBOR`
206    ///
207    /// # Returns
208    ///
209    /// A new CBOR value representing the tagged value.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use dcbor::prelude::*;
215    ///
216    /// // Create a CBOR value with tag 42 applied to the string "hello"
217    /// let tagged = CBOR::to_tagged_value(42, "hello");
218    ///
219    /// // Get the diagnostic representation
220    /// assert_eq!(tagged.diagnostic(), "42(\"hello\")");
221    ///
222    /// // Extract the tag and the tagged value
223    /// let (tag, value) = tagged.try_into_tagged_value().unwrap();
224    /// assert_eq!(tag.value(), 42);
225    /// let s: String = value.try_into().unwrap();
226    /// assert_eq!(s, "hello");
227    /// ```
228    pub fn to_tagged_value(tag: impl Into<Tag>, item: impl Into<CBOR>) -> CBOR {
229        CBORCase::Tagged(tag.into(), item.into()).into()
230    }
231
232    /// Extract the CBOR value as a tagged value.
233    ///
234    /// Returns `Ok` if the value is a tagged value, `Err` otherwise.
235    pub fn try_into_tagged_value(self) -> Result<(Tag, CBOR)> {
236        match self.into_case() {
237            CBORCase::Tagged(tag, value) => Ok((tag, value)),
238            _ => Err(Error::WrongType),
239        }
240    }
241
242    pub fn is_tagged_value(&self) -> bool {
243        matches!(self.as_case(), CBORCase::Tagged(_, _))
244    }
245
246    pub fn as_tagged_value(&self) -> Option<(&Tag, &CBOR)> {
247        match self.as_case() {
248            CBORCase::Tagged(tag, value) => Some((tag, value)),
249            _ => None,
250        }
251    }
252
253    /// Tries to convert a reference to a CBOR value into a tagged value.
254    ///
255    /// # Arguments
256    ///
257    /// * `cbor` - A reference to a CBOR value
258    ///
259    /// # Returns
260    ///
261    /// * `Ok((Tag, CBOR))` - The tag and the tagged value if the CBOR value is
262    ///   a tagged value
263    /// * `Err(Error::WrongType)` - If the value is not a tagged value
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// use dcbor::prelude::*;
269    ///
270    /// let tagged = CBOR::to_tagged_value(42, "hello");
271    /// let (tag, value) = CBOR::try_tagged_value(&tagged).unwrap();
272    /// assert_eq!(tag.value(), 42);
273    /// let s: String = value.try_into().unwrap();
274    /// assert_eq!(s, "hello");
275    /// ```
276    pub fn try_tagged_value(&self) -> Result<(Tag, CBOR)> {
277        self.clone().try_into_tagged_value()
278    }
279
280    /// Extract the CBOR value as an expected tagged value.
281    ///
282    /// Returns `Ok` if the value is a tagged value with the expected tag, `Err`
283    /// otherwise.
284    pub fn try_into_expected_tagged_value(
285        self,
286        expected_tag: impl Into<Tag>,
287    ) -> Result<CBOR> {
288        let (tag, value) = self.try_into_tagged_value()?;
289        let expected_tag = expected_tag.into();
290        if tag == expected_tag {
291            Ok(value)
292        } else {
293            Err(Error::WrongTag(expected_tag, tag))
294        }
295    }
296
297    /// Tries to extract a CBOR value that is tagged with an expected tag from a
298    /// reference.
299    ///
300    /// # Arguments
301    ///
302    /// * `cbor` - A reference to a CBOR value
303    /// * `expected_tag` - The tag value that is expected
304    ///
305    /// # Returns
306    ///
307    /// * `Ok(CBOR)` - The tagged value if the CBOR value is tagged with the
308    ///   expected tag
309    /// * `Err(Error::WrongType)` - If the value is not a tagged value
310    /// * `Err(Error::WrongTag)` - If the value is tagged but with a different
311    ///   tag
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use dcbor::prelude::*;
317    ///
318    /// let tagged = CBOR::to_tagged_value(42, "hello");
319    ///
320    /// // This will succeed because the tag matches
321    /// let value = CBOR::try_expected_tagged_value(&tagged, 42).unwrap();
322    /// let s: String = value.try_into().unwrap();
323    /// assert_eq!(s, "hello");
324    ///
325    /// // This will fail because the tag doesn't match
326    /// let result = CBOR::try_expected_tagged_value(&tagged, 43);
327    /// assert!(result.is_err());
328    /// ```
329    pub fn try_expected_tagged_value(
330        &self,
331        expected_tag: impl Into<Tag>,
332    ) -> Result<CBOR> {
333        self.clone().try_into_expected_tagged_value(expected_tag)
334    }
335}
336
337/// Conveniences for text strings.
338impl CBOR {
339    /// Extract the CBOR value as a text string.
340    ///
341    /// Returns `Ok` if the value is a text string, `Err` otherwise.
342    pub fn try_into_text(self) -> Result<String> {
343        match self.into_case() {
344            CBORCase::Text(t) => Ok(t),
345            _ => Err(Error::WrongType),
346        }
347    }
348
349    pub fn is_text(&self) -> bool {
350        matches!(self.as_case(), CBORCase::Text(_))
351    }
352
353    pub fn try_text(&self) -> Result<String> { self.clone().try_into_text() }
354
355    pub fn into_text(self) -> Option<String> { self.try_into_text().ok() }
356
357    pub fn as_text(&self) -> Option<&str> {
358        match self.as_case() {
359            CBORCase::Text(t) => Some(t),
360            _ => None,
361        }
362    }
363}
364
365/// Conveniences for arrays.
366impl CBOR {
367    /// Extract the CBOR value as an array.
368    ///
369    /// Returns `Ok` if the value is an array, `Err` otherwise.
370    pub fn try_into_array(self) -> Result<Vec<CBOR>> {
371        match self.into_case() {
372            CBORCase::Array(a) => Ok(a),
373            _ => Err(Error::WrongType),
374        }
375    }
376
377    pub fn is_array(&self) -> bool {
378        matches!(self.as_case(), CBORCase::Array(_))
379    }
380
381    pub fn try_array(&self) -> Result<Vec<CBOR>> {
382        self.clone().try_into_array()
383    }
384
385    pub fn into_array(self) -> Option<Vec<CBOR>> { self.try_into_array().ok() }
386
387    pub fn as_array(&self) -> Option<&[CBOR]> {
388        match self.as_case() {
389            CBORCase::Array(a) => Some(a),
390            _ => None,
391        }
392    }
393}
394
395/// Conveniences for maps.
396impl CBOR {
397    /// Extract the CBOR value as a map.
398    ///
399    /// Returns `Ok` if the value is a map, `Err` otherwise.
400    pub fn try_into_map(self) -> Result<Map> {
401        match self.into_case() {
402            CBORCase::Map(m) => Ok(m),
403            _ => Err(Error::WrongType),
404        }
405    }
406
407    pub fn is_map(&self) -> bool { matches!(self.as_case(), CBORCase::Map(_)) }
408
409    pub fn try_map(&self) -> Result<Map> { self.clone().try_into_map() }
410
411    pub fn into_map(self) -> Option<Map> { self.try_into_map().ok() }
412
413    /// Extract the CBOR value as a simple value.
414    ///
415    /// Returns `Ok` if the value is a simple value, `Err` otherwise.
416    pub fn try_into_simple_value(self) -> Result<Simple> {
417        match self.into_case() {
418            CBORCase::Simple(s) => Ok(s),
419            _ => Err(Error::WrongType),
420        }
421    }
422
423    pub fn as_map(&self) -> Option<&Map> {
424        match self.as_case() {
425            CBORCase::Map(m) => Some(m),
426            _ => None,
427        }
428    }
429}
430
431/// Conveniences for booleans.
432impl CBOR {
433    /// The CBOR simple value representing `false`.
434    pub fn r#false() -> Self { CBORCase::Simple(Simple::False).into() }
435
436    /// The CBOR simple value representing `true`.
437    pub fn r#true() -> Self { CBORCase::Simple(Simple::True).into() }
438
439    pub fn as_bool(&self) -> Option<bool> {
440        match self.as_case() {
441            CBORCase::Simple(Simple::True) => Some(true),
442            CBORCase::Simple(Simple::False) => Some(false),
443            _ => None,
444        }
445    }
446    /// Extract the CBOR value as a boolean.
447    pub fn try_into_bool(self) -> Result<bool> {
448        match Self::as_bool(&self) {
449            Some(b) => Ok(b),
450            None => Err(Error::WrongType),
451        }
452    }
453
454    pub fn is_bool(&self) -> bool {
455        matches!(
456            self.as_case(),
457            CBORCase::Simple(Simple::True | Simple::False)
458        )
459    }
460
461    pub fn try_bool(&self) -> Result<bool> { self.clone().try_into_bool() }
462
463    /// Check if the CBOR value is true.
464    pub fn is_true(&self) -> bool {
465        matches!(self.as_case(), CBORCase::Simple(Simple::True))
466    }
467
468    /// Check if the CBOR value is false.
469    pub fn is_false(&self) -> bool {
470        matches!(self.as_case(), CBORCase::Simple(Simple::False))
471    }
472}
473
474/// Conveniences for simple values.
475impl CBOR {
476    /// Creates a CBOR value representing `null`.
477    ///
478    /// This is equivalent to the CBOR simple value `null`.
479    ///
480    /// # Returns
481    ///
482    /// A CBOR value representing `null`.
483    ///
484    /// # Examples
485    ///
486    /// ```
487    /// use dcbor::prelude::*;
488    ///
489    /// let null_value = CBOR::null();
490    /// assert!(null_value.is_null());
491    /// assert_eq!(null_value.diagnostic(), "null");
492    /// ```
493    pub fn null() -> Self { CBORCase::Simple(Simple::Null).into() }
494
495    /// Checks if the CBOR value is `null`.
496    ///
497    /// # Returns
498    ///
499    /// * `true` if the value is the CBOR simple value `null`
500    /// * `false` otherwise
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// use dcbor::prelude::*;
506    ///
507    /// let null_value = CBOR::null();
508    /// assert!(null_value.is_null());
509    ///
510    /// let other_value: CBOR = 42u64.into();
511    /// assert!(!other_value.is_null());
512    /// ```
513    pub fn is_null(&self) -> bool {
514        matches!(self.as_case(), CBORCase::Simple(Simple::Null))
515    }
516}
517
518/// Conveniences for numeric values.
519impl CBOR {
520    /// Checks if the CBOR value represents a number.
521    ///
522    /// A CBOR value is considered to be a number if it is:
523    /// - An unsigned integer (major type 0)
524    /// - A negative integer (major type 1)
525    /// - A floating-point value (major type 7, simple values 25, 26, or 27)
526    ///
527    /// # Returns
528    ///
529    /// * `true` if the value represents a number
530    /// * `false` otherwise
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use dcbor::{CBORCase, Simple, prelude::*};
536    ///
537    /// let unsigned: CBOR = 42u64.into();
538    /// assert!(unsigned.is_number());
539    ///
540    /// let negative: CBOR = (-42i64).into();
541    /// assert!(negative.is_number());
542    ///
543    /// let float: CBOR = CBORCase::Simple(Simple::Float(3.14)).into();
544    /// assert!(float.is_number());
545    ///
546    /// let text: CBOR = "not a number".into();
547    /// assert!(!text.is_number());
548    /// ```
549    pub fn is_number(&self) -> bool {
550        match self.as_case() {
551            CBORCase::Unsigned(_) | CBORCase::Negative(_) => true,
552            CBORCase::Simple(s) => s.is_float(),
553            _ => false,
554        }
555    }
556
557    /// Checks if the CBOR value represents the NaN (Not a Number) value.
558    ///
559    /// # Returns
560    ///
561    /// * `true` if the value is the CBOR representation of NaN
562    /// * `false` otherwise
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// use dcbor::{CBORCase, Simple, prelude::*};
568    ///
569    /// let nan_value = CBOR::nan();
570    /// assert!(nan_value.is_nan());
571    ///
572    /// let float: CBOR = CBORCase::Simple(Simple::Float(3.14)).into();
573    /// assert!(!float.is_nan());
574    /// ```
575    pub fn is_nan(&self) -> bool {
576        match self.as_case() {
577            CBORCase::Simple(s) => s.is_nan(),
578            _ => false,
579        }
580    }
581
582    /// Creates a CBOR value representing NaN (Not a Number).
583    ///
584    /// # Returns
585    ///
586    /// A CBOR value representing NaN.
587    ///
588    /// # Examples
589    ///
590    /// ```
591    /// use dcbor::prelude::*;
592    ///
593    /// let nan_value = CBOR::nan();
594    /// assert!(nan_value.is_nan());
595    /// ```
596    pub fn nan() -> Self { CBORCase::Simple(Simple::Float(f64::NAN)).into() }
597}