use core::{ borrow, cmp::Ordering, fmt, hash::{Hash, Hasher}, ops, str, }; #[cfg(not(feature = "std"))] use core::convert::TryFrom; #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::{ borrow::{Cow, ToOwned}, boxed::Box, string::String, }; #[cfg(feature = "std")] use std::borrow::Cow; #[derive(Clone)] pub struct CappedString { buf: [u8; N], len: u8, } impl CappedString { const MAX_LEN: u8 = { #[allow(clippy::cast_possible_truncation, clippy::checked_conversions)] if N <= u8::MAX as usize { N as u8 } else { panic!("`N` must be within the bounds of `u8`") } }; /// Creates a new `CappedString` from a given byte buffer and length. /// /// # Safety /// /// The first `len` bytes of `buf` (i.e. `buf[..len]`) must be valid UTF-8. #[inline] #[must_use] pub const unsafe fn from_raw_parts(buf: [u8; N], len: u8) -> Self { Self { buf, len } } /// Returns a new empty `CappedString`. #[inline] #[must_use] pub const fn empty() -> Self { // SAFETY: // The first zero bytes of the buffer are valid UTF-8, because an empty byte slice is // valid UTF-8. unsafe { Self::from_raw_parts([0; N], 0) } } #[inline] pub fn new(s: &S) -> Result where S: AsRef + ?Sized, { // Convert the string to a byte slice, which is guaranteed to be valid UTF-8 since this is // an invariant of `str`. let s = >::as_ref(s).as_bytes(); // If the length of the string is greater than `Self::MAX_LEN`, it will not fit in the // buffer so return `None`. let len = u8::try_from(s.len()) .ok() .and_then(|len| (len <= Self::MAX_LEN).then_some(len)) .ok_or(Error { max_len: N, actual_len: s.len(), })?; let mut buf = [0; N]; buf[..usize::from(len)].copy_from_slice(s); // SAFETY: // The first `len` bytes of the buffer are valid UTF-8 because the first `len` bytes of // the buffer contain data copied from a `&str`, and `&str` is always valid UTF-8. unsafe { Ok(Self::from_raw_parts(buf, len)) } } /// Returns a string slice pointing to the underlying string data. pub fn as_str(&self) -> &str { // SAFETY: // `len` being less than or equal to `N` is an invariant of `CappedString`, so it is // always within the bounds of `buf`. let slice = unsafe { self.buf.get_unchecked(..usize::from(self.len)) }; // SAFETY: // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `CappedString`. unsafe { str::from_utf8_unchecked(slice) } } /// Returns a mutable string slice pointing to the underlying string data. pub fn as_str_mut(&mut self) -> &mut str { // SAFETY: // `len` being less than or equal to `N` is an invariant of `CappedString`, so it is // always within the bounds of `buf`. let slice = unsafe { self.buf.get_unchecked_mut(..usize::from(self.len)) }; // SAFETY: // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `CappedString`. unsafe { str::from_utf8_unchecked_mut(slice) } } pub fn len(&self) -> usize { usize::from(self.len) } pub fn is_empty(&self) -> bool { self.len == 0 } } #[cfg(feature = "alloc")] impl CappedString { pub fn into_boxed_str(self) -> Box { self.as_str().into() } pub fn into_string(self) -> String { self.as_str().to_owned() } } impl Default for CappedString { #[inline] fn default() -> Self { Self::empty() } } impl ops::Deref for CappedString { type Target = str; #[inline] fn deref(&self) -> &Self::Target { self.as_str() } } impl ops::DerefMut for CappedString { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.as_str_mut() } } impl AsRef for CappedString { #[inline] fn as_ref(&self) -> &str { self } } impl AsMut for CappedString { #[inline] fn as_mut(&mut self) -> &mut str { self } } impl borrow::Borrow for CappedString { #[inline] fn borrow(&self) -> &str { self } } impl borrow::BorrowMut for CappedString { #[inline] fn borrow_mut(&mut self) -> &mut str { self } } impl<'a, const N: usize> TryFrom<&'a str> for CappedString { type Error = Error; #[inline] fn try_from(s: &'a str) -> Result { Self::new(s) } } #[cfg(feature = "alloc")] impl TryFrom for CappedString { type Error = Error; #[inline] fn try_from(s: String) -> Result { Self::new(&s) } } #[cfg(feature = "alloc")] impl<'a, const N: usize> TryFrom> for CappedString { type Error = Error; #[inline] fn try_from(s: Cow<'a, str>) -> Result { Self::new(&s) } } #[cfg(feature = "alloc")] impl From> for String { #[inline] fn from(s: CappedString) -> Self { s.into_string() } } impl PartialEq> for CappedString { #[inline] fn eq(&self, other: &CappedString) -> bool { **self == **other } } impl Eq for CappedString {} impl PartialOrd> for CappedString { #[inline] fn partial_cmp(&self, other: &CappedString) -> Option { (**self).partial_cmp(&**other) } } impl Ord for CappedString { #[inline] fn cmp(&self, other: &Self) -> Ordering { (**self).cmp(&**other) } } impl Hash for CappedString { #[inline] fn hash(&self, state: &mut H) { (**self).hash(state); } } impl str::FromStr for CappedString { type Err = Error; #[inline] fn from_str(s: &str) -> Result { Self::new(s) } } impl fmt::Debug for CappedString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } impl fmt::Display for CappedString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&**self, f) } } #[derive(Debug)] pub struct Error { max_len: usize, actual_len: usize, } impl Error { pub fn max_len(&self) -> usize { self.max_len } pub fn actual_len(&self) -> usize { self.actual_len } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "string of length {} exceeds limit for `CappedString<{}>`", self.actual_len, self.max_len ) } } #[cfg(feature = "std")] impl std::error::Error for Error {}