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
70pub 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}