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}