1use alloc::string::String;
2use core::{
3 fmt::{self, Display},
4 ops::Deref,
5 str::FromStr,
6};
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
8
9use bytestring::ByteString;
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub struct Subject(ByteString);
27
28impl Subject {
29 #[must_use]
35 pub fn from_static(value: &'static str) -> Self {
36 Self::try_from(ByteString::from_static(value)).expect("invalid Subject")
37 }
38
39 #[expect(
52 clippy::missing_panics_doc,
53 reason = "The subject validation is only made in debug"
54 )]
55 #[must_use]
56 pub fn from_dangerous_value(value: ByteString) -> Self {
57 if cfg!(debug_assertions)
58 && let Err(err) = validate_subject(&value)
59 {
60 panic!("Subject {value:?} isn't valid {err:?}");
61 }
62 Self(value)
63 }
64
65 #[must_use]
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69}
70
71impl Display for Subject {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 Display::fmt(&self.0, f)
74 }
75}
76
77impl TryFrom<ByteString> for Subject {
78 type Error = SubjectValidateError;
79
80 fn try_from(value: ByteString) -> Result<Self, Self::Error> {
81 validate_subject(&value)?;
82 Ok(Self::from_dangerous_value(value))
83 }
84}
85
86impl FromStr for Subject {
87 type Err = SubjectValidateError;
88
89 fn from_str(value: &str) -> Result<Self, Self::Err> {
90 validate_subject(value)?;
91 Ok(Self::from_dangerous_value(value.into()))
92 }
93}
94
95impl TryFrom<String> for Subject {
96 type Error = SubjectValidateError;
97
98 fn try_from(value: String) -> Result<Self, Self::Error> {
99 validate_subject(&value)?;
100 Ok(Self::from_dangerous_value(value.into()))
101 }
102}
103
104impl From<Subject> for ByteString {
105 fn from(value: Subject) -> Self {
106 value.0
107 }
108}
109
110impl AsRef<[u8]> for Subject {
111 fn as_ref(&self) -> &[u8] {
112 self.as_str().as_bytes()
113 }
114}
115
116impl AsRef<str> for Subject {
117 fn as_ref(&self) -> &str {
118 self.as_str()
119 }
120}
121
122impl Deref for Subject {
123 type Target = str;
124
125 fn deref(&self) -> &Self::Target {
126 self.as_str()
127 }
128}
129
130impl Serialize for Subject {
131 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
132 self.as_str().serialize(serializer)
133 }
134}
135
136impl<'de> Deserialize<'de> for Subject {
137 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
138 let s = ByteString::deserialize(deserializer)?;
139 s.try_into().map_err(de::Error::custom)
140 }
141}
142
143#[derive(Debug, thiserror::Error)]
145#[cfg_attr(test, derive(PartialEq, Eq))]
146pub enum SubjectValidateError {
147 #[error("Subject is empty")]
149 Empty,
150 #[error("Subject contained an illegal character")]
152 IllegalCharacter,
153 #[error("Subject contained a broken token")]
155 BrokenToken,
156 #[error("Subject contained a broken wildcard")]
159 BrokenWildcard,
160}
161
162fn validate_subject(subject: &str) -> Result<(), SubjectValidateError> {
163 if subject.is_empty() {
164 return Err(SubjectValidateError::Empty);
165 }
166
167 for b in subject.bytes() {
168 if b == b' ' || b == b'\t' || b == b'\r' || b == b'\n' {
176 return Err(SubjectValidateError::IllegalCharacter);
177 }
178 }
179
180 let mut tokens = subject.split('.').peekable();
181 while let Some(token) = tokens.next() {
182 if token.is_empty() || token.contains("..") {
183 return Err(SubjectValidateError::BrokenToken);
184 }
185
186 if token.len() > 1 && (token.contains(['*', '>'])) {
187 return Err(SubjectValidateError::BrokenWildcard);
188 }
189
190 if token == ">" && tokens.peek().is_some() {
191 return Err(SubjectValidateError::BrokenWildcard);
192 }
193 }
194
195 Ok(())
196}
197
198#[cfg(test)]
199mod tests {
200 use bytestring::ByteString;
201
202 use super::{Subject, SubjectValidateError};
203
204 #[test]
205 fn valid_subjects() {
206 let subjects = [
207 "cmd",
208 "cmd.endpoint",
209 "cmd.endpoint.detail",
210 "cmd.*.detail",
211 "cmd.*.*",
212 "cmd.endpoint.>",
213 ];
214 for subject in subjects {
215 let s = Subject::try_from(ByteString::from_static(subject)).unwrap();
216 assert_eq!(subject, s.as_str());
217 }
218 }
219
220 #[test]
221 fn invalid_subjects() {
222 let subjects = [
223 ("", SubjectValidateError::Empty),
224 ("cmd ", SubjectValidateError::IllegalCharacter),
225 ("cmd .endpoint", SubjectValidateError::IllegalCharacter),
226 (" cmd", SubjectValidateError::IllegalCharacter),
227 ("cmd.endpoint ", SubjectValidateError::IllegalCharacter),
228 (
229 "cmd.endpoint.detail ",
230 SubjectValidateError::IllegalCharacter,
231 ),
232 (
233 "cmd.endpoint.detail\r",
234 SubjectValidateError::IllegalCharacter,
235 ),
236 (
237 "cmd.endpoint.detail\t",
238 SubjectValidateError::IllegalCharacter,
239 ),
240 (
241 "cmd.endp oint.detail",
242 SubjectValidateError::IllegalCharacter,
243 ),
244 (
245 "cmd.endp\toint.detail",
246 SubjectValidateError::IllegalCharacter,
247 ),
248 (
249 "cmd.endpoint .detail",
250 SubjectValidateError::IllegalCharacter,
251 ),
252 (
253 "cmd.endpoint\t.detail",
254 SubjectValidateError::IllegalCharacter,
255 ),
256 (" ", SubjectValidateError::IllegalCharacter),
257 ("\t", SubjectValidateError::IllegalCharacter),
258 (
259 "cmd.endpoint.detail\n",
260 SubjectValidateError::IllegalCharacter,
261 ),
262 (
263 "cmd.endp\roint.detail",
264 SubjectValidateError::IllegalCharacter,
265 ),
266 (
267 "cmd.endp\noint.detail",
268 SubjectValidateError::IllegalCharacter,
269 ),
270 (
271 "cmd.endpoint\r.detail",
272 SubjectValidateError::IllegalCharacter,
273 ),
274 (
275 "cmd.endpoint\n.detail",
276 SubjectValidateError::IllegalCharacter,
277 ),
278 ("\r", SubjectValidateError::IllegalCharacter),
279 ("\n", SubjectValidateError::IllegalCharacter),
280 ("cmd..endpoint", SubjectValidateError::BrokenToken),
281 (".cmd.endpoint", SubjectValidateError::BrokenToken),
282 ("cmd.endpoint.", SubjectValidateError::BrokenToken),
283 ("cmd.**", SubjectValidateError::BrokenWildcard),
284 ("cmd.**.endpoint", SubjectValidateError::BrokenWildcard),
285 ("cmd.a*.endpoint", SubjectValidateError::BrokenWildcard),
286 ("cmd.*a.endpoint", SubjectValidateError::BrokenWildcard),
287 ("cmd.>.endpoint", SubjectValidateError::BrokenWildcard),
288 ("cmd.a>.endpoint", SubjectValidateError::BrokenWildcard),
289 ("cmd.endpoint.a>", SubjectValidateError::BrokenWildcard),
290 ("cmd.endpoint.>a", SubjectValidateError::BrokenWildcard),
291 ];
292 for (subject, expected_err) in subjects {
293 let err = Subject::try_from(ByteString::from_static(subject)).unwrap_err();
294 assert_eq!(expected_err, err);
295 }
296 }
297}