1#[derive(Clone, PartialEq, Eq, Hash)]
29pub struct Namespace {
30 pub prefix: String,
32 pub suffix: String
34}
35
36const NAMESPACE_RULE: &str = r#"^[a-z:._\-/\d]+$"#;
37
38use regex::Regex;
39impl Namespace {
40 pub fn new(prefix: impl Into<String>, suffix: impl Into<String>) -> Namespace {
42 let prefix = prefix.into();
43 let suffix = suffix.into();
44 Namespace { prefix, suffix }
45 }
46
47 pub fn decode(value: impl Into<String>) -> Result<Namespace, DecodeError> {
73 let value = value.into();
74 let namespace_validation = Regex::new(&NAMESPACE_RULE)?;
75
76 if !namespace_validation.is_match(&value) {
77 return Err(DecodeError::InvalidNamespace(value));
78 }
79
80 let semicolon_counts = value.clone().chars().filter(|&c| c == ':').count();
81 if semicolon_counts > 1 {
82 return Err(DecodeError::TooManyColons(value));
83 }
84
85 let (prefix, suffix) = {
86 if semicolon_counts == 1 {
87 let result: Vec<&str> = value.split(':').take(2).collect();
88
89 (result[0], result[1])
90 }
91 else {
92 let result: &str = &value;
93 ("minecraft", result)
94 }
95 };
96
97 let result = Namespace::new(prefix, suffix);
98 Ok(result)
99 }
100}
101
102#[derive(Debug, PartialEq)]
104pub enum DecodeError {
105 RegexError(regex::Error),
107 InvalidNamespace(String),
110 TooManyColons(String)
113}
114
115use colored::*;
116impl From<&str> for Namespace {
119 fn from(value: &str) -> Namespace {
120 Namespace::decode(value).unwrap()
121 }
122}
123impl From<regex::Error> for DecodeError {
124 fn from(error: regex::Error) -> DecodeError {
125 DecodeError::RegexError(error)
126 }
127}
128impl fmt::Display for DecodeError {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 match self {
131 DecodeError::RegexError(error) => write!(f, "{}", error),
132 DecodeError::InvalidNamespace(original) => write!(f, "'{}' contain invalid character ({})", original.red(), format!("/{}/", NAMESPACE_RULE.yellow()).red()),
133 DecodeError::TooManyColons(original) => write!(f, "'{}' can only contain at most 1 colon.", original.cyan()),
134 }
135 }
136}
137
138use std::fmt;
139impl fmt::Debug for Namespace {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 write!(f, "{}:{}", self.prefix, self.suffix)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn decode_namespace_with_prefix() {
151 assert_eq!(
152 Namespace::from("boomber:test"),
153 Namespace {
154 prefix: String::from("boomber"),
155 suffix: String::from("test")
156 }
157 );
158 }
159
160 #[test]
161 #[should_panic]
162 fn panic_on_invalid_namespace() {
163 Namespace::from("this:namespace:IS invalid");
164 }
165
166 #[test]
167 fn decode_namespace_without_prefix() {
168 assert_eq!(
169 Namespace::from("no_prefix"),
170 Namespace {
171 prefix: String::from("minecraft"),
172 suffix: String::from("no_prefix")
173 }
174 );
175 }
176
177 #[test]
178 #[should_panic]
179 fn panic_on_invalid_namespace_without_prefix() {
180 Namespace::from("This Namespace Is Not Valid");
181 }
182}