1use std::{fmt, slice};
9
10use smallvec::SmallVec;
11use thiserror::Error;
12
13pub const MAX_NAME_LEN: usize = u16::MAX as usize;
17
18#[derive(Debug, Error, PartialEq, Eq)]
24pub enum NameError {
25 #[error("{what} must not be empty")]
26 Empty { what: &'static str },
27 #[error("{what} is {len} bytes, exceeding the {max}-byte maximum")]
28 TooLong {
29 what: &'static str,
30 len: usize,
31 max: usize,
32 },
33}
34
35fn validate_name(s: &str, what: &'static str) -> Result<(), NameError> {
36 if s.is_empty() {
37 return Err(NameError::Empty { what });
38 }
39 if s.len() > MAX_NAME_LEN {
40 return Err(NameError::TooLong {
41 what,
42 len: s.len(),
43 max: MAX_NAME_LEN,
44 });
45 }
46 Ok(())
47}
48
49#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
56pub struct EventType(Box<str>);
57
58impl EventType {
59 pub fn new(s: impl Into<Box<str>>) -> Result<Self, NameError> {
61 let s = s.into();
62 validate_name(&s, "event type")?;
63 Ok(EventType(s))
64 }
65
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69
70 pub fn into_inner(self) -> Box<str> {
71 self.0
72 }
73}
74
75impl AsRef<str> for EventType {
76 fn as_ref(&self) -> &str {
77 &self.0
78 }
79}
80
81impl fmt::Display for EventType {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 self.0.fmt(f)
84 }
85}
86
87#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
91pub struct Tag(Box<str>);
92
93impl Tag {
94 pub fn new(s: impl Into<Box<str>>) -> Result<Self, NameError> {
96 let s = s.into();
97 validate_name(&s, "tag")?;
98 Ok(Tag(s))
99 }
100
101 pub fn as_str(&self) -> &str {
102 &self.0
103 }
104
105 pub fn into_inner(self) -> Box<str> {
106 self.0
107 }
108}
109
110impl AsRef<str> for Tag {
111 fn as_ref(&self) -> &str {
112 &self.0
113 }
114}
115
116impl fmt::Display for Tag {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 self.0.fmt(f)
119 }
120}
121
122#[derive(Debug, Error, PartialEq, Eq)]
128pub enum TagsError {
129 #[error("duplicate tag '{tag}'")]
130 Duplicate { tag: Tag },
131}
132
133#[derive(Clone, Debug, Default, PartialEq, Eq)]
145pub struct Tags(SmallVec<[Tag; 4]>);
146
147impl Tags {
148 pub fn new(tags: impl IntoIterator<Item = Tag>) -> Result<Self, TagsError> {
155 let mut tags: SmallVec<[Tag; 4]> = tags.into_iter().collect();
156 tags.sort_unstable();
157 if let Some(i) = (1..tags.len()).find(|&i| tags[i] == tags[i - 1]) {
161 return Err(TagsError::Duplicate {
162 tag: tags.swap_remove(i),
163 });
164 }
165 Ok(Tags(tags))
166 }
167
168 pub fn empty() -> Self {
170 Tags(SmallVec::new())
171 }
172
173 pub fn into_inner(self) -> SmallVec<[Tag; 4]> {
174 self.0
175 }
176
177 pub fn as_slice(&self) -> &[Tag] {
178 &self.0
179 }
180
181 pub fn len(&self) -> usize {
182 self.0.len()
183 }
184
185 pub fn is_empty(&self) -> bool {
186 self.0.is_empty()
187 }
188
189 pub fn iter(&self) -> slice::Iter<'_, Tag> {
190 self.0.iter()
191 }
192}
193
194impl IntoIterator for Tags {
195 type Item = Tag;
196 type IntoIter = smallvec::IntoIter<[Tag; 4]>;
197
198 fn into_iter(self) -> Self::IntoIter {
199 self.0.into_iter()
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn tag(s: &str) -> Tag {
208 Tag::new(s).unwrap()
209 }
210
211 fn tags(items: &[&str]) -> Tags {
212 Tags::new(items.iter().map(|s| tag(s)).collect::<SmallVec<[Tag; 4]>>()).unwrap()
213 }
214
215 #[test]
216 fn name_rejects_empty() {
217 assert_eq!(
218 EventType::new(""),
219 Err(NameError::Empty { what: "event type" })
220 );
221 assert_eq!(Tag::new(""), Err(NameError::Empty { what: "tag" }));
222 }
223
224 #[test]
225 fn name_rejects_over_long() {
226 let big = "x".repeat(MAX_NAME_LEN + 1);
227 assert_eq!(
228 EventType::new(big),
229 Err(NameError::TooLong {
230 what: "event type",
231 len: MAX_NAME_LEN + 1,
232 max: MAX_NAME_LEN,
233 })
234 );
235 assert!(Tag::new("y".repeat(MAX_NAME_LEN)).is_ok());
237 }
238
239 #[test]
240 fn name_accessors_and_ordering() {
241 assert_eq!(EventType::new("Registered").unwrap().as_str(), "Registered");
242 assert_eq!(<Tag as AsRef<str>>::as_ref(&tag("course:c1")), "course:c1");
243 assert!(tag("course:a") < tag("course:b"));
244 assert_eq!(format!("{}", tag("student:s1")), "student:s1");
245 }
246
247 #[test]
248 fn tags_sorts_input() {
249 let t = tags(&["course:c1", "student:s1", "admin:a1"]);
250 let got: Vec<&str> = t.iter().map(|t| t.as_str()).collect();
251 assert_eq!(got, ["admin:a1", "course:c1", "student:s1"]);
252 }
253
254 #[test]
255 fn tags_rejects_duplicates() {
256 let input: SmallVec<[Tag; 4]> = [tag("course:c1"), tag("course:c1")].into_iter().collect();
257 assert_eq!(
258 Tags::new(input),
259 Err(TagsError::Duplicate {
260 tag: tag("course:c1")
261 })
262 );
263 }
264
265 #[test]
266 fn tags_empty_is_empty() {
267 assert!(Tags::empty().is_empty());
268 assert_eq!(Tags::empty().len(), 0);
269 }
270}