musli_wire/
tag.rs

1//! Type flags available for `musli-wire`.
2
3#![allow(clippy::unusual_byte_groupings)]
4
5use core::fmt;
6use core::mem;
7
8#[cfg(feature = "test")]
9use musli::{Decode, Encode};
10
11/// Data masked into the data type.
12pub(crate) const DATA_MASK: u8 = 0b00_111111;
13/// The maximum length that can be inlined in the tag without adding additional
14/// data to the wire format.
15pub const MAX_INLINE_LEN: usize = (DATA_MASK - 1) as usize;
16
17/// The structure of a type tag.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u8)]
20pub enum Kind {
21    /// A reserved value.
22    Reserve = 0b00_000000,
23    /// A fixed element where data indicates how many bytes it consists of. Data
24    /// contains the prefix length unless it's set to all 1s after which a
25    /// continuation sequence indicating the length should be decoded.
26    Prefix = 0b01_000000,
27    /// A length-prefixed sequence of values. Data contains the length of the
28    /// sequence if it's short enough to fit in 6 bits. All bits as 1s is
29    /// reserved to indicate when it's empty.
30    Sequence = 0b10_000000,
31    /// A continuation-encoded value. Data is the immediate value embedded if
32    /// it's small enough to fit in 6 bits. All bits as 1s is reserved to
33    /// indicate when a continuation sequence is used.
34    Continuation = 0b11_000000,
35}
36
37/// A type tag.
38///
39/// The [Kind] of the element is indicates by its 2 MSBs, and remaining 6 bits
40/// is the data field. The exact use of the data field depends on the [Kind] in
41/// question. It is primarily used to smuggle extra data for the kind in
42/// question.
43#[derive(Clone, Copy, PartialEq, Eq, Hash)]
44#[cfg_attr(feature = "test", derive(Encode, Decode))]
45#[repr(transparent)]
46#[cfg_attr(feature = "test", musli(transparent))]
47pub struct Tag {
48    /// The internal representation of the tag.
49    repr: u8,
50}
51
52impl Tag {
53    /// Construct a new tag through an unchecked constructor.
54    ///
55    /// `data` must not be equal to or larger than [MAX_INLINE_LEN], or else it
56    /// could corrupt the payload.
57    #[inline]
58    pub const fn new(kind: Kind, data: u8) -> Self {
59        Self {
60            repr: kind as u8 | data,
61        }
62    }
63
64    /// Construct a new empty tag of the given [Kind].
65    #[inline]
66    pub const fn empty(kind: Kind) -> Self {
67        Self {
68            repr: kind as u8 | DATA_MASK,
69        }
70    }
71
72    /// Construct from a byte.
73    #[inline]
74    pub const fn from_byte(repr: u8) -> Self {
75        Self { repr }
76    }
77
78    /// Coerce type flag into a byte.
79    #[inline]
80    pub const fn byte(self) -> u8 {
81        self.repr
82    }
83
84    /// Access the kind of the tag.
85    #[inline]
86    pub const fn kind(self) -> Kind {
87        // SAFETY: this is safe because we've ensured that all available Kind
88        // variants occupy all available bit patterns.
89        unsafe { mem::transmute(self.repr & !DATA_MASK) }
90    }
91
92    /// Perform raw access over the data payload. Will return [DATA_MASK] if
93    /// data is empty.
94    #[inline]
95    pub(crate) const fn data_raw(self) -> u8 {
96        self.repr & DATA_MASK
97    }
98
99    /// Perform checked access over the internal data. Returns [None] if data is
100    /// empty.
101    #[inline]
102    pub const fn data(self) -> Option<u8> {
103        let data = self.data_raw();
104
105        if data == DATA_MASK {
106            None
107        } else {
108            Some(data)
109        }
110    }
111
112    /// Attempt to construct a type tag with the given length embedded.
113    ///
114    /// Returns a tuple where the boolean indicates if the value was embedded or
115    /// not.
116    #[inline]
117    pub const fn with_len(kind: Kind, len: usize) -> (Self, bool) {
118        if len < DATA_MASK as usize {
119            (Self::new(kind, len as u8), true)
120        } else {
121            (Self::new(kind, DATA_MASK), false)
122        }
123    }
124
125    /// Attempt to construct a type tag with the given length embedded.
126    ///
127    /// Returns a tuple where the boolean indicates if the value was embedded or
128    /// not.
129    #[inline]
130    pub const fn with_byte(kind: Kind, len: u8) -> (Self, bool) {
131        if len < DATA_MASK {
132            (Self::new(kind, len), true)
133        } else {
134            (Self::new(kind, DATA_MASK), false)
135        }
136    }
137}
138
139impl fmt::Debug for Tag {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.debug_struct("Tag")
142            .field("kind", &self.kind())
143            .field("data", &self.data())
144            .finish()
145    }
146}