bare_types/sys/
username.rs1use core::fmt;
28use core::str::FromStr;
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[non_exhaustive]
37pub enum UsernameError {
38 Empty,
42 TooLong(usize),
47 InvalidFirstCharacter,
52 InvalidCharacter,
57}
58
59impl fmt::Display for UsernameError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Empty => write!(f, "username cannot be empty"),
63 Self::TooLong(len) => write!(f, "username too long (got {len}, max 32 characters)"),
64 Self::InvalidFirstCharacter => {
65 write!(f, "username must start with a letter or underscore")
66 }
67 Self::InvalidCharacter => write!(f, "username contains invalid character"),
68 }
69 }
70}
71
72#[cfg(feature = "std")]
73impl std::error::Error for UsernameError {}
74
75#[repr(transparent)]
80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82pub struct Username(heapless::String<32>);
83
84impl Username {
85 pub const MAX_LEN: usize = 32;
87
88 pub fn new(s: &str) -> Result<Self, UsernameError> {
94 Self::validate(s)?;
95 let mut value = heapless::String::new();
96 value
97 .push_str(s)
98 .map_err(|_| UsernameError::TooLong(s.len()))?;
99 Ok(Self(value))
100 }
101
102 fn validate(s: &str) -> Result<(), UsernameError> {
104 if s.is_empty() {
105 return Err(UsernameError::Empty);
106 }
107 if s.len() > Self::MAX_LEN {
108 return Err(UsernameError::TooLong(s.len()));
109 }
110 let mut chars = s.chars();
111 if let Some(first) = chars.next()
112 && !first.is_ascii_alphabetic()
113 && first != '_'
114 {
115 return Err(UsernameError::InvalidFirstCharacter);
116 }
117 for ch in chars {
118 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {
119 return Err(UsernameError::InvalidCharacter);
120 }
121 }
122 Ok(())
123 }
124
125 #[must_use]
127 #[inline]
128 pub fn as_str(&self) -> &str {
129 &self.0
130 }
131
132 #[must_use]
134 #[inline]
135 pub const fn as_inner(&self) -> &heapless::String<32> {
136 &self.0
137 }
138
139 #[must_use]
141 #[inline]
142 pub fn into_inner(self) -> heapless::String<32> {
143 self.0
144 }
145
146 #[must_use]
150 #[inline]
151 pub fn is_system_user(&self) -> bool {
152 self.0.starts_with('_') || self.0 == "root" || self.0 == "daemon"
153 }
154
155 #[must_use]
159 #[inline]
160 pub fn is_service_account(&self) -> bool {
161 self.0.starts_with("svc-") || self.0.starts_with("service-")
162 }
163}
164
165impl AsRef<str> for Username {
166 fn as_ref(&self) -> &str {
167 self.as_str()
168 }
169}
170
171impl TryFrom<&str> for Username {
172 type Error = UsernameError;
173
174 fn try_from(s: &str) -> Result<Self, Self::Error> {
175 Self::new(s)
176 }
177}
178
179impl FromStr for Username {
180 type Err = UsernameError;
181
182 fn from_str(s: &str) -> Result<Self, Self::Err> {
183 Self::new(s)
184 }
185}
186
187impl fmt::Display for Username {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(f, "{}", self.0)
190 }
191}
192
193#[cfg(feature = "arbitrary")]
194impl<'a> arbitrary::Arbitrary<'a> for Username {
195 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
196 const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
197 const DIGITS: &[u8] = b"0123456789";
198
199 let len = 1 + (u8::arbitrary(u)? % 32).min(31);
201 let mut inner = heapless::String::<32>::new();
202
203 let first_byte = u8::arbitrary(u)?;
205 if first_byte % 10 == 0 {
206 inner
208 .push('_')
209 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
210 } else {
211 let first = ALPHABET[(first_byte % 26) as usize] as char;
212 inner
213 .push(first)
214 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
215 }
216
217 for _ in 1..len {
219 let byte = u8::arbitrary(u)?;
220 let c = match byte % 4 {
221 0 => ALPHABET[((byte >> 2) % 26) as usize] as char,
222 1 => DIGITS[((byte >> 2) % 10) as usize] as char,
223 2 => '_',
224 _ => '-',
225 };
226 inner
227 .push(c)
228 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
229 }
230
231 Ok(Self(inner))
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_new_valid() {
241 let username = Username::new("root").unwrap();
242 assert_eq!(username.as_str(), "root");
243 }
244
245 #[test]
246 fn test_new_empty() {
247 assert!(matches!(Username::new(""), Err(UsernameError::Empty)));
248 }
249
250 #[test]
251 fn test_new_too_long() {
252 let long_name = "a".repeat(33);
253 assert!(matches!(
254 Username::new(&long_name),
255 Err(UsernameError::TooLong(33))
256 ));
257 }
258
259 #[test]
260 fn test_new_invalid_first_character() {
261 assert!(matches!(
262 Username::new("1root"),
263 Err(UsernameError::InvalidFirstCharacter)
264 ));
265 assert!(matches!(
266 Username::new("-root"),
267 Err(UsernameError::InvalidFirstCharacter)
268 ));
269 }
270
271 #[test]
272 fn test_new_invalid_character() {
273 assert!(matches!(
274 Username::new("root@"),
275 Err(UsernameError::InvalidCharacter)
276 ));
277 assert!(matches!(
278 Username::new("root.user"),
279 Err(UsernameError::InvalidCharacter)
280 ));
281 }
282
283 #[test]
284 fn test_is_system_user() {
285 let root = Username::new("root").unwrap();
286 assert!(root.is_system_user());
287 let daemon = Username::new("daemon").unwrap();
288 assert!(daemon.is_system_user());
289 let system = Username::new("_system").unwrap();
290 assert!(system.is_system_user());
291 let user = Username::new("user").unwrap();
292 assert!(!user.is_system_user());
293 }
294
295 #[test]
296 fn test_is_service_account() {
297 let svc = Username::new("svc-api").unwrap();
298 assert!(svc.is_service_account());
299 let service = Username::new("service-api").unwrap();
300 assert!(service.is_service_account());
301 let user = Username::new("user").unwrap();
302 assert!(!user.is_service_account());
303 }
304
305 #[test]
306 fn test_from_str() {
307 let username: Username = "root".parse().unwrap();
308 assert_eq!(username.as_str(), "root");
309 }
310
311 #[test]
312 fn test_from_str_error() {
313 assert!("".parse::<Username>().is_err());
314 assert!("1root".parse::<Username>().is_err());
315 }
316
317 #[test]
318 fn test_display() {
319 let username = Username::new("root").unwrap();
320 assert_eq!(format!("{}", username), "root");
321 }
322
323 #[test]
324 fn test_as_ref() {
325 let username = Username::new("root").unwrap();
326 let s: &str = username.as_ref();
327 assert_eq!(s, "root");
328 }
329
330 #[test]
331 fn test_clone() {
332 let username = Username::new("root").unwrap();
333 let username2 = username.clone();
334 assert_eq!(username, username2);
335 }
336
337 #[test]
338 fn test_equality() {
339 let u1 = Username::new("root").unwrap();
340 let u2 = Username::new("root").unwrap();
341 let u3 = Username::new("user").unwrap();
342 assert_eq!(u1, u2);
343 assert_ne!(u1, u3);
344 }
345
346 #[test]
347 fn test_as_inner() {
348 let username = Username::new("root").unwrap();
349 let inner = username.as_inner();
350 assert_eq!(inner.as_str(), "root");
351 }
352
353 #[test]
354 fn test_into_inner() {
355 let username = Username::new("root").unwrap();
356 let inner = username.into_inner();
357 assert_eq!(inner.as_str(), "root");
358 }
359}