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 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(), ];
135
136 let result = FetchResponse::from_lines(input);
137 assert!(result.is_err());
138
139 let input = vec![
140 "Start: xyz\n".to_string(), ];
142 let result = FetchResponse::from_lines(input);
143 assert!(result.is_err());
144
145 let input = vec![
146 "End: xyz\n".to_string(), ];
148 let result = FetchResponse::from_lines(input);
149 assert!(result.is_err());
150
151 let input = vec![
152 "Step: xyz\n".to_string(), ];
154 let result = FetchResponse::from_lines(input);
155 assert!(result.is_err());
156
157 let input = vec![
158 "DSCount: xyz\n".to_string(), ];
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 "1708800040: 1.0 2.0\n".to_string(),
170 ];
171
172 let expected = FetchResponse {
174 flush_version: 1,
175 start: 0, end: 0, step: 0, ds_count: 0, ds_names: Vec::new(), 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 ];
201
202 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(), "1708800040: abc\n".to_string(), "1708800040: 2.0\n".to_string(), ];
222
223 let result = FetchResponse::from_lines(input);
224 assert!(result.is_err());
225 }
226}