willow_data_model/paths/
component.rs

1use core::fmt::{Debug, Write};
2use core::{borrow::Borrow, fmt, ops::Deref};
3
4use bytes::Bytes;
5
6/// A slice of a [Path Component](https://willowprotocol.org/specs/data-model/index.html#Component).
7///
8/// This type statically enforces a [**m**ax\_**c**omponent\_**l**ength](https://willowprotocol.org/specs/data-model/index.html#max_component_length) of `MCL`. Otherwise, it is basically a regular `[u8]`.
9///
10/// This is an *unsized* type, meaning that it must always be used behind a pointer like `&` or [`Box`].
11#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(transparent)]
13pub struct Component<const MCL: usize>([u8]);
14
15impl<const MCL: usize> Component<MCL> {
16    /// Creates a [`Component`] from a byte slice. Returns [`None`] if the slice is longer than `MCL`.
17    ///
18    /// Aside from checking the length, this is a cost-free conversion.
19    ///
20    /// #### Examples
21    ///
22    /// ```
23    /// use willow_data_model::prelude::*;
24    /// assert!(Component::<3>::new(b"yay").is_ok());
25    /// assert!(Component::<3>::new(b"too_long").is_err());
26    /// ```
27    pub fn new(s: &[u8]) -> Result<&Self, InvalidComponentError> {
28        if s.len() <= MCL {
29            Ok(unsafe { Self::new_unchecked(s) })
30        } else {
31            Err(InvalidComponentError)
32        }
33    }
34
35    /// Creates a [`Component`] from a byte slice, without verifying its length.
36    ///
37    /// This is a cost-free conversion.
38    ///
39    /// #### Safety
40    ///
41    /// Supplying a slice of length strictly greater than `MCL` may trigger undefined behavior,
42    /// either immediately, or on any subsequent function invocation that operates on the resulting [`Component`].
43    ///
44    /// #### Examples
45    ///
46    /// ```
47    /// use willow_data_model::prelude::*;
48    /// let unchecked_component = unsafe { Component::<3>::new_unchecked(b"yay") };
49    /// assert_eq!(unchecked_component.as_ref(), b"yay");
50    /// ```
51    pub unsafe fn new_unchecked(s: &[u8]) -> &Self {
52        debug_assert!(s.len() <= MCL);
53        unsafe { &*(s as *const [u8] as *const Self) }
54    }
55
56    /// Creates a `&'static` reference to the empty component.
57    pub fn new_empty() -> &'static Self {
58        unsafe { Self::new_unchecked(&[]) }
59    }
60
61    /// Returns the raw bytes of the component.
62    ///
63    /// ```
64    /// use willow_data_model::prelude::*;
65    /// assert_eq!(Component::<3>::new(b"yay").unwrap().as_bytes(), b"yay");
66    /// ```
67    pub fn as_bytes(&self) -> &[u8] {
68        &self.0
69    }
70}
71
72impl<const MCL: usize> Deref for Component<MCL> {
73    type Target = [u8];
74
75    fn deref(&self) -> &Self::Target {
76        &self.0
77    }
78}
79
80impl<const MCL: usize> AsRef<[u8]> for Component<MCL> {
81    fn as_ref(&self) -> &[u8] {
82        &self.0
83    }
84}
85
86impl<const MCL: usize> Borrow<[u8]> for Component<MCL> {
87    fn borrow(&self) -> &[u8] {
88        &self.0
89    }
90}
91
92impl<const MCL: usize> fmt::Debug for Component<MCL> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_tuple("Component").field(&FmtHelper(&self)).finish()
95    }
96}
97
98impl<const MCL: usize> fmt::Display for Component<MCL> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        FmtHelper(&self).fmt(f)
101    }
102}
103
104/// An owned [component](https://willowprotocol.org/specs/data-model/index.html#Component) of a Willow [Path](https://willowprotocol.org/specs/data-model/index.html#Path), using reference counting for cheap cloning. Typically obtained from a [`Path`](super::Path) instead of being created independently.
105///
106/// This type enforces a const-generic [maximum component length](https://willowprotocol.org/specs/data-model/index.html#max_component_length). Use the [`AsRef`], [`Deref`], or [`Borrow`] implementation to access wrapped the immutable byte slice.
107#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
108pub struct OwnedComponent<const MCL: usize>(pub(crate) Bytes);
109
110impl<const MCL: usize> OwnedComponent<MCL> {
111    /// Creates an [`OwnedComponent`] by copying data from a byte slice. Returns [`None`] if the slice is longer than `MCL`.
112    ///
113    /// #### Complexity
114    ///
115    /// Runs in `O(n)`, where `n` is the length of the slice. Performs a single allocation of `O(n)` bytes.
116    ///
117    /// #### Examples
118    ///
119    /// ```
120    /// use willow_data_model::prelude::*;
121    /// assert!(OwnedComponent::<3>::new(b"yay").is_ok());
122    /// assert!(OwnedComponent::<3>::new(b"too_long").is_err());
123    /// ```
124    pub fn new(data: &[u8]) -> Result<Self, InvalidComponentError> {
125        if data.len() <= MCL {
126            Ok(unsafe { Self::new_unchecked(data) }) // Safe because we just checked the length.
127        } else {
128            Err(InvalidComponentError)
129        }
130    }
131
132    /// Creates an [`OwnedComponent`] by copying data from a byte slice, without verifying its length.
133    ///
134    /// #### Safety
135    ///
136    /// Supplying a slice of length strictly greater than `MCL` may trigger undefined behavior,
137    /// either immediately, or on any subsequent function invocation that operates on the resulting [`OwnedComponent`].
138    ///
139    /// #### Complexity
140    ///
141    /// Runs in `O(n)`, where `n` is the length of the slice. Performs a single allocation of `O(n)` bytes.
142    ///
143    /// #### Examples
144    ///
145    /// ```
146    /// use willow_data_model::prelude::*;
147    /// let unchecked_component = unsafe { OwnedComponent::<3>::new_unchecked(b"yay") };
148    /// assert_eq!(unchecked_component.as_ref(), b"yay");
149    /// ```
150    pub unsafe fn new_unchecked(data: &[u8]) -> Self {
151        debug_assert!(data.len() <= MCL);
152        Self(Bytes::copy_from_slice(data))
153    }
154
155    /// Returns an empty [`OwnedComponent`].
156    ///
157    /// #### Complexity
158    ///
159    /// Runs in `O(1)`, performs no allocations.
160    ///
161    /// #### Examples
162    ///
163    /// ```
164    /// use willow_data_model::prelude::*;
165    /// let empty_component = OwnedComponent::<3>::new_empty();
166    /// assert_eq!(empty_component.as_ref(), &[]);
167    /// assert_eq!(empty_component, OwnedComponent::<3>::default());
168    /// ```
169    pub fn new_empty() -> Self {
170        Self(Bytes::new())
171    }
172
173    /// Returns the raw bytes of the component.
174    ///
175    /// ```
176    /// use willow_data_model::prelude::*;
177    /// assert_eq!(OwnedComponent::<3>::new(b"yay").unwrap().as_bytes(), b"yay");
178    /// ```
179    pub fn as_bytes(&self) -> &[u8] {
180        self.0.as_ref()
181    }
182}
183
184impl<const MCL: usize> Deref for OwnedComponent<MCL> {
185    type Target = [u8];
186
187    fn deref(&self) -> &Self::Target {
188        self.0.deref()
189    }
190}
191
192impl<const MCL: usize> AsRef<[u8]> for OwnedComponent<MCL> {
193    fn as_ref(&self) -> &[u8] {
194        self.0.as_ref()
195    }
196}
197
198impl<const MCL: usize> Borrow<[u8]> for OwnedComponent<MCL> {
199    fn borrow(&self) -> &[u8] {
200        self.0.borrow()
201    }
202}
203
204impl<const MCL: usize> fmt::Debug for OwnedComponent<MCL> {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.debug_tuple("OwnedComponent")
207            .field(&FmtHelper(self))
208            .finish()
209    }
210}
211
212impl<const MCL: usize> fmt::Display for OwnedComponent<MCL> {
213    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
214        FmtHelper(self).fmt(f)
215    }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
219/// An error arising from trying to construct a [`Component`] of length strictly greater than the `MCL` ([max\_component\_length](https://willowprotocol.org/specs/data-model/index.html#max_component_length)).
220///
221/// #### Example
222///
223/// ```
224/// use willow_data_model::prelude::*;
225/// assert_eq!(Component::<4>::new(b"too_long"), Err(InvalidComponentError));
226/// ```
227pub struct InvalidComponentError;
228
229impl core::fmt::Display for InvalidComponentError {
230    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
231        write!(
232            f,
233            "Length of a component exceeded the maximum component length"
234        )
235    }
236}
237
238impl core::error::Error for InvalidComponentError {}
239
240struct FmtHelper<'a, T>(&'a T);
241
242impl<'a, T> fmt::Debug for FmtHelper<'a, T>
243where
244    T: AsRef<[u8]>,
245{
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        if self.0.as_ref().is_empty() {
248            write!(f, "<empty>")
249        } else {
250            for byte in self.0.as_ref().iter() {
251                percent_encode_fmt(f, *byte)?;
252            }
253            Ok(())
254        }
255    }
256}
257
258fn byte_is_unreserved(byte: u8) -> bool {
259    byte.is_ascii_alphanumeric()
260        || byte == b'-'
261        || byte == b'.'
262        || byte == b'_'
263        || byte == b'~'
264}
265
266fn percent_encode_fmt(f: &mut fmt::Formatter<'_>, byte: u8) -> fmt::Result {
267    if byte_is_unreserved(byte) {
268        f.write_char(unsafe { char::from_u32_unchecked(byte as u32) })
269    } else {
270        f.write_char('%')?;
271        let low = byte & 0b0000_1111;
272        let high = byte >> 4;
273        f.write_char(char::from_digit(high as u32, 16).unwrap())?;
274        f.write_char(char::from_digit(low as u32, 16).unwrap())
275    }
276}
277
278#[test]
279#[cfg(feature = "std")]
280fn test_fmt() {
281    assert_eq!(
282        &format!("{}", Component::<17>::new(b"").unwrap()),
283        "<empty>"
284    );
285    assert_eq!(&format!("{}", Component::<17>::new(b" ").unwrap()), "%20");
286    assert_eq!(
287        &format!("{}", Component::<17>::new(b".- ~_ab190%/").unwrap()),
288        ".-%20~_ab190%25%2f"
289    );
290}