dcbor/
cbor_tagged_decodable.rs

1use crate::{ CBOR, Error, Result, CBORTagged, CBORCase };
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 data.
9/// This trait allows Rust types to verify that incoming CBOR data has the expected
10/// 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 considering
117    /// the tag) and convert it to the implementing type.
118    fn from_untagged_cbor(cbor: CBOR) -> Result<Self> where Self: Sized;
119
120    /// Creates an instance of this type by decoding it from tagged CBOR.
121    ///
122    /// This method first verifies that the CBOR value has one of the expected tags
123    /// (as defined by `cbor_tags()`), then delegates to `from_untagged_cbor()` to
124    /// decode the content.
125    ///
126    /// For backward compatibility, this method accepts any tag from the `cbor_tags()`
127    /// vector, not just the first one. This allows new versions of types to still
128    /// accept data tagged with older/alternative tag values.
129    ///
130    /// In most cases, you don't need to override this method.
131    fn from_tagged_cbor(cbor: CBOR) -> Result<Self> where Self: Sized {
132        match cbor.into_case() {
133            CBORCase::Tagged(tag, item) => {
134                let cbor_tags = Self::cbor_tags();
135                if cbor_tags.iter().any(|t| *t == tag) {
136                    Self::from_untagged_cbor(item)
137                } else {
138                    return Err(Error::WrongTag(cbor_tags[0].clone(), tag));
139                }
140            }
141            _ => {
142                return Err(Error::WrongType);
143            }
144        }
145    }
146
147    /// Creates an instance of this type by decoding it from binary encoded tagged CBOR.
148    ///
149    /// This is a convenience method that first parses the binary data into a CBOR value,
150    /// then uses `from_tagged_cbor()` to decode it.
151    fn from_tagged_cbor_data(data: impl AsRef<[u8]>) -> Result<Self> where Self: Sized {
152        Self::from_tagged_cbor(CBOR::try_from_data(data)?)
153    }
154
155    /// Creates an instance of this type by decoding it from binary encoded untagged CBOR.
156    ///
157    /// This is a convenience method that first parses the binary data into a CBOR value,
158    /// then uses `from_untagged_cbor()` to decode it.
159    fn from_untagged_cbor_data(data: impl AsRef<[u8]>) -> Result<Self> where Self: Sized {
160        Self::from_untagged_cbor(CBOR::try_from_data(data)?)
161    }
162}