Skip to main content

tmi_parser/
tags.rs

1//! Tags for TMI messages
2
3use std::{collections::HashMap};
4use std::hash::BuildHasherDefault;
5
6/// [`Tags`] is type alias for a [`HashMap`] whose keys are [`&str`] and values [`TagValue`].
7/// Uses slice [`&str`] instead of owned [`String`] in order to avoid data duplication.
8/// Also uses a custom hasher which implements the 'Fnv 1a' hash function.
9///
10/// # Examples
11///
12/// ```
13/// # use tmi_parser::*;
14/// let mut map = Tags::default();
15/// map.insert("hello", TagValue::String("world"));
16/// # assert_eq!(*map.get("hello").unwrap(), TagValue::String("world"));
17/// ````
18pub type Tags<'a> = HashMap<&'a str, TagValue<'a>, BuildHasherDefault<hash::TagsHasher>>;
19
20/// [`TagsHasher`] is an internal implementation of the 'Fnv 1a' hash function.
21/// It lives under the private module hash and was made only for improving [`Tags`] performance.
22/// Since [`Tags`] will contain few items in average, the faster the hash, the better the performance,
23mod hash {
24    use std::hash::Hasher;
25
26    pub struct TagsHasher(u64);
27
28    impl Default for TagsHasher {
29        #[inline]
30        fn default() -> TagsHasher {
31            TagsHasher(14695981039346656037)
32        }
33    }
34
35    impl Hasher for TagsHasher {
36        #[inline]
37        fn finish(&self) -> u64 {
38            self.0
39        }
40
41        #[inline]
42        fn write(&mut self, bytes: &[u8]) {
43            let TagsHasher(mut hash) = *self;
44
45            for byte in bytes.iter() {
46                hash ^= *byte as u64;
47                hash = hash.wrapping_mul(1099511628211);
48            }
49
50            *self = TagsHasher(hash);
51        }
52    }
53}
54
55/// Possible values of message tags.
56#[derive(Debug, PartialEq)]
57pub enum TagValue<'a> {
58    /// Represents a parsed sequence of numbers of type u32.
59    Number(u32),
60    /// Represents a parsed sequence of numbers of type u64.
61    Timestamp(u64),
62    /// Boolean values represents literal "1" (true) or "0" (false).
63    ///
64    /// Note that a single digit number "1" or "0" may be represented
65    /// as a Boolean value instead of a Number value.
66    /// Type conversion should therefore be done by the user code.
67    Boolean(bool),
68    /// Strings represent an unparsed string literal.
69    String(&'a str),
70    /// None represents literal empty string "".
71    None,
72}
73
74impl<'a> TagValue<'a> {
75    /// Returns a TagValue variant based on the given [`&str`].
76    pub fn new(val: &'a str) -> TagValue<'a> {
77        match val {
78            "" => TagValue::None,
79            "0" => TagValue::Boolean(false),
80            "1" => TagValue::Boolean(true),
81            _ => {
82                if let Ok(num) = val.parse::<u32>() {
83                    TagValue::Number(num)
84                } else if let Ok(tm) = val.parse::<u64>() {
85                    TagValue::Timestamp(tm)
86                } else if val.starts_with('#') {
87                    // Try to convert hexadecimal values, used by the 'color' tag, to Number.
88                    if let Ok(num) = u32::from_str_radix(&val[1..], 16) {
89                        TagValue::Number(num)
90                    } else {
91                        TagValue::String(val)
92                    }
93                } else {
94                    TagValue::String(val)
95                }
96            }
97        }
98    }
99}