Skip to main content

hostaddr/domain/
inlined.rs

1//! Stack-allocated buffer for storing validated domain names.
2//!
3//! This module provides the `Buffer` type, a fixed-size buffer designed for
4//! storing domain names without heap allocation. It is ideal for `no_std` and
5//! `no-alloc` environments.
6//!
7//! ## Design
8//!
9//! The `Buffer` type uses a 255-byte array to store domain names:
10//! - 253 bytes for the domain name content (max DNS domain length)
11//! - 1 byte for a trailing dot (for FQDNs)
12//! - 1 byte to store the actual length
13//!
14//! This design allows `Buffer` to be used in const contexts and on the stack
15//! without requiring any heap allocation.
16//!
17//! ## Usage
18//!
19//! The `Buffer` type is typically used with the `Domain<Buffer>` type:
20//!
21//! ```rust
22//! use hostaddr::{Domain, Buffer};
23//!
24//! // Create a stack-allocated domain (no heap allocation)
25//! let domain: Domain<Buffer> = Domain::try_from("example.com").unwrap();
26//!
27//! // Access as str or bytes
28//! assert_eq!(domain.as_inner().as_str(), "example.com");
29//! assert_eq!(domain.as_inner().as_bytes(), b"example.com");
30//! ```
31//!
32//! ## Converting to Other Types
33//!
34//! `Buffer` can be converted to various string and byte types:
35//!
36//! ```rust
37//! # #[cfg(any(feature = "std", feature = "alloc"))]
38//! # {
39//! use hostaddr::{Domain, Buffer};
40//!
41//! let domain: Domain<Buffer> = Domain::try_from("example.com").unwrap();
42//!
43//! // Convert to String
44//! let s: String = domain.into_inner().into();
45//!
46//! // Convert to Vec<u8>
47//! let domain: Domain<Buffer> = Domain::try_from("example.com").unwrap();
48//! let v: Vec<u8> = domain.into_inner().into();
49//! # }
50//! ```
51
52use core::borrow::Borrow;
53
54/// An immutable buffer which contains a valid domain.
55///
56/// The internal storage is a `[u8; 255]` array with the following layout:
57/// - bytes 0-253: domain name content
58/// - byte 254: length of the domain name (0-254)
59///
60/// This fixed-size design allows the `Buffer` to be used in `no_std` and
61/// `no-alloc` environments without requiring heap allocation.
62///
63/// ## Example
64///
65/// ```rust
66/// use hostaddr::{Domain, Buffer};
67///
68/// let domain: Domain<Buffer> = Domain::try_from("example.com").unwrap();
69/// assert_eq!(domain.as_inner().as_str(), "example.com");
70/// ```
71#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, derive_more::Display)]
72#[repr(transparent)]
73#[display("{}", self.as_str())]
74pub struct Buffer {
75  /// 253 bytes for possible domain name, 1 byte for '.', 1 byte for length.
76  buf: [u8; 255],
77}
78
79impl PartialOrd for Buffer {
80  fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
81    Some(self.cmp(other))
82  }
83}
84
85impl Ord for Buffer {
86  fn cmp(&self, other: &Self) -> core::cmp::Ordering {
87    self.as_str().cmp(other.as_str())
88  }
89}
90
91#[allow(unused)]
92#[cfg(any(
93  feature = "std",
94  feature = "alloc",
95  feature = "smol_str_0_3",
96  feature = "triomphe_0_1",
97  feature = "bytes_1",
98  feature = "tinyvec_1",
99  feature = "smallvec_1",
100  feature = "bytes_1"
101))]
102macro_rules! impl_from_domain_buffer {
103  ($($as:ident(
104    $($into:ident -> $ty:ty), +$(,)?
105  )), +$(,)?) => {
106    $(
107      $(
108        impl_from_domain_buffer!($as -> $into -> $ty);
109      )*
110    )*
111  };
112  ($as:ident -> $into:ident -> $ty:ty) => {
113    impl From<Buffer> for $ty {
114      fn from(value: Buffer) -> Self {
115        value.$as().$into()
116      }
117    }
118  };
119}
120
121#[cfg(feature = "smol_str_0_3")]
122impl_from_domain_buffer!(as_str -> into -> smol_str_0_3::SmolStr);
123#[cfg(feature = "triomphe_0_1")]
124impl_from_domain_buffer!(as_str -> into -> triomphe_0_1::Arc<str>);
125#[cfg(feature = "triomphe_0_1")]
126impl_from_domain_buffer!(as_bytes -> into -> triomphe_0_1::Arc<[u8]>);
127#[cfg(feature = "bytes_1")]
128const _: () = {
129  use bytes_1::Bytes;
130
131  impl From<Buffer> for Bytes {
132    fn from(value: Buffer) -> Self {
133      Bytes::copy_from_slice(value.as_bytes())
134    }
135  }
136};
137#[cfg(feature = "tinyvec_1")]
138const _: () = {
139  use tinyvec_1::TinyVec;
140
141  impl<const N: usize> From<Buffer> for TinyVec<[u8; N]> {
142    fn from(value: Buffer) -> Self {
143      TinyVec::from(value.as_bytes())
144    }
145  }
146};
147#[cfg(feature = "smallvec_1")]
148const _: () = {
149  use smallvec_1::SmallVec;
150
151  impl<const N: usize> From<Buffer> for SmallVec<[u8; N]> {
152    fn from(value: Buffer) -> Self {
153      SmallVec::from_slice(value.as_bytes())
154    }
155  }
156};
157
158#[cfg(any(feature = "std", feature = "alloc"))]
159const _: () = {
160  use std::{borrow::ToOwned, string::String, vec::Vec};
161
162  impl_from_domain_buffer!(
163    as_str(
164      into -> String,
165      into -> std::sync::Arc<str>,
166      into -> std::boxed::Box<str>,
167      into -> std::rc::Rc<str>,
168    ),
169    as_bytes(
170      into -> Vec<u8>,
171      into -> std::sync::Arc<[u8]>,
172      into -> std::boxed::Box<[u8]>,
173      into -> std::rc::Rc<[u8]>,
174    ),
175  );
176
177  impl From<Buffer> for std::borrow::Cow<'_, str> {
178    /// ```rust
179    /// use hostaddr::{Buffer, Domain};
180    ///
181    /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
182    /// let str: std::borrow::Cow<'_, str> = domain.into_inner().into();
183    /// ```
184    fn from(value: Buffer) -> Self {
185      std::borrow::Cow::Owned(value.as_str().to_owned())
186    }
187  }
188
189  impl From<Buffer> for std::borrow::Cow<'_, [u8]> {
190    /// ```rust
191    /// use hostaddr::{Buffer, Domain};
192    ///
193    /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
194    /// let bytes: std::borrow::Cow<'_, [u8]> = domain.into_inner().into();
195    /// ```
196    fn from(value: Buffer) -> Self {
197      std::borrow::Cow::Owned(value.as_bytes().to_owned())
198    }
199  }
200};
201
202impl<'a> From<&'a Buffer> for &'a str {
203  /// ```rust
204  /// # #[cfg(any(feature = "std", feature = "alloc"))]
205  /// # {
206  /// use hostaddr::{Buffer, Domain};
207  ///
208  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
209  /// let str: &str = (&domain.into_inner()).into();
210  /// # }
211  /// ```
212  fn from(value: &'a Buffer) -> Self {
213    value.as_str()
214  }
215}
216
217impl<'a> From<&'a Buffer> for &'a [u8] {
218  /// ```rust
219  /// # #[cfg(any(feature = "std", feature = "alloc"))]
220  /// # {
221  /// use hostaddr::{Buffer, Domain};
222  ///
223  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
224  /// let bytes: &[u8] = (&domain.into_inner()).into();
225  /// # }
226  /// ```
227  fn from(value: &'a Buffer) -> Self {
228    value.as_bytes()
229  }
230}
231
232impl Borrow<str> for Buffer {
233  /// ```rust
234  /// # #[cfg(any(feature = "std", feature = "alloc"))]
235  /// # {
236  /// use hostaddr::{Buffer, Domain};
237  /// use std::borrow::Borrow;
238  ///
239  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
240  /// let str: &str = domain.into_inner().borrow();
241  /// # }
242  /// ```
243  #[inline]
244  fn borrow(&self) -> &str {
245    self.as_ref()
246  }
247}
248
249impl AsRef<[u8]> for Buffer {
250  /// ```rust
251  /// # #[cfg(any(feature = "std", feature = "alloc"))]
252  /// # {
253  /// use hostaddr::{Buffer, Domain};
254  ///
255  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
256  /// let bytes: &[u8] = domain.into_inner().as_ref();
257  /// # }
258  /// ```
259  fn as_ref(&self) -> &[u8] {
260    self.as_bytes()
261  }
262}
263
264impl AsRef<str> for Buffer {
265  /// ```rust
266  /// # #[cfg(any(feature = "std", feature = "alloc"))]
267  /// # {
268  /// use hostaddr::{Buffer, Domain};
269  ///
270  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
271  /// let str: &str = domain.into_inner().as_ref();
272  /// # }
273  /// ```
274  fn as_ref(&self) -> &str {
275    self.as_str()
276  }
277}
278
279impl Buffer {
280  #[inline]
281  pub(super) const fn new() -> Self {
282    Self { buf: [0; 255] }
283  }
284
285  /// Returns the domain as a `str`.
286  #[inline]
287  pub fn as_str(&self) -> &str {
288    // SAFETY: The domain is guaranteed to be valid UTF-8.
289    unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len()]) }
290  }
291
292  /// Returns the domain as a `str`.
293  ///
294  /// ## Example
295  ///
296  /// ```rust
297  /// # #[cfg(any(feature = "std", feature = "alloc"))]
298  /// # {
299  /// use hostaddr::{Buffer, Domain};
300  ///
301  /// let domain: Domain<Buffer> = "example.com".parse().unwrap();
302  /// assert_eq!("example.com", domain.into_inner().const_as_str());
303  /// # }
304  /// ```
305  #[inline]
306  pub const fn const_as_str(&self) -> &str {
307    // SAFETY: The domain is guaranteed to be valid UTF-8.
308    unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
309  }
310
311  /// Returns the domain as a `[u8]`
312  #[inline]
313  pub const fn as_bytes(&self) -> &[u8] {
314    let len = self.len();
315    self.buf.split_at(len).0
316  }
317
318  /// Push a `u8` to the buffer.
319  #[inline]
320  pub const fn push(&mut self, byte: u8) -> Result<(), u8> {
321    let len = self.len();
322    if len == 254 {
323      return Err(byte);
324    }
325    self.buf[len] = byte;
326    self.buf[254] += 1;
327    Ok(())
328  }
329
330  #[inline]
331  const fn len(&self) -> usize {
332    *self.buf.last().unwrap() as usize
333  }
334
335  #[inline]
336  pub(super) fn copy_from_slice(slice: &[u8]) -> Self {
337    let len = slice.len();
338    assert!(len <= 254, "domain name too long");
339
340    let mut buf = Self::new();
341    buf.buf[..len].copy_from_slice(slice);
342    buf.buf[254] = len as u8;
343    buf
344  }
345
346  #[inline]
347  pub(super) fn copy_from_str(s: &str) -> Self {
348    Self::copy_from_slice(s.as_bytes())
349  }
350}
351
352impl core::fmt::Write for Buffer {
353  fn write_str(&mut self, s: &str) -> core::fmt::Result {
354    let len = s.len();
355    let pos = self.len();
356    if pos + len > 254 {
357      return Err(core::fmt::Error);
358    }
359    self.buf[pos..pos + len].copy_from_slice(s.as_bytes());
360    *self.buf.last_mut().unwrap() += len as u8;
361    Ok(())
362  }
363}
364
365#[cfg(feature = "serde")]
366const _: () = {
367  #[cfg(any(feature = "std", feature = "alloc"))]
368  use either::Either;
369
370  use serde::{Deserialize, Serialize};
371
372  use super::Domain;
373
374  impl Serialize for Buffer {
375    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
376    where
377      S: serde::Serializer,
378    {
379      if serializer.is_human_readable() {
380        serializer.serialize_str(self.as_str())
381      } else {
382        serializer.serialize_bytes(self.as_bytes())
383      }
384    }
385  }
386
387  impl<'de> Deserialize<'de> for Buffer {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390      D: serde::Deserializer<'de>,
391    {
392      if deserializer.is_human_readable() {
393        let s = <&str>::deserialize(deserializer)?;
394
395        #[cfg(any(feature = "std", feature = "alloc"))]
396        {
397          let res = Domain::try_from_str(s).map_err(serde::de::Error::custom)?;
398
399          Ok(match res {
400            Either::Left(d) => Self::copy_from_slice(d.0.as_bytes()),
401            Either::Right(d) => d,
402          })
403        }
404
405        #[cfg(not(any(feature = "std", feature = "alloc")))]
406        {
407          let res = Domain::try_from_ascii_str(s).map_err(serde::de::Error::custom)?;
408          Ok(Self::copy_from_slice(res.0.as_bytes()))
409        }
410      } else {
411        let bytes = <&[u8]>::deserialize(deserializer)?;
412
413        #[cfg(any(feature = "std", feature = "alloc"))]
414        {
415          let res = Domain::try_from_bytes(bytes).map_err(serde::de::Error::custom)?;
416          Ok(match res {
417            Either::Left(d) => Self::copy_from_slice(d.0),
418            Either::Right(d) => d,
419          })
420        }
421
422        #[cfg(not(any(feature = "std", feature = "alloc")))]
423        {
424          let res = Domain::try_from_ascii_bytes(bytes).map_err(serde::de::Error::custom)?;
425          Ok(Self::copy_from_slice(&res.0))
426        }
427      }
428    }
429  }
430};
431
432#[cfg(test)]
433mod test {
434  #[cfg(any(feature = "std", feature = "alloc", feature = "serde"))]
435  #[test]
436  fn test_ord() {
437    use super::*;
438
439    let a = Buffer::copy_from_str("a");
440    let b = Buffer::copy_from_str("b");
441    assert!(a < b);
442
443    assert!(a.partial_cmp(&b) == Some(core::cmp::Ordering::Less));
444  }
445}