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}