Skip to main content

dcbor/
tag.rs

1import_stdlib!();
2
3/// Internal representation of a tag name, with optimization for statically
4/// known names.
5#[derive(Debug, Clone)]
6enum TagName {
7    /// A tag name that's known at compile time, avoiding allocation.
8    Static(&'static str),
9
10    /// A dynamically created tag name, requiring heap allocation.
11    Dynamic(String),
12}
13
14/// Represents the numeric value of a CBOR tag.
15///
16/// In CBOR, tags (major type 6) are identified by unsigned integer values.
17/// Per RFC 8949, tag values are registered with IANA to ensure
18/// interoperability.
19///
20/// # Examples of standard CBOR tag values
21///
22/// - 0: RFC3339 date/time string
23/// - 1: Epoch-based date/time
24/// - 2: Positive bignum
25/// - 3: Negative bignum
26/// - 4: Decimal fraction
27/// - 5: Bigfloat
28/// - 18: CBOR data item (tagged data is embedded CBOR)
29/// - 32: URI
30/// - 201: dCBOR tag (per the dCBOR specification)
31///
32/// For the full registry of tag values, see the IANA CBOR Tags registry:
33/// <https://www.iana.org/assignments/cbor-tags/>
34pub type TagValue = u64;
35
36/// Represents a CBOR tag (major type 6) with optional associated name.
37///
38/// Tags in CBOR provide semantic information about the tagged data item.
39/// They are used to indicate that the data item has some additional semantics
40/// beyond its basic CBOR type. For example, a tag might indicate that a string
41/// should be interpreted as a date, or that a byte string contains embedded
42/// CBOR.
43///
44/// This implementation supports both the numeric tag value and an optional
45/// human-readable name to improve code clarity and debugging.
46///
47/// # Tag equality and comparison
48///
49/// Tags are considered equal if their numeric values are equal, regardless of
50/// their names. This matches the CBOR specification behavior where the tag
51/// value (not the name) determines the semantic meaning.
52///
53/// # Deterministic encoding
54///
55/// Tags, like all other CBOR types, must follow deterministic encoding rules in
56/// dCBOR. The tag value is encoded according to the general integer encoding
57/// rules (shortest form possible), and the tagged content itself must also
58/// follow deterministic encoding rules.
59///
60/// # Examples
61///
62/// ```
63/// use dcbor::prelude::*;
64///
65/// // Create a tag with a name
66/// let epoch_time_tag = Tag::new(1, "epoch-time");
67/// assert_eq!(epoch_time_tag.value(), 1);
68/// assert_eq!(epoch_time_tag.name(), Some("epoch-time".to_string()));
69///
70/// // Create a tag without a name
71/// let unnamed_tag = Tag::with_value(42);
72/// assert_eq!(unnamed_tag.value(), 42);
73/// assert_eq!(unnamed_tag.name(), None);
74///
75/// // Create a tag at compile time with a static name
76/// const REGISTERED_TAG: Tag = Tag::with_static_name(32, "uri");
77/// assert_eq!(REGISTERED_TAG.value(), 32);
78/// assert_eq!(REGISTERED_TAG.name(), Some("uri".to_string()));
79///
80/// // Tags with the same value are equal, even with different names
81/// let tag1 = Tag::new(42, "meaning");
82/// let tag2 = Tag::with_value(42);
83/// assert_eq!(tag1, tag2);
84/// ```
85#[derive(Debug, Clone)]
86pub struct Tag {
87    /// The numeric value of the tag.
88    value: TagValue,
89
90    /// Optional human-readable name for the tag.
91    name: Option<TagName>,
92}
93
94impl Tag {
95    /// Creates a new CBOR tag with the given value and associated name.
96    ///
97    /// This constructor allocates memory for the tag name, storing it as a
98    /// dynamic string. If the tag name is known at compile time, consider
99    /// using `with_static_name` instead.
100    ///
101    /// # Parameters
102    ///
103    /// * `value` - The numeric tag value, typically registered with IANA
104    /// * `name` - A human-readable name for the tag, improving code readability
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use dcbor::prelude::*;
110    ///
111    /// let date_tag = Tag::new(12345, "This is a tagged string.");
112    /// assert_eq!(date_tag.value(), 12345);
113    /// assert_eq!(
114    ///     date_tag.name(),
115    ///     Some("This is a tagged string.".to_string())
116    /// );
117    /// ```
118    pub fn new(value: TagValue, name: impl Into<String>) -> Tag {
119        Self { value, name: Some(TagName::Dynamic(name.into())) }
120    }
121
122    /// Creates a new CBOR tag with the given value and no name.
123    ///
124    /// This constructor creates an unnamed tag, which is sufficient for many
125    /// use cases but may be less readable in code compared to named tags.
126    ///
127    /// # Parameters
128    ///
129    /// * `value` - The numeric tag value, typically registered with IANA
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use dcbor::prelude::*;
135    ///
136    /// let tag = Tag::with_value(42);
137    /// assert_eq!(tag.value(), 42);
138    /// assert_eq!(tag.name(), None);
139    /// ```
140    pub const fn with_value(value: TagValue) -> Tag {
141        Self { value, name: None }
142    }
143
144    /// Creates a new CBOR tag at compile time with the given value and
145    /// associated name.
146    ///
147    /// This constructor is optimized for cases where the tag name is known at
148    /// compile time, avoiding runtime allocations for the name string. It
149    /// can be used in `const` contexts.
150    ///
151    /// # Parameters
152    ///
153    /// * `value` - The numeric tag value, typically registered with IANA
154    /// * `name` - A static string literal as the human-readable name for the
155    ///   tag
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use dcbor::prelude::*;
161    ///
162    /// const DATE_TAG: Tag = Tag::with_static_name(0, "date-time-string");
163    /// assert_eq!(DATE_TAG.value(), 0);
164    /// assert_eq!(DATE_TAG.name(), Some("date-time-string".to_string()));
165    /// ```
166    pub const fn with_static_name(value: TagValue, name: &'static str) -> Tag {
167        Self { value, name: Some(TagName::Static(name)) }
168    }
169
170    /// Returns the numeric value of the tag.
171    ///
172    /// The tag value is the primary identifier for a tag in CBOR and determines
173    /// its semantic meaning according to the IANA CBOR Tags registry.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// use dcbor::prelude::*;
179    ///
180    /// let tag = Tag::new(18, "cbor-data-item");
181    /// assert_eq!(tag.value(), 18);
182    /// ```
183    pub fn value(&self) -> TagValue { self.value }
184
185    /// Returns the tag's associated human-readable name, if any.
186    ///
187    /// # Returns
188    ///
189    /// * `Some(String)` - The tag's name if it has one
190    /// * `None` - If the tag was created without a name
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use dcbor::prelude::*;
196    ///
197    /// let named_tag = Tag::new(32, "uri");
198    /// assert_eq!(named_tag.name(), Some("uri".to_string()));
199    ///
200    /// let unnamed_tag = Tag::with_value(32);
201    /// assert_eq!(unnamed_tag.name(), None);
202    /// ```
203    pub fn name(&self) -> Option<String> {
204        match &self.name {
205            Some(TagName::Static(name)) => Some(name.to_string()),
206            Some(TagName::Dynamic(name)) => Some(name.clone()),
207            None => None,
208        }
209    }
210}
211
212/// Compares tags for equality based on their numeric values.
213///
214/// Tags are considered equal if they have the same numeric value, regardless of
215/// whether they have different names or if one has a name and the other
216/// doesn't. This matches the CBOR standard behavior where the tag value, not
217/// its human-readable name, determines its semantic meaning.
218///
219/// # Examples
220///
221/// ```
222/// use dcbor::prelude::*;
223///
224/// let tag1 = Tag::new(32, "uri");
225/// let tag2 = Tag::with_value(32);
226/// let tag3 = Tag::new(32, "different-name");
227/// let tag4 = Tag::with_value(42);
228///
229/// assert_eq!(tag1, tag2); // Same value, one named and one unnamed
230/// assert_eq!(tag1, tag3); // Same value, different names
231/// assert_ne!(tag1, tag4); // Different values
232/// ```
233impl PartialEq for Tag {
234    fn eq(&self, other: &Self) -> bool { self.value == other.value }
235}
236
237/// Confirms `Tag` implements full equality, not just partial equality.
238///
239/// This is required for `Tag` to be used in collections like `HashSet` and
240/// as keys in `HashMap` along with the `Hash` implementation.
241impl Eq for Tag {}
242
243/// Implements hashing for `Tag` based solely on the numeric tag value.
244///
245/// This implementation ensures that two tags with the same value but different
246/// names will hash to the same value, which is consistent with the equality
247/// implementation. This allows tags to be used as keys in hash-based
248/// collections.
249impl hash::Hash for Tag {
250    fn hash<H: hash::Hasher>(&self, state: &mut H) { self.value.hash(state); }
251}
252
253/// Formats a tag for display, preferring the name if available.
254///
255/// If the tag has a name, the name will be displayed. Otherwise, the numeric
256/// value will be displayed.
257///
258/// # Examples
259///
260/// ```
261/// use dcbor::prelude::*;
262///
263/// let named_tag = Tag::new(32, "uri");
264/// let unnamed_tag = Tag::with_value(42);
265///
266/// assert_eq!(named_tag.to_string(), "uri");
267/// assert_eq!(unnamed_tag.to_string(), "42");
268/// ```
269impl fmt::Display for Tag {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match &self.name {
272            Some(TagName::Static(name)) => write!(f, "{}", name),
273            Some(TagName::Dynamic(name)) => write!(f, "{}", name),
274            None => write!(f, "{}", self.value),
275        }
276    }
277}
278
279/// Converts a raw tag value to a `Tag` instance.
280///
281/// This provides a convenient way to create unnamed tags directly from numeric
282/// values.
283///
284/// # Examples
285///
286/// ```
287/// use dcbor::prelude::*;
288///
289/// // These are equivalent:
290/// let tag1: Tag = 42.into();
291/// let tag2 = Tag::with_value(42);
292///
293/// assert_eq!(tag1, tag2);
294/// ```
295impl From<TagValue> for Tag {
296    fn from(value: TagValue) -> Self { Tag::with_value(value) }
297}
298
299/// Converts an `i32` integer to a `Tag` instance.
300///
301/// This provides a convenient way to create unnamed tags from 32-bit signed
302/// integers. Note that the value will be converted to an unsigned 64-bit
303/// integer internally.
304///
305/// # Examples
306///
307/// ```
308/// use dcbor::prelude::*;
309///
310/// let tag: Tag = 42i32.into();
311/// assert_eq!(tag.value(), 42);
312/// ```
313impl From<i32> for Tag {
314    fn from(value: i32) -> Self { Tag::with_value(value as TagValue) }
315}
316
317/// Converts a `usize` to a `Tag` instance.
318///
319/// This provides a convenient way to create unnamed tags from platform-specific
320/// sized unsigned integers. Note that on platforms where `usize` is larger than
321/// 64 bits, values that don't fit in 64 bits will be truncated.
322///
323/// # Examples
324///
325/// ```
326/// use dcbor::prelude::*;
327///
328/// let tag: Tag = 42usize.into();
329/// assert_eq!(tag.value(), 42);
330/// ```
331impl From<usize> for Tag {
332    fn from(value: usize) -> Self { Tag::with_value(value as TagValue) }
333}
334
335/// Converts a reference to a `Tag` into an owned `Tag` instance.
336impl From<&Tag> for Tag {
337    fn from(tag: &Tag) -> Self { tag.clone() }
338}