statsd_parser/
lib.rs

1use std::collections::BTreeMap;
2
3mod parser;
4
5pub use parser::ParseError;
6
7#[derive(Debug,PartialEq)]
8pub struct Message {
9    pub name: String,
10    pub tags: Option<BTreeMap<String, String>>,
11    pub metric: Metric
12}
13
14#[derive(Debug,PartialEq)]
15pub enum Metric {
16    Gauge(Gauge),
17    Counter(Counter),
18    Timing(Timing),
19    Histogram(Histogram),
20    Meter(Meter),
21    ServiceCheck(ServiceCheck)
22}
23
24#[derive(Debug,PartialEq)]
25pub enum Status {
26    OK,
27    WARNING,
28    CRITICAL,
29    UNKNOWN
30}
31
32#[derive(Debug,PartialEq)]
33pub struct Gauge {
34    pub value: f64,
35    pub sample_rate: Option<f64>,
36}
37
38#[derive(Debug,PartialEq)]
39pub struct Counter {
40    pub value: f64,
41    pub sample_rate: Option<f64>,
42}
43
44#[derive(Debug,PartialEq)]
45pub struct Timing {
46    pub value: f64,
47    pub sample_rate: Option<f64>,
48}
49
50#[derive(Debug,PartialEq)]
51pub struct Histogram {
52    pub value: f64,
53    pub sample_rate: Option<f64>,
54}
55
56#[derive(Debug,PartialEq)]
57pub struct Meter {
58    pub value: f64,
59    pub sample_rate: Option<f64>,
60}
61
62#[derive(Debug,PartialEq)]
63pub struct ServiceCheck {
64    pub status: Status,
65    pub timestamp: Option<f64>,
66    pub hostname: Option<String>,
67    pub message: Option<String>,
68}
69
70/// Parse a statsd string and return a metric or error message
71pub fn parse<S: Into<String>>(input: S) -> Result<Message, ParseError> {
72    let string = input.into();
73
74    if string.starts_with("_sc") {
75        parser::service_check_parser::parse(string)
76    } else {
77        parser::metric_parser::parse(string)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use {Message, Metric};
84    use std::collections::BTreeMap;
85
86    use super::*;
87
88    #[test]
89    fn test_statsd_counter() {
90        let expected = Message {
91            name: "gorets".to_string(),
92            tags: None,
93            metric: Metric::Counter(Counter {
94                value: 1.0,
95                sample_rate: None,
96            })
97        };
98
99        assert_eq!(parse("gorets:1|c"), Ok(expected));
100    }
101
102    #[test]
103    fn test_statsd_gauge() {
104        let expected = Message {
105            name: "gorets".to_string(),
106            tags: None,
107            metric: Metric::Gauge(Gauge {
108                value: 1.0,
109                sample_rate: None,
110            })
111        };
112
113        assert_eq!(parse("gorets:1|g"), Ok(expected));
114    }
115
116    #[test]
117    fn test_statsd_time() {
118        let expected = Message {
119            name: "gorets".to_string(),
120            tags: None,
121            metric: Metric::Timing(Timing {
122                value: 233.0,
123                sample_rate: None,
124            })
125        };
126
127        assert_eq!(parse("gorets:233|ms"), Ok(expected));
128    }
129
130    #[test]
131    fn test_statsd_histogram() {
132        let expected = Message {
133            name: "gorets".to_string(),
134            tags: None,
135            metric: Metric::Histogram(Histogram {
136                value: 233.0,
137                sample_rate: None,
138            })
139        };
140
141        assert_eq!(parse("gorets:233|h"), Ok(expected));
142    }
143
144    #[test]
145    fn test_statsd_meter() {
146        let expected = Message {
147            name: "gorets".to_string(),
148            tags: None,
149            metric: Metric::Meter(Meter {
150                value: 233.0,
151                sample_rate: None,
152            })
153        };
154
155        assert_eq!(parse("gorets:233|m"), Ok(expected));
156    }
157
158    #[test]
159    fn test_statsd_counter_with_sample_rate() {
160        let expected = Message {
161            name: "gorets".to_string(),
162            tags: None,
163            metric: Metric::Counter(Counter {
164                value: 1.0,
165                sample_rate: Some(0.5),
166            })
167        };
168
169        assert_eq!(parse("gorets:1|c|@0.5"), Ok(expected));
170    }
171
172    #[test]
173    fn test_statsd_counter_with_key_value_tags() {
174        let mut tags = BTreeMap::new();
175        tags.insert("foo".to_string(), "bar".to_string());
176
177        let expected = Message {
178            name: "gorets".to_string(),
179            tags: Some(tags),
180            metric: Metric::Counter(Counter {
181                value: 1.0,
182                sample_rate: None,
183            })
184        };
185
186        assert_eq!(parse("gorets:1|c|#foo:bar"), Ok(expected));
187    }
188
189    #[test]
190    fn test_statsd_counter_with_key_tags() {
191        let mut tags = BTreeMap::new();
192        tags.insert("foo".to_string(), "".to_string());
193        tags.insert("moo".to_string(), "".to_string());
194
195        let expected = Message {
196            name: "gorets".to_string(),
197            tags: Some(tags),
198            metric: Metric::Counter(Counter {
199                value: 1.0,
200                sample_rate: None,
201            })
202        };
203
204        assert_eq!(parse("gorets:1|c|#foo,moo"), Ok(expected));
205    }
206
207    #[test]
208    fn test_statsd_counter_with_sample_rate_and_tags() {
209        let mut tags = BTreeMap::new();
210        tags.insert("foo".to_string(), "bar".to_string());
211        tags.insert("moo".to_string(), "maa".to_string());
212
213        let expected = Message {
214            name: "gorets".to_string(),
215            tags: Some(tags),
216            metric: Metric::Counter(Counter {
217                value: 1.0,
218                sample_rate: Some(0.9),
219            })
220        };
221
222        assert_eq!(parse("gorets:1|c|@0.9|#foo:bar,moo:maa"), Ok(expected));
223    }
224
225    #[test]
226    fn test_statsd_utf8_boundary() {
227        let expected = Message {
228            name: "goretsβ".to_string(),
229            tags: None,
230            metric: Metric::Counter(Counter {
231                value: 1.0,
232                sample_rate: None,
233            })
234        };
235
236        assert_eq!(parse("goretsβ:1|c"), Ok(expected));
237    }
238
239    #[test]
240    fn test_statsd_empty() {
241        assert_eq!(parse(""), Err(ParseError::EmptyInput));
242    }
243
244    #[test]
245    fn test_statsd_no_name() {
246        assert_eq!(parse(":1|c"), Err(ParseError::NoName));
247    }
248
249    #[test]
250    fn test_statsd_value_not_float() {
251        assert_eq!(parse("gorets:aaa|h"), Err(ParseError::ValueNotFloat));
252    }
253
254    #[test]
255    fn test_statsd_sample_rate_not_float() {
256        assert_eq!(parse("gorets:1|c|@aaa"), Err(ParseError::SampleRateNotFloat));
257    }
258
259    #[test]
260    fn test_statsd_metric_type_unknown() {
261        assert_eq!(parse("gorets:1|wrong"), Err(ParseError::UnknownMetricType));
262    }
263}