dcbor/
cbor_tagged_decodable.rs

1use crate::{CBOR, CBORCase, CBORTagged, Error, Result};
2
3/// # Tagged CBOR Decoding Support
4///
5/// This module provides the `CBORTaggedDecodable` trait, which enables types to
6/// be decoded from tagged CBOR values.
7///
8/// Tagged CBOR values include semantic information about how to interpret the
9/// data. This trait allows Rust types to verify that incoming CBOR data has the
10/// expected tag(s) and to decode the data appropriately.
11/// A trait for types that can be decoded from CBOR with a specific tag.
12///
13/// This trait extends `CBORTagged` and `TryFrom<CBOR>` to provide methods for
14/// decoding tagged CBOR data into Rust types. It handles verification that
15/// the CBOR data has the expected tag(s) and provides utilities for both
16/// tagged and untagged decoding.
17///
18/// ## Example
19///
20/// ```
21/// use dcbor::prelude::*;
22///
23/// // Define a Date type
24/// #[derive(Debug, Clone, PartialEq)]
25/// struct Date(f64); // Timestamp as seconds since epoch
26///
27/// // Implement CBORTagged
28/// impl CBORTagged for Date {
29///     fn cbor_tags() -> Vec<Tag> {
30///         vec![Tag::with_value(1)] // Standard date tag
31///     }
32/// }
33///
34/// // Implement decoding from tagged CBOR
35/// impl CBORTaggedDecodable for Date {
36///     fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
37///         // Convert the untagged CBOR to a float
38///         let timestamp: f64 = cbor.try_into()?;
39///         Ok(Date(timestamp))
40///     }
41/// }
42///
43/// // Implement TryFrom<CBOR> (required by CBORTaggedDecodable)
44/// impl TryFrom<CBOR> for Date {
45///     type Error = dcbor::Error;
46///
47///     fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
48///         Self::from_tagged_cbor(cbor)
49///     }
50/// }
51///
52/// // Create tagged CBOR data
53/// let tagged_cbor = CBOR::to_tagged_value(1, 1609459200.0); // Tag 1 with timestamp
54///
55/// // Decode using the trait
56/// let date = Date::from_tagged_cbor(tagged_cbor.clone()).unwrap();
57/// assert_eq!(date.0, 1609459200.0);
58///
59/// // Or use TryFrom
60/// let date2: Date = tagged_cbor.try_into().unwrap();
61/// assert_eq!(date, date2);
62///
63/// // This would fail - wrong tag
64/// let wrong_tag = CBOR::to_tagged_value(2, 1609459200.0);
65/// assert!(Date::from_tagged_cbor(wrong_tag).is_err());
66///
67/// // Example of backward compatibility with multiple tags
68/// #[derive(Debug, Clone, PartialEq)]
69/// struct VersionedData {
70///     version: u8,
71///     value: String,
72/// }
73///
74/// impl CBORTagged for VersionedData {
75///     fn cbor_tags() -> Vec<Tag> {
76///         vec![
77///             Tag::with_value(201),  // Current version (preferred for encoding)
78///             Tag::with_value(200),  // Legacy version (still accepted for decoding)
79///         ]
80///     }
81/// }
82///
83/// impl CBORTaggedDecodable for VersionedData {
84///     fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
85///         if let CBORCase::Map(map) = cbor.into_case() {
86///             let version: u8 = map.extract("version")?;
87///             let value: String = map.extract("value")?;
88///             Ok(VersionedData { version, value })
89///         } else {
90///             return Err(dcbor::Error::WrongType);
91///         }
92///     }
93/// }
94///
95/// impl TryFrom<CBOR> for VersionedData {
96///     type Error = dcbor::Error;
97///
98///     fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
99///         Self::from_tagged_cbor(cbor)
100///     }
101/// }
102///
103/// // Legacy data with tag 200 can still be decoded
104/// let mut map = Map::new();
105/// map.insert("version", 1);
106/// map.insert("value", "legacy");
107/// let legacy_cbor = CBOR::to_tagged_value(200, map);
108///
109/// let data = VersionedData::from_tagged_cbor(legacy_cbor).unwrap();
110/// assert_eq!(data.version, 1);
111/// assert_eq!(data.value, "legacy");
112/// ```
113pub trait CBORTaggedDecodable: TryFrom<CBOR> + CBORTagged {
114    /// Creates an instance of this type by decoding it from untagged CBOR.
115    ///
116    /// This method defines how to interpret the CBOR content (without
117    /// considering the tag) and convert it to the implementing type.
118    fn from_untagged_cbor(cbor: CBOR) -> Result<Self>
119    where
120        Self: Sized;
121
122    /// Creates an instance of this type by decoding it from tagged CBOR.
123    ///
124    /// This method first verifies that the CBOR value has one of the expected
125    /// tags (as defined by `cbor_tags()`), then delegates to
126    /// `from_untagged_cbor()` to decode the content.
127    ///
128    /// For backward compatibility, this method accepts any tag from the
129    /// `cbor_tags()` vector, not just the first one. This allows new
130    /// versions of types to still accept data tagged with older/alternative
131    /// tag values.
132    ///
133    /// In most cases, you don't need to override this method.
134    fn from_tagged_cbor(cbor: CBOR) -> Result<Self>
135    where
136        Self: Sized,
137    {
138        match cbor.into_case() {
139            CBORCase::Tagged(tag, item) => {
140                let cbor_tags = Self::cbor_tags();
141                if cbor_tags.contains(&tag) {
142                    Self::from_untagged_cbor(item)
143                } else {
144                    Err(Error::WrongTag(cbor_tags[0].clone(), tag))
145                }
146            }
147            _ => Err(Error::WrongType),
148        }
149    }
150
151    /// Creates an instance of this type by decoding it from binary encoded
152    /// tagged CBOR.
153    ///
154    /// This is a convenience method that first parses the binary data into a
155    /// CBOR value, then uses `from_tagged_cbor()` to decode it.
156    fn from_tagged_cbor_data(data: impl AsRef<[u8]>) -> Result<Self>
157    where
158        Self: Sized,
159    {
160        Self::from_tagged_cbor(CBOR::try_from_data(data)?)
161    }
162
163    /// Creates an instance of this type by decoding it from binary encoded
164    /// untagged CBOR.
165    ///
166    /// This is a convenience method that first parses the binary data into a
167    /// CBOR value, then uses `from_untagged_cbor()` to decode it.
168    fn from_untagged_cbor_data(data: impl AsRef<[u8]>) -> Result<Self>
169    where
170        Self: Sized,
171    {
172        Self::from_untagged_cbor(CBOR::try_from_data(data)?)
173    }
174}