1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::errors::InvalidIrcFormatError;
use core::fmt;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ops::Index;

/// Tag Map as described through IRCv3.
///
/// test bench::bench_tag_create    ... bench:           1 ns/iter (+/- 0)
// test bench::bench_tag_index     ... bench:      14,707 ns/iter (+/- 2,811)
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Tags<'a> {
    raw: &'a str,
    tags: HashMap<&'a str, &'a str>,
}

impl<'a> Tags<'a> {
    /// Create a new Tag map from the given string. Expects it to be in valid IRCv3 format.
    pub fn new() -> Tags<'a> {
        Tags {
            raw: "",
            tags: HashMap::new(),
        }
    }

    /// Character length of the tags if formatted as IRC string.
    pub fn len_raw(&self) -> usize {
        self.raw.len()
    }

    pub fn len(&self) -> usize {
        self.tags.len()
    }

    pub fn is_empty(&self) -> bool {
        self.tags.is_empty()
    }

    /// Iterator over the tag entries.
    pub fn iter(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
        self.raw.split(';').map(|kv| {
            let mut split = kv.split('=');
            (split.next().unwrap(), split.next().unwrap())
        })
    }

    // Search for the key and return start and end of the value
    fn find(&self, key: &'a str) -> Option<(usize, usize)> {
        let key_equals = format!("{}=", key);
        self.raw
            .find(&key_equals)
            .map(|start| start + key.len() + 1)
            .and_then(|start| {
                self.raw[start..]
                    .find(';')
                    .or_else(|| self.raw[start..].find(' '))
                    .or_else(|| Some(self.raw.len() - start))
                    .map(|end| (start, start + end))
            })
    }

    pub fn get(&self, key: &'a str) -> Option<&'a str> {
        self.find(key).map(|(start, end)| &self.raw[start..end])
    }
}

impl<'a> TryFrom<&'a str> for Tags<'a> {
    type Error = InvalidIrcFormatError;

    fn try_from(raw: &'a str) -> Result<Self, Self::Error> {
        let size = raw.chars().filter(|c| *c == ';').count();
        let mut tags = HashMap::with_capacity(size);

        for key_val in raw.split(';') {
            if key_val.is_empty() {
                continue;
            }
            let mut split = key_val.split('=');
            let key = match split.next() {
                Some(key) => key,
                None => return Err(InvalidIrcFormatError::Tag(raw.to_string())),
            };
            let value = match split.next() {
                Some(value) => value,
                None => return Err(InvalidIrcFormatError::Tag(raw.to_string())),
            };
            if split.next().is_some() {
                return Err(InvalidIrcFormatError::Tag(raw.to_string()));
            }
            tags.insert(key, value);
        }
        tags.shrink_to_fit();
        Ok(Tags { raw, tags })
    }
}

impl<'a> Index<&'a str> for Tags<'a> {
    type Output = str;

    fn index(&self, key: &'a str) -> &Self::Output {
        self.tags[key]
    }
}

impl<'a> fmt::Display for Tags<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.raw.fmt(f)
    }
}

impl<'a> AsRef<str> for Tags<'a> {
    fn as_ref(&self) -> &str {
        self.raw
    }
}

#[cfg(test)]
mod tests {
    use crate::tags::Tags;
    use crate::InvalidIrcFormatError;
    use std::convert::TryFrom;

    #[test]
    fn test_get_and_index() -> Result<(), InvalidIrcFormatError> {
        let tags = Tags::try_from("hello=world;whats=goes;hello2=world2")?;
        let get = tags.get("hello");
        let index = &tags["hello"];
        assert_eq!(get, Some("world"));
        assert_eq!(index, "world");
        let get = tags.get("whats");
        let index = &tags["whats"];
        assert_eq!(get, Some("goes"));
        assert_eq!(index, "goes");
        let get = tags.get("world");
        // Would panic
        // let index = &tags["world"];
        assert_eq!(get, None);
        Ok(())
    }
}