use core::fmt::{self, Write};
use core::str;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct AsciiStr<const N: usize> {
bytes: [u8; N],
}
impl<const N: usize> fmt::Debug for AsciiStr<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_str() {
Ok(s) => write!(f, "{:?}", s),
Err(_) => write!(f, "AsciiStr(<invalid ascii>)"),
}
}
}
#[cfg(feature = "serde")]
impl<const N: usize> serde::Serialize for AsciiStr<N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str()
.map_err(serde::ser::Error::custom)?
.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, const N: usize> serde::Deserialize<'de> for AsciiStr<N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: &str = serde::Deserialize::deserialize(deserializer)?;
AsciiStr::try_from_str(s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsciiStrError {
InvalidAscii,
TooLong {
capacity: usize,
length: usize,
},
CorruptedData,
}
impl fmt::Display for AsciiStrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsciiStrError::InvalidAscii => f.write_str("input contained non-ASCII characters"),
AsciiStrError::TooLong { capacity, length } => {
write!(
f,
"input is too long: length {} exceeds capacity {}",
length, capacity
)
}
AsciiStrError::CorruptedData => {
f.write_str("internal data is corrupted or violates the representation invariant")
}
}
}
}
impl<const N: usize> AsciiStr<N> {
pub const fn new() -> Self {
Self { bytes: [0; N] }
}
pub const WIRE_SIZE: usize = N;
pub const DEFAULT: Self = Self::new();
#[cfg(feature = "wire")]
#[inline]
pub fn to_wire_bytes(&self) -> [u8; N] {
self.bytes
}
#[cfg(feature = "wire")]
pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != N {
return None;
}
let mut arr = [0u8; N];
arr.copy_from_slice(bytes);
Self::try_from_filled_buffer(arr).ok()
}
pub(crate) const fn from_filled_buffer(buffer: [u8; N]) -> Self {
Self { bytes: buffer }
}
pub fn try_from_filled_buffer(buffer: [u8; N]) -> Result<Self, AsciiStrError> {
if !buffer.is_ascii() {
return Err(AsciiStrError::InvalidAscii);
}
if let Some(first_nul) = buffer.iter().position(|&b| b == 0) {
if buffer[first_nul..].iter().any(|&b| b != 0) {
return Err(AsciiStrError::CorruptedData);
}
}
Ok(Self { bytes: buffer })
}
pub fn try_from_str(s: &str) -> Result<Self, AsciiStrError> {
if !s.is_ascii() {
return Err(AsciiStrError::InvalidAscii);
}
if s.len() > N {
return Err(AsciiStrError::TooLong {
capacity: N,
length: s.len(),
});
}
let mut bytes = [0u8; N];
bytes[..s.len()].copy_from_slice(s.as_bytes());
Ok(Self { bytes })
}
pub fn try_from_str_upper(s: &str) -> Result<Self, AsciiStrError> {
if !s.is_ascii() {
return Err(AsciiStrError::InvalidAscii);
}
if s.len() > N {
return Err(AsciiStrError::TooLong {
capacity: N,
length: s.len(),
});
}
let mut bytes = [0u8; N];
let src = s.as_bytes();
bytes[..src.len()].copy_from_slice(src);
bytes[..src.len()].make_ascii_uppercase();
Ok(Self { bytes })
}
pub fn as_str(&self) -> Result<&str, AsciiStrError> {
let len = self
.bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(self.bytes.len());
str::from_utf8(&self.bytes[..len]).map_err(|_| AsciiStrError::CorruptedData)
}
pub fn as_bytes(&self) -> &[u8] {
let len = self
.bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(self.bytes.len());
&self.bytes[..len]
}
pub fn len(&self) -> usize {
self.bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(self.bytes.len())
}
pub const fn is_empty(&self) -> bool {
self.bytes[0] == 0
}
pub const fn capacity(&self) -> usize {
N
}
pub fn from_str_truncate(s: &str) -> Self {
let mut bytes = [0u8; N];
let len = s.len().min(N);
bytes[..len].copy_from_slice(&s.as_bytes()[..len]);
Self { bytes }
}
pub fn from_display<T: core::fmt::Display>(value: T) -> Self {
let mut s = Self::new();
let _ = write!(&mut s, "{}", value);
s
}
pub fn from_fmt(args: core::fmt::Arguments<'_>) -> Self {
let mut s = Self::new();
let _ = write!(&mut s, "{}", args);
s
}
}
impl<const N: usize> TryFrom<&str> for AsciiStr<N> {
type Error = AsciiStrError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
AsciiStr::try_from_str(s)
}
}
impl<const N: usize> TryFrom<[u8; N]> for AsciiStr<N> {
type Error = AsciiStrError;
fn try_from(buffer: [u8; N]) -> Result<Self, Self::Error> {
AsciiStr::try_from_filled_buffer(buffer)
}
}
impl<const N: usize> core::fmt::Write for AsciiStr<N> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if !s.is_ascii() {
return Err(core::fmt::Error);
}
let current_len = self.len();
let remaining = N.saturating_sub(current_len);
if remaining == 0 {
return Ok(());
}
let to_copy = s.len().min(remaining);
self.bytes[current_len..current_len + to_copy].copy_from_slice(&s.as_bytes()[..to_copy]);
Ok(())
}
}