raystack_core/
tag.rs

1use thiserror::Error;
2
3/// A Haystack tag name.
4#[derive(Clone, Debug, Eq, Hash, PartialEq)]
5pub struct TagName(String);
6
7impl TagName {
8    /// Create a new `TagName`
9    pub fn new(s: String) -> Option<Self> {
10        if is_tag_name(&s) {
11            Some(TagName(s))
12        } else {
13            None
14        }
15    }
16
17    /// Return the tag name string.
18    pub fn into_string(self) -> String {
19        self.0
20    }
21}
22
23impl std::fmt::Display for TagName {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29/// Return true if the string is a valid tag name.
30pub fn is_tag_name<T: AsRef<str>>(s: T) -> bool {
31    let s = s.as_ref();
32    if s.is_empty() {
33        false
34    } else {
35        let chars = s.chars().enumerate();
36        let mut is_tag_name = true;
37        for (index, c) in chars {
38            if index == 0 {
39                if !c.is_ascii_lowercase() {
40                    is_tag_name = false;
41                    break;
42                }
43            } else if !(c.is_ascii_alphanumeric() || c == '_') {
44                is_tag_name = false;
45                break;
46            };
47        }
48        is_tag_name
49    }
50}
51
52impl std::str::FromStr for TagName {
53    type Err = ParseTagNameError;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        if is_tag_name(s) {
57            Ok(TagName(s.to_owned()))
58        } else {
59            let unparsable_tag_name = s.to_owned();
60            Err(ParseTagNameError {
61                unparsable_tag_name,
62            })
63        }
64    }
65}
66
67impl std::convert::AsRef<str> for TagName {
68    fn as_ref(&self) -> &str {
69        &self.0
70    }
71}
72
73impl std::convert::AsRef<[u8]> for TagName {
74    fn as_ref(&self) -> &[u8] {
75        self.0.as_ref()
76    }
77}
78
79/// An error indicating that a `TagName` could not be parsed.
80#[derive(Clone, Debug, Eq, Error, PartialEq)]
81#[error("Could not parse a tag name from the string {unparsable_tag_name}")]
82pub struct ParseTagNameError {
83    unparsable_tag_name: String,
84}
85
86#[cfg(test)]
87mod tests {
88    #[test]
89    fn is_tag_name() {
90        use super::is_tag_name;
91        assert_eq!(is_tag_name("siteRef"), true);
92        assert_eq!(is_tag_name("s"), true);
93        assert_eq!(is_tag_name("s1"), true);
94        assert_eq!(is_tag_name(""), false);
95        assert_eq!(is_tag_name("1s"), false);
96        assert_eq!(is_tag_name("s%"), false);
97        assert_eq!(is_tag_name("s-"), false);
98        assert_eq!(is_tag_name("s_s"), true);
99        assert_eq!(is_tag_name("_s"), false);
100        assert_eq!(is_tag_name("s_"), true);
101    }
102}