statsd_parser/parser/
mod.rs

1use std::{error,fmt};
2use std::num::ParseFloatError;
3use std::vec::Vec;
4use std::collections::BTreeMap;
5
6
7pub mod metric_parser;
8pub mod service_check_parser;
9
10#[derive(Debug,PartialEq)]
11pub enum ParseError {
12    /// No content in statsd message
13    EmptyInput,
14    /// Incomplete input in statsd message
15    IncompleteInput,
16    /// No name in input
17    NoName,
18    /// Value is not a float
19    ValueNotFloat,
20    /// Sample rate is not a float
21    SampleRateNotFloat,
22    /// Metric type is unknown
23    UnknownMetricType,
24}
25
26impl fmt::Display for ParseError {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        match *self {
29            ParseError::EmptyInput => write!(f, "Empty input"),
30            ParseError::IncompleteInput => write!(f, "Incomplete input"),
31            ParseError::NoName => write!(f, "No name in input"),
32            ParseError::ValueNotFloat => write!(f, "Value is not a float"),
33            ParseError::SampleRateNotFloat => write!(f, "Sample rate is not a float"),
34            ParseError::UnknownMetricType => write!(f, "Unknown metric type")
35        }
36    }
37}
38
39impl error::Error for ParseError {
40  // Implement description so that older versions of rust still work
41  fn description(&self) -> &str {
42    "description() is deprecated; use Display"
43  }
44}
45
46#[derive(Debug,PartialEq)]
47pub struct Parser {
48    chars: Vec<char>,
49    len: usize,
50    pos: usize
51}
52
53impl Parser {
54    // Returns a Parser for given string
55    pub fn new(buf: String) -> Parser {
56        let chars: Vec<char> = buf.chars().collect();
57        let len = chars.len();
58        Parser {
59            chars: chars,
60            len:   len,
61            pos:   0
62        }
63    }
64
65    /// Consumes the buffer until the given character is found
66    /// or the end is reached
67    fn take_until(&mut self, to_match: Vec<char>) -> String {
68        let mut chars = Vec::new();
69        loop {
70            if self.pos >= self.len {
71                break
72            }
73            let current_char = self.chars[self.pos];
74            self.pos += 1;
75            if to_match.contains(&current_char) {
76                break
77            } else {
78                chars.push(current_char);
79            }
80        }
81        chars.into_iter().collect()
82    }
83
84    /// Consumes the buffer untill the character is found
85    /// or the end is reached, the result is parsed into a float
86    fn take_float_until(&mut self, to_match: Vec<char>) -> Result<f64, ParseFloatError> {
87        let string = self.take_until(to_match);
88        string.parse()
89    }
90
91    /// Returns the current character in the buffer
92    fn peek(&mut self) -> Option<char> {
93        if self.pos == self.len {
94            None
95        } else {
96            Some(self.chars[self.pos])
97        }
98    }
99
100    /// Returns the previous character in the buffer
101    fn last(&mut self) -> Option<char> {
102        if self.pos == 0 {
103            None
104        } else {
105            Some(self.chars[self.pos - 1 ])
106        }
107    }
108
109    /// Moves the buffer to the next position
110    fn skip(&mut self) {
111        self.pos += 1;
112    }
113
114    fn parse_tags(&mut self) -> BTreeMap<String, String> {
115        let mut tags = BTreeMap::new();
116
117        self.skip(); // Skip the `#`
118
119        // Loop over the remaining buffer and see
120        // if we can find key/value pairs, separated by : and ,
121        // in the format key:value,key:value
122        loop {
123            // Stop the loop if we've encountered a separator (|)
124            if Some('|') == self.last() {
125              break
126            }
127
128            // Stop the loop if we have nothing left to parse
129            let tag = self.take_until(vec![',', '|']);
130            if tag.is_empty() {
131                break
132            }
133
134            // Split the string on ':' and use the first part as key, last parts as value
135            // host:localhost:3000 will become key: host, value: localhost:3000
136            let mut split= tag.split(":");
137            match split.next() {
138                Some(key) => {
139                  let parts: Vec<&str> = split.collect();
140                  tags.insert(key.to_owned(), parts.join(":"))
141                },
142                None => break
143            };
144        }
145
146        tags
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use std::collections::BTreeMap;
153
154    use super::Parser;
155
156    #[test]
157    fn test_take_until() {
158        let mut parser = Parser::new("this is a string".to_string());
159
160        // Returns up untill the first occurrence of the character
161        assert_eq!(parser.take_until(vec![' ']), "this");
162
163        // Moves the position to the first occurrence
164        assert_eq!(parser.pos, 5);
165
166        // Returns the rest of the string if character is not found
167        assert_eq!(parser.take_until(vec!['.']), "is a string");
168
169        // Moves the position to the end of the string
170        assert_eq!(parser.pos, 16);
171    }
172
173    #[test]
174    fn test_take_float_until() {
175        let mut parser = Parser::new("10.01|number|string".to_string());
176
177        // Returns float up untill the first occurrence of the character
178        assert_eq!(parser.take_float_until(vec!['|']), Ok(10.01));
179
180        // Moves the position to the first occurrence
181        assert_eq!(parser.pos, 6);
182
183        // Returns err if not float
184        assert!(parser.take_float_until(vec!['|']).is_err());
185
186        // Moves the position to the end of the string
187        assert_eq!(parser.pos, 13);
188    }
189
190    #[test]
191    fn test_peek() {
192        let mut parser = Parser::new("this is a string".to_string());
193        parser.pos = 10;
194
195        // Returns the character at the current position
196        assert_eq!(parser.peek(), Some('s'));
197
198        // It does not move the position
199        assert_eq!(parser.pos, 10);
200
201        parser.pos = 16;
202
203        // Returns None if we're at the end of the string
204        assert_eq!(parser.peek(), None);
205    }
206
207    #[test]
208    fn test_last() {
209        let mut parser = Parser::new("abcdef".to_string());
210        parser.pos = 0;
211
212        // Returns None if we're at the beginning
213        assert_eq!(parser.last(), None);
214
215        // It does not move the position
216        assert_eq!(parser.pos, 0);
217
218        parser.pos = 3;
219
220        // Returns the character if we're not at the beginning
221        assert_eq!(parser.last(), Some('c'));
222    }
223
224    #[test]
225    fn test_skip() {
226        let mut parser = Parser::new("foo#bar".to_string());
227        parser.pos = 3;
228        parser.skip();
229
230        // Increases the position by one
231        assert_eq!(parser.pos, 4);
232    }
233
234    #[test]
235    fn test_parse_tags() {
236        let mut parser = Parser::new("#hostname:frontend1,redis_instance:10.0.0.16:6379,namespace:web".to_string());
237
238        let mut tags = BTreeMap::new();
239        tags.insert("hostname".to_string(), "frontend1".to_string());
240        tags.insert("redis_instance".to_string(), "10.0.0.16:6379".to_string());
241        tags.insert("namespace".to_string(), "web".to_string());
242
243        // Increases the position by one
244        assert_eq!(parser.parse_tags(), tags);
245    }
246}