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}