1#![forbid(unsafe_code)]
8
9use inspect::Inspect;
10use mesh_protobuf::Protobuf;
11use std::str::FromStr;
12use thiserror::Error;
13use zerocopy::FromBytes;
14use zerocopy::Immutable;
15use zerocopy::IntoBytes;
16use zerocopy::KnownLayout;
17
18#[derive(Copy, Clone, Protobuf, IntoBytes, Immutable, KnownLayout, FromBytes)]
20#[repr(transparent)]
21#[mesh(transparent)]
22pub struct AsciiString<const N: usize>([u8; N]);
23
24impl<const N: usize> std::fmt::Debug for AsciiString<N> {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 if let Some(s) = self.as_str() {
27 s.fmt(f)
28 } else {
29 self.as_bytes().fmt(f)
30 }
31 }
32}
33
34impl<const N: usize> Inspect for AsciiString<N> {
35 fn inspect(&self, req: inspect::Request<'_>) {
36 if let Some(s) = self.as_str() {
37 req.value(s)
38 } else {
39 req.value(self.as_bytes().to_vec())
40 }
41 }
42}
43
44#[derive(Debug, Error)]
46pub enum InvalidAsciiString {
47 #[error("string is too long")]
49 TooLong,
50 #[error("string contains non-ascii character")]
52 NonAscii,
53}
54
55impl<const N: usize> AsciiString<N> {
56 pub fn new(s: &str) -> Result<Self, InvalidAsciiString> {
60 let mut b = [b' '; N];
62 if !s.bytes().all(|c| matches!(c, 0x20..=0x7f)) {
63 return Err(InvalidAsciiString::NonAscii);
64 }
65 b.get_mut(..s.len())
66 .ok_or(InvalidAsciiString::TooLong)?
67 .copy_from_slice(s.as_bytes());
68
69 Ok(Self(b))
70 }
71
72 pub fn as_str(&self) -> Option<&str> {
76 Some(std::str::from_utf8(&self.0).ok()?.trim_end_matches(' '))
77 }
78
79 pub fn as_bytes(&self) -> &[u8; N] {
81 &self.0
82 }
83}
84
85impl<const N: usize> From<[u8; N]> for AsciiString<N> {
86 fn from(value: [u8; N]) -> Self {
87 Self(value)
88 }
89}
90
91impl<const N: usize> From<AsciiString<N>> for [u8; N] {
92 fn from(value: AsciiString<N>) -> Self {
93 value.0
94 }
95}
96
97impl<const N: usize> FromStr for AsciiString<N> {
98 type Err = InvalidAsciiString;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Self::new(s)
102 }
103}