rrdcached_client/
parsers.rs

1use nom::{
2    branch::alt,
3    bytes::complete::{tag, take_until1},
4    character::complete::{i64 as parse_i64, newline, not_line_ending, space1, u64 as parse_u64},
5    combinator::value,
6    multi::separated_list1,
7    number::complete::double,
8    sequence::terminated,
9    IResult, Parser,
10};
11
12use crate::errors::RRDCachedClientError;
13
14pub fn parse_response_line(input: &str) -> Result<(i64, &str), RRDCachedClientError> {
15    let parse_result: IResult<&str, (i64, &str)> = (
16        terminated(parse_i64, space1),
17        terminated(not_line_ending, newline),
18    )
19        .parse(input);
20
21    match parse_result {
22        Ok((_, (code, message))) => Ok((code, message)),
23        Err(_) => Err(RRDCachedClientError::Parsing("parse error".to_string())),
24    }
25}
26
27pub fn parse_queue_line(input: &str) -> Result<(&str, usize), RRDCachedClientError> {
28    let parse_result: IResult<&str, (u64, &str)> = (
29        terminated(parse_u64, space1),
30        terminated(not_line_ending, newline),
31    )
32        .parse(input);
33
34    match parse_result {
35        Ok((_, (code, message))) => Ok((message, code as usize)),
36        Err(_) => Err(RRDCachedClientError::Parsing("parse error".to_string())),
37    }
38}
39
40pub fn parse_stats_line(input: &str) -> Result<(&str, i64), RRDCachedClientError> {
41    // name, : , at least one whitespace, number, newline
42    let parse_result: IResult<&str, (&str, &str, &str, i64)> = (
43        take_until1(":"),
44        tag(":"),
45        space1,
46        terminated(parse_i64, newline),
47    )
48        .parse(input);
49
50    match parse_result {
51        Ok((_, (name, _, _, value))) => Ok((name, value)),
52        Err(_) => Err(RRDCachedClientError::Parsing("parse error".to_string())),
53    }
54}
55
56pub fn parse_timestamp(input: &str) -> Result<usize, RRDCachedClientError> {
57    let parse_result: IResult<&str, u64> = parse_u64(input);
58    match parse_result {
59        Ok((_, timestamp)) => Ok(timestamp as usize),
60        Err(_) => Err(RRDCachedClientError::Parsing("parse error".to_string())),
61    }
62}
63
64pub fn parse_fetch_header_line(input: &str) -> Result<(String, String), RRDCachedClientError> {
65    let parse_result: IResult<&str, (&str, &str, &str, &str)> = (
66        take_until1(":"),
67        tag(":"),
68        space1,
69        terminated(not_line_ending, newline),
70    )
71        .parse(input);
72
73    match parse_result {
74        Ok((_, (name, _tag, _space, value))) => Ok((name.to_string(), value.to_string())),
75        Err(_) => Err(RRDCachedClientError::Parsing("parse error".to_string())),
76    }
77}
78
79pub fn parse_fetch_line(input: &str) -> IResult<&str, (usize, Vec<f64>)> {
80    (
81        parse_u64,
82        tag(":"),
83        space1,
84        separated_list1(space1, alt((double, value(f64::NAN, tag("-nan"))))),
85        newline,
86    )
87        .parse(input)
88        .map(|(i, (timestamp, _, _, values, _))| (i, (timestamp as usize, values)))
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_parse_response_line() {
97        let input = "1234  hello world\n";
98        let result = parse_response_line(input);
99        assert_eq!(result.unwrap(), (1234, "hello world"));
100
101        let input = "1234  hello world";
102        let result = parse_response_line(input);
103        assert!(result.is_err());
104
105        let input = "0 PONG\n";
106        let result = parse_response_line(input);
107        assert_eq!(result.unwrap(), (0, "PONG"));
108
109        let input = "-20 errors, a lot of errors\n";
110        let result = parse_response_line(input);
111        assert_eq!(result.unwrap(), (-20, "errors, a lot of errors"));
112
113        let input = "";
114        let result = parse_response_line(input);
115        assert!(result.is_err());
116
117        let input = "1234";
118        let result = parse_response_line(input);
119        assert!(result.is_err());
120    }
121
122    #[test]
123    fn test_parse_queue_line() {
124        let input = "12  test.rrd\n";
125        let result = parse_queue_line(input);
126        assert_eq!(result.unwrap(), ("test.rrd", 12));
127
128        let input = "-0  test/test.rrd";
129        let result = parse_queue_line(input);
130        assert!(result.is_err());
131    }
132
133    #[test]
134    fn test_parse_stats_line() {
135        let input = "uptime: 1234\n";
136        let result = parse_stats_line(input);
137        assert_eq!(result.unwrap(), ("uptime", 1234));
138
139        let input = "uptime: 1234";
140        let result = parse_stats_line(input);
141        assert!(result.is_err());
142
143        let input = "upti:me:\n 1234\n";
144        let result = parse_stats_line(input);
145        assert!(result.is_err());
146
147        let input = " upti:me: 1234\n";
148        let result = parse_stats_line(input);
149        assert!(result.is_err());
150    }
151
152    #[test]
153    fn test_parse_timestamp() {
154        let input = "1234";
155        let result = parse_timestamp(input);
156        assert_eq!(result.unwrap(), 1234);
157
158        let input = "abcd\n";
159        let result = parse_timestamp(input);
160        assert!(result.is_err());
161    }
162    /*
163    FlushVersion: 1
164    Start: 1708800030
165    Step: 10
166    DSCount: 2
167    DSName: ds1 ds2
168    1708800040: nan nan
169    1708800050: nan nan
170    1708800060: nan nan
171    1708800070: nan nan
172    1708800080: nan nan
173    */
174    #[test]
175    fn test_parse_fetch_header_line() {
176        let input = "FlushVersion: 1\n";
177        let result = parse_fetch_header_line(input);
178        assert_eq!(
179            result.unwrap(),
180            ("FlushVersion".to_string(), "1".to_string())
181        );
182
183        let input = "FlushVersion: 1";
184        let result = parse_fetch_header_line(input);
185        assert!(result.is_err());
186
187        let input = "DSName: ds1 ds2\n";
188        let result = parse_fetch_header_line(input);
189        assert_eq!(
190            result.unwrap(),
191            ("DSName".to_string(), "ds1 ds2".to_string())
192        );
193
194        let input = "0 PONG\n";
195        let result = parse_fetch_header_line(input);
196        assert!(result.is_err());
197    }
198
199    #[test]
200    fn test_parse_fetch_line() {
201        let input = "1708800040: nan nan\n";
202        let result = parse_fetch_line(input).unwrap().1;
203        assert_eq!(result.0, 1708800040);
204        assert_eq!(result.1.len(), 2);
205        assert!(result.1.iter().all(|f| f.is_nan()));
206
207        let input = "1708800040: 4.2 100000\n";
208        let result = parse_fetch_line(input);
209        assert_eq!(result.unwrap().1, (1708800040, vec![4.2, 100000.0]));
210
211        let input = "1708800040: nan nan";
212        let result = parse_fetch_line(input);
213        assert!(result.is_err());
214
215        let input = "End: 1708886440";
216        let result = parse_fetch_line(input);
217        assert!(result.is_err());
218    }
219
220    #[test]
221    fn test_minus_nan_handling() {
222        // Some systems somehow return -nan sometimes
223        let input = "1708800040: -nan -nan\n";
224        let result = parse_fetch_line(input).unwrap().1;
225        assert_eq!(result.0, 1708800040);
226        assert_eq!(result.1.len(), 2);
227        assert!(result.1.iter().all(|f| f.is_nan()));
228    }
229}