rrdcached_client/
fetch.rs1use 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(), ];
147
148 let result = FetchResponse::from_lines(input);
149 assert!(result.is_err());
150
151 let input = vec![
152 "Start: xyz\n".to_string(), ];
154 let result = FetchResponse::from_lines(input);
155 assert!(result.is_err());
156
157 let input = vec![
158 "End: xyz\n".to_string(), ];
160 let result = FetchResponse::from_lines(input);
161 assert!(result.is_err());
162
163 let input = vec![
164 "Step: xyz\n".to_string(), ];
166 let result = FetchResponse::from_lines(input);
167 assert!(result.is_err());
168
169 let input = vec![
170 "DSCount: xyz\n".to_string(), ];
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 "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 ];
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(), "1708800040: abc\n".to_string(), "1708800040: 2.0\n".to_string(), ];
228
229 let result = FetchResponse::from_lines(input);
230 assert!(result.is_err());
231 }
232}