rrdcached_client/
fetch.rs

1use crate::{
2    errors::RRDCachedClientError,
3    parsers::{parse_fetch_header_line, parse_fetch_line},
4};
5
6#[derive(Debug, PartialEq)]
7pub struct FetchResponse {
8    pub flush_version: u32,
9    pub start: usize,
10    pub end: usize,
11    pub step: usize,
12    pub ds_count: usize,
13    pub ds_names: Vec<String>,
14    pub data: Vec<(usize, Vec<f64>)>,
15}
16
17impl FetchResponse {
18    pub fn from_lines(lines: Vec<String>) -> Result<FetchResponse, RRDCachedClientError> {
19        let mut flush_version = None;
20        let mut start = None;
21        let mut end = None;
22        let mut step = None;
23        let mut ds_count = None;
24        let mut ds_names = None;
25        let mut data: Vec<(usize, Vec<f64>)> = Vec::new();
26
27        let mut index_data_start = None;
28        for (index, line) in lines.iter().enumerate() {
29            let (key, value) = parse_fetch_header_line(line)?;
30            match key.as_str() {
31                "FlushVersion" => {
32                    flush_version = Some(value.parse().map_err(|_| {
33                        RRDCachedClientError::Parsing("Unable to parse flush version".to_string())
34                    })?);
35                }
36                "Start" => {
37                    start = Some(value.parse().map_err(|_| {
38                        RRDCachedClientError::Parsing("Unable to parse start".to_string())
39                    })?);
40                }
41                "End" => {
42                    end = Some(value.parse().map_err(|_| {
43                        RRDCachedClientError::Parsing("Unable to parse end".to_string())
44                    })?);
45                }
46                "Step" => {
47                    step = Some(value.parse().map_err(|_| {
48                        RRDCachedClientError::Parsing("Unable to parse step".to_string())
49                    })?);
50                }
51                "DSCount" => {
52                    ds_count = Some(value.parse().map_err(|_| {
53                        RRDCachedClientError::Parsing("Unable to parse ds count".to_string())
54                    })?);
55                }
56                "DSName" => {
57                    ds_names = Some(value.split_whitespace().map(|s| s.to_string()).collect());
58                }
59                _ => match parse_fetch_line(line) {
60                    Ok((_, (timestamp, values))) => {
61                        data.push((timestamp, values));
62                        index_data_start = Some(index);
63                        break;
64                    }
65                    Err(_) => {
66                        return Err(RRDCachedClientError::InvalidFetchHeaderLine(
67                            line.to_string(),
68                        ));
69                    }
70                },
71            }
72        }
73
74        if let Some(index_data_start) = index_data_start {
75            for line in lines.iter().skip(index_data_start + 1) {
76                match parse_fetch_line(line) {
77                    Ok((_, (timestamp, values))) => {
78                        data.push((timestamp, values));
79                    }
80                    Err(_) => {
81                        return Err(RRDCachedClientError::InvalidFetch(line.to_string()));
82                    }
83                }
84            }
85        }
86
87        Ok(FetchResponse {
88            flush_version: flush_version.unwrap_or(0),
89            start: start.unwrap_or(0),
90            end: end.unwrap_or(0),
91            step: step.unwrap_or(0),
92            ds_count: ds_count.unwrap_or(0),
93            ds_names: ds_names.unwrap_or(Vec::new()),
94            data,
95        })
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_successful_parse() {
105        let input = vec![
106            "FlushVersion: 1\n".to_string(),
107            "Start: 1708800030\n".to_string(),
108            "End: 1708886440\n".to_string(),
109            "Step: 10\n".to_string(),
110            "DSCount: 2\n".to_string(),
111            "DSName: ds1 ds2\n".to_string(),
112            "1708800040: 1 2\n".to_string(),
113            "1708800050: 3 3\n".to_string(),
114        ];
115
116        let expected = FetchResponse {
117            flush_version: 1,
118            start: 1708800030,
119            end: 1708886440,
120            step: 10,
121            ds_count: 2,
122            ds_names: vec!["ds1".to_string(), "ds2".to_string()],
123            data: vec![(1708800040, vec![1.0, 2.0]), (1708800050, vec![3.0, 3.0])],
124        };
125
126        let result = FetchResponse::from_lines(input).unwrap();
127        assert_eq!(result, expected);
128    }
129
130    #[test]
131    fn test_parse_error_numbers() {
132        let input = vec![
133            "FlushVersion: xyz\n".to_string(), // Incorrect format
134        ];
135
136        let result = FetchResponse::from_lines(input);
137        assert!(result.is_err());
138
139        let input = vec![
140            "Start: xyz\n".to_string(), // Incorrect format
141        ];
142        let result = FetchResponse::from_lines(input);
143        assert!(result.is_err());
144
145        let input = vec![
146            "End: xyz\n".to_string(), // Incorrect format
147        ];
148        let result = FetchResponse::from_lines(input);
149        assert!(result.is_err());
150
151        let input = vec![
152            "Step: xyz\n".to_string(), // Incorrect format
153        ];
154        let result = FetchResponse::from_lines(input);
155        assert!(result.is_err());
156
157        let input = vec![
158            "DSCount: xyz\n".to_string(), // Incorrect format
159        ];
160        let result = FetchResponse::from_lines(input);
161        assert!(result.is_err());
162    }
163
164    #[test]
165    fn test_incomplete_data() {
166        let input = vec![
167            "FlushVersion: 1\n".to_string(),
168            // Missing "Start", "End", "Step", "DSCount", "DSName"
169            "1708800040: 1.0 2.0\n".to_string(),
170        ];
171
172        // Expect defaults for missing fields
173        let expected = FetchResponse {
174            flush_version: 1,
175            start: 0,             // Default due to missing
176            end: 0,               // Default due to missing
177            step: 0,              // Default due to missing
178            ds_count: 0,          // Default due to missing
179            ds_names: Vec::new(), // Default due to missing
180            data: vec![(1708800040, vec![1.0, 2.0])],
181        };
182
183        let result = FetchResponse::from_lines(input).unwrap();
184        assert_eq!(result, expected);
185    }
186
187    #[test]
188    fn test_empty_input() {
189        let input: Vec<String> = vec![];
190
191        let _ = FetchResponse::from_lines(input).unwrap();
192    }
193
194    #[test]
195    fn test_no_data_lines() {
196        let input = vec![
197            "FlushVersion: 1\n".to_string(),
198            "DSName: ds1 ds2\n".to_string(),
199            // No data lines
200        ];
201
202        // Expected behavior could vary, this is just an example
203        let _ = FetchResponse::from_lines(input).unwrap();
204    }
205
206    #[test]
207    fn test_valid_header_invalid_data() {
208        let input = vec![
209            "FlushVersion: 1\n".to_string(),
210            "1708800040: abc def\n".to_string(),
211        ];
212
213        let result = FetchResponse::from_lines(input);
214        assert!(result.is_err());
215
216        let input = vec![
217            "FlushVersion: 1\n".to_string(),
218            "1708800040: 1.0\n".to_string(), // Missing second value
219            "1708800040: abc\n".to_string(), // Missing second value
220            "1708800040: 2.0\n".to_string(), // Missing second value
221        ];
222
223        let result = FetchResponse::from_lines(input);
224        assert!(result.is_err());
225    }
226}