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}