use std::{
fmt,
str::{self, FromStr},
};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Name<const N: usize> {
bytes: [u8; N],
}
impl<const N: usize> Name<N> {
const LIGHTNING_BOLT_CHAR: u8 = 120;
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
if bytes.len() > N {
return Err(FromBytesError::TooLong);
}
let mut dest = [0; N];
for (index, byte) in bytes.iter().enumerate() {
match *byte {
byte if Self::is_byte_allowed(byte) => dest[index] = byte,
0 => break,
_ => return Err(FromBytesError::InvalidByte { byte: *byte, index }),
}
}
Ok(Self { bytes: dest })
}
pub fn bytes(&self) -> &[u8; N] {
&self.bytes
}
pub const fn capacity(&self) -> usize {
N
}
pub fn len(&self) -> usize {
self.bytes.iter().position(|c| *c == 0).unwrap_or(N)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) }
}
pub fn is_byte_allowed(byte: u8) -> bool {
(65..=90).contains(&byte) || (48..=57).contains(&byte) || byte == 32 || byte == Self::LIGHTNING_BOLT_CHAR }
}
impl<const N: usize> Default for Name<N> {
fn default() -> Self {
Self { bytes: [0; N] }
}
}
impl<const N: usize> fmt::Display for Name<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<'a, const N: usize> TryFrom<&'a [u8]> for Name<N> {
type Error = FromBytesError;
#[inline]
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl<'a, const N: usize> TryFrom<&'a str> for Name<N> {
type Error = FromBytesError;
#[inline]
fn try_from(str: &'a str) -> Result<Self, Self::Error> {
str.as_bytes().try_into()
}
}
impl<const N: usize> FromStr for Name<N> {
type Err = FromBytesError;
#[inline]
fn from_str(str: &str) -> Result<Self, Self::Err> {
str.try_into()
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum FromBytesError {
#[error("The slice did not fit in the name array")]
TooLong,
#[error("Byte {byte} at position {index} is not allowed as a name character")]
InvalidByte { byte: u8, index: usize },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_bytes() {
const HELLO: &str = "HELLO";
let name = Name::<8>::from_str(HELLO).expect("bytes rejected");
assert_eq!(name.len(), 5);
assert!(!name.is_empty());
assert_eq!(name.as_str(), HELLO);
assert_eq!(format!("{name}"), HELLO);
assert_eq!(
Name::<8>::from_str("123456789"),
Err(FromBytesError::TooLong)
);
assert_eq!(
Name::<8>::from_str("A!"),
Err(FromBytesError::InvalidByte {
byte: 33, index: 1
})
);
}
#[test]
fn default() {
let name = Name::<8>::default();
assert_eq!(name.len(), 0);
assert!(name.is_empty());
assert_eq!(name.as_str(), "");
}
}