1use std::fmt;
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 AsRef<str>) -> Result<Self, NameError> {
61 let s = s.as_ref();
62 validate_name(s, "event type")?;
63 Ok(EventType(Box::from(s)))
64 }
65
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69}
70
71impl AsRef<str> for EventType {
72 fn as_ref(&self) -> &str {
73 &self.0
74 }
75}
76
77impl fmt::Display for EventType {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 self.0.fmt(f)
80 }
81}
82
83#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
87pub struct Tag(Box<str>);
88
89impl Tag {
90 pub fn new(s: impl AsRef<str>) -> Result<Self, NameError> {
92 let s = s.as_ref();
93 validate_name(s, "tag")?;
94 Ok(Tag(Box::from(s)))
95 }
96
97 pub fn as_str(&self) -> &str {
98 &self.0
99 }
100}
101
102impl AsRef<str> for Tag {
103 fn as_ref(&self) -> &str {
104 &self.0
105 }
106}
107
108impl fmt::Display for Tag {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 self.0.fmt(f)
111 }
112}
113
114#[derive(Debug, Error, PartialEq, Eq)]
120pub enum TagsError {
121 #[error("duplicate tag '{tag}'")]
122 Duplicate { tag: Tag },
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq)]
137pub struct Tags(SmallVec<[Tag; 4]>);
138
139impl Tags {
140 pub fn new(tags: impl IntoIterator<Item = Tag>) -> Result<Self, TagsError> {
147 let mut tags: SmallVec<[Tag; 4]> = tags.into_iter().collect();
148 tags.sort_unstable();
149 if let Some(i) = (1..tags.len()).find(|&i| tags[i] == tags[i - 1]) {
153 return Err(TagsError::Duplicate {
154 tag: tags.swap_remove(i),
155 });
156 }
157 Ok(Tags(tags))
158 }
159
160 pub fn empty() -> Self {
162 Tags(SmallVec::new())
163 }
164
165 pub fn as_slice(&self) -> &[Tag] {
166 &self.0
167 }
168
169 pub fn len(&self) -> usize {
170 self.0.len()
171 }
172
173 pub fn is_empty(&self) -> bool {
174 self.0.is_empty()
175 }
176
177 pub fn iter(&self) -> std::slice::Iter<'_, Tag> {
178 self.0.iter()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn tag(s: &str) -> Tag {
187 Tag::new(s).unwrap()
188 }
189
190 fn tags(items: &[&str]) -> Tags {
191 Tags::new(items.iter().map(|s| tag(s)).collect::<SmallVec<[Tag; 4]>>()).unwrap()
192 }
193
194 #[test]
195 fn name_rejects_empty() {
196 assert_eq!(
197 EventType::new(""),
198 Err(NameError::Empty { what: "event type" })
199 );
200 assert_eq!(Tag::new(""), Err(NameError::Empty { what: "tag" }));
201 }
202
203 #[test]
204 fn name_rejects_over_long() {
205 let big = "x".repeat(MAX_NAME_LEN + 1);
206 assert_eq!(
207 EventType::new(&big),
208 Err(NameError::TooLong {
209 what: "event type",
210 len: MAX_NAME_LEN + 1,
211 max: MAX_NAME_LEN,
212 })
213 );
214 assert!(Tag::new("y".repeat(MAX_NAME_LEN)).is_ok());
216 }
217
218 #[test]
219 fn name_accessors_and_ordering() {
220 assert_eq!(EventType::new("Registered").unwrap().as_str(), "Registered");
221 assert_eq!(<Tag as AsRef<str>>::as_ref(&tag("course:c1")), "course:c1");
222 assert!(tag("course:a") < tag("course:b"));
223 assert_eq!(format!("{}", tag("student:s1")), "student:s1");
224 }
225
226 #[test]
227 fn tags_sorts_input() {
228 let t = tags(&["course:c1", "student:s1", "admin:a1"]);
229 let got: Vec<&str> = t.iter().map(|t| t.as_str()).collect();
230 assert_eq!(got, ["admin:a1", "course:c1", "student:s1"]);
231 }
232
233 #[test]
234 fn tags_rejects_duplicates() {
235 let input: SmallVec<[Tag; 4]> = [tag("course:c1"), tag("course:c1")].into_iter().collect();
236 assert_eq!(
237 Tags::new(input),
238 Err(TagsError::Duplicate {
239 tag: tag("course:c1")
240 })
241 );
242 }
243
244 #[test]
245 fn tags_empty_is_empty() {
246 assert!(Tags::empty().is_empty());
247 assert_eq!(Tags::empty().len(), 0);
248 }
249}