1use crate::error::Error;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd)]
6pub struct Namespace(String);
8
9impl From<Namespace> for String {
10 fn from(from: Namespace) -> String {
11 from.0
12 }
13}
14
15impl AsRef<str> for Namespace {
16 fn as_ref(&self) -> &str {
17 self.0.as_ref()
18 }
19}
20
21impl std::fmt::Display for Namespace {
22 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23 self.0.fmt(f)
24 }
25}
26
27impl std::str::FromStr for Namespace {
28 type Err = Error;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 let mut previous_char_was_alphanum = false;
32 for c in s.chars() {
33 if c.is_ascii_alphanumeric() {
34 previous_char_was_alphanum = true;
35 continue;
36 }
37 if c == '-' && previous_char_was_alphanum {
38 previous_char_was_alphanum = false;
39 continue;
40 }
41 if c == '-' {
42 return Err(Error::InvalidNamespaceHyphens);
43 }
44 return Err(Error::InvalidNamespaceChars);
45 }
46 if !previous_char_was_alphanum {
47 return Err(Error::InvalidNamespaceHyphens);
48 }
49 Ok(Namespace(s.into()))
50 }
51}
52
53#[cfg(test)]
54mod test {
55 use super::*;
56
57 #[test]
58 fn invalid_namespace() {
59 for s in [
60 "https://example.com/",
61 "-my-namespace",
62 "my-namespace-",
63 "my--namespace",
64 ] {
65 s.parse::<Namespace>().unwrap_err();
66 }
67 }
68
69 #[test]
70 fn valid_namespace() {
71 for s in ["my-namespace", "My-nAmespac3-2"] {
72 s.parse::<Namespace>().unwrap();
73 }
74 }
75}