Skip to main content

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        let flush_version = flush_version
88            .ok_or_else(|| RRDCachedClientError::Parsing("missing fetch flush version".to_string()))?;
89        let start = start
90            .ok_or_else(|| RRDCachedClientError::Parsing("missing fetch start".to_string()))?;
91        let end = end.ok_or_else(|| RRDCachedClientError::Parsing("missing fetch end".to_string()))?;
92        let step =
93            step.ok_or_else(|| RRDCachedClientError::Parsing("missing fetch step".to_string()))?;
94        let ds_count = ds_count
95            .ok_or_else(|| RRDCachedClientError::Parsing("missing fetch ds count".to_string()))?;
96        let ds_names = ds_names
97            .ok_or_else(|| RRDCachedClientError::Parsing("missing fetch ds names".to_string()))?;
98
99        Ok(FetchResponse {
100            flush_version,
101            start,
102            end,
103            step,
104            ds_count,
105            ds_names,
106            data,
107        })
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_successful_parse() {
117        let input = vec![
118            "FlushVersion: 1\n".to_string(),
119            "Start: 1708800030\n".to_string(),
120            "End: 1708886440\n".to_string(),
121            "Step: 10\n".to_string(),
122            "DSCount: 2\n".to_string(),
123            "DSName: ds1 ds2\n".to_string(),
124            "1708800040: 1 2\n".to_string(),
125            "1708800050: 3 3\n".to_string(),
126        ];
127
128        let expected = FetchResponse {
129            flush_version: 1,
130            start: 1708800030,
131            end: 1708886440,
132            step: 10,
133            ds_count: 2,
134            ds_names: vec!["ds1".to_string(), "ds2".to_string()],
135            data: vec![(1708800040, vec![1.0, 2.0]), (1708800050, vec![3.0, 3.0])],
136        };
137
138        let result = FetchResponse::from_lines(input).unwrap();
139        assert_eq!(result, expected);
140    }
141
142    #[test]
143    fn test_parse_error_numbers() {
144        let input = vec![
145            "FlushVersion: xyz\n".to_string(), // Incorrect format
146        ];
147
148        let result = FetchResponse::from_lines(input);
149        assert!(result.is_err());
150
151        let input = vec![
152            "Start: 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            "End: xyz\n".to_string(), // Incorrect format
159        ];
160        let result = FetchResponse::from_lines(input);
161        assert!(result.is_err());
162
163        let input = vec![
164            "Step: xyz\n".to_string(), // Incorrect format
165        ];
166        let result = FetchResponse::from_lines(input);
167        assert!(result.is_err());
168
169        let input = vec![
170            "DSCount: xyz\n".to_string(), // Incorrect format
171        ];
172        let result = FetchResponse::from_lines(input);
173        assert!(result.is_err());
174    }
175
176    #[test]
177    fn test_incomplete_data() {
178        let input = vec![
179            "FlushVersion: 1\n".to_string(),
180            // Missing "Start", "End", "Step", "DSCount", "DSName"
181            "1708800040: 1.0 2.0\n".to_string(),
182        ];
183
184        let result = FetchResponse::from_lines(input);
185        assert!(result.is_err());
186    }
187
188    #[test]
189    fn test_empty_input() {
190        let input: Vec<String> = vec![];
191
192        let result = FetchResponse::from_lines(input);
193        assert!(result.is_err());
194    }
195
196    #[test]
197    fn test_no_data_lines() {
198        let input = vec![
199            "FlushVersion: 1\n".to_string(),
200            "Start: 1708800030\n".to_string(),
201            "End: 1708886440\n".to_string(),
202            "Step: 10\n".to_string(),
203            "DSCount: 2\n".to_string(),
204            "DSName: ds1 ds2\n".to_string(),
205            // No data lines
206        ];
207
208        let result = FetchResponse::from_lines(input).unwrap();
209        assert!(result.data.is_empty());
210    }
211
212    #[test]
213    fn test_valid_header_invalid_data() {
214        let input = vec![
215            "FlushVersion: 1\n".to_string(),
216            "1708800040: abc def\n".to_string(),
217        ];
218
219        let result = FetchResponse::from_lines(input);
220        assert!(result.is_err());
221
222        let input = vec![
223            "FlushVersion: 1\n".to_string(),
224            "1708800040: 1.0\n".to_string(), // Missing second value
225            "1708800040: abc\n".to_string(), // Missing second value
226            "1708800040: 2.0\n".to_string(), // Missing second value
227        ];
228
229        let result = FetchResponse::from_lines(input);
230        assert!(result.is_err());
231    }
232}