1use crate::{
2 consolidation_function::ConsolidationFunction,
3 errors::RRDCachedClientError,
4 sanitisation::{check_data_source_name, check_rrd_path},
5};
6
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum CreateDataSourceType {
9 Gauge,
10 Counter,
11 DCounter,
12 Derive,
13 DDerive,
14 Absolute,
15}
16
17impl CreateDataSourceType {
18 pub fn to_str(self) -> &'static str {
19 match self {
20 CreateDataSourceType::Gauge => "GAUGE",
21 CreateDataSourceType::Counter => "COUNTER",
22 CreateDataSourceType::DCounter => "DCOUNTER",
23 CreateDataSourceType::Derive => "DERIVE",
24 CreateDataSourceType::DDerive => "DDERIVE",
25 CreateDataSourceType::Absolute => "ABSOLUTE",
26 }
27 }
28}
29
30#[derive(Debug)]
32pub struct CreateDataSource {
33 pub name: String,
37
38 pub minimum: Option<f64>,
40
41 pub maximum: Option<f64>,
43
44 pub heartbeat: i64,
47
48 pub serie_type: CreateDataSourceType,
50}
51
52impl CreateDataSource {
53 pub fn validate(&self) -> Result<(), RRDCachedClientError> {
55 if self.heartbeat <= 0 {
56 return Err(RRDCachedClientError::InvalidCreateDataSerie(
57 "heartbeat must be greater than 0".to_string(),
58 ));
59 }
60 if let Some(minimum) = self.minimum {
61 if let Some(maximum) = self.maximum {
62 if maximum <= minimum {
63 return Err(RRDCachedClientError::InvalidCreateDataSerie(
64 "maximum must be greater than to minimum".to_string(),
65 ));
66 }
67 }
68 }
69
70 check_data_source_name(&self.name)?;
71
72 Ok(())
73 }
74
75 pub fn to_str(&self) -> String {
77 format!(
78 "DS:{}:{}:{}:{}:{}",
79 self.name,
80 self.serie_type.to_str(),
81 self.heartbeat,
82 match self.minimum {
83 Some(minimum) => minimum.to_string(),
84 None => "U".to_string(),
85 },
86 match self.maximum {
87 Some(maximum) => maximum.to_string(),
88 None => "U".to_string(),
89 }
90 )
91 }
92}
93
94#[derive(Debug)]
96pub struct CreateRoundRobinArchive {
97 pub consolidation_function: ConsolidationFunction,
99
100 pub xfiles_factor: f64,
104
105 pub steps: i64,
107
108 pub rows: i64,
110}
111
112impl CreateRoundRobinArchive {
113 pub fn validate(&self) -> Result<(), RRDCachedClientError> {
115 if self.xfiles_factor < 0.0 || self.xfiles_factor > 1.0 {
116 return Err(RRDCachedClientError::InvalidCreateDataSerie(
117 "xfiles_factor must be between 0 and 1".to_string(),
118 ));
119 }
120 if self.steps <= 0 {
121 return Err(RRDCachedClientError::InvalidCreateDataSerie(
122 "steps must be greater than 0".to_string(),
123 ));
124 }
125 if self.rows <= 0 {
126 return Err(RRDCachedClientError::InvalidCreateDataSerie(
127 "rows must be greater than 0".to_string(),
128 ));
129 }
130 Ok(())
131 }
132
133 pub fn to_str(&self) -> String {
135 format!(
136 "RRA:{}:{}:{}:{}",
137 self.consolidation_function.to_str(),
138 self.xfiles_factor,
139 self.steps,
140 self.rows
141 )
142 }
143}
144
145#[derive(Debug)]
147pub struct CreateArguments {
148 pub path: String,
153
154 pub data_sources: Vec<CreateDataSource>,
157
158 pub round_robin_archives: Vec<CreateRoundRobinArchive>,
161
162 pub start_timestamp: u64,
164
165 pub step_seconds: u64,
167}
168
169impl CreateArguments {
170 pub fn validate(&self) -> Result<(), RRDCachedClientError> {
172 if self.data_sources.is_empty() {
173 return Err(RRDCachedClientError::InvalidCreateDataSerie(
174 "at least one data serie is required".to_string(),
175 ));
176 }
177 if self.round_robin_archives.is_empty() {
178 return Err(RRDCachedClientError::InvalidCreateDataSerie(
179 "at least one round robin archive is required".to_string(),
180 ));
181 }
182 for data_serie in &self.data_sources {
183 data_serie.validate()?;
184 }
185 for rr_archive in &self.round_robin_archives {
186 rr_archive.validate()?;
187 }
188 check_rrd_path(&self.path)?;
189 Ok(())
190 }
191
192 pub fn to_str(&self) -> String {
194 let mut result = format!(
195 "{}.rrd -s {} -b {}",
196 self.path, self.step_seconds, self.start_timestamp
197 );
198 for data_serie in &self.data_sources {
199 result.push(' ');
200 result.push_str(&data_serie.to_str());
201 }
202 for rr_archive in &self.round_robin_archives {
203 result.push(' ');
204 result.push_str(&rr_archive.to_str());
205 }
206 result
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
216 fn test_create_data_source_type_to_str() {
217 assert_eq!(CreateDataSourceType::Gauge.to_str(), "GAUGE");
218 assert_eq!(CreateDataSourceType::Counter.to_str(), "COUNTER");
219 assert_eq!(CreateDataSourceType::DCounter.to_str(), "DCOUNTER");
220 assert_eq!(CreateDataSourceType::Derive.to_str(), "DERIVE");
221 assert_eq!(CreateDataSourceType::DDerive.to_str(), "DDERIVE");
222 assert_eq!(CreateDataSourceType::Absolute.to_str(), "ABSOLUTE");
223 }
224
225 #[test]
227 fn test_create_data_source_validate() {
228 let valid_ds = CreateDataSource {
229 name: "valid_name_1".to_string(),
230 minimum: Some(0.0),
231 maximum: Some(100.0),
232 heartbeat: 300,
233 serie_type: CreateDataSourceType::Gauge,
234 };
235 assert!(valid_ds.validate().is_ok());
236
237 let invalid_ds_name = CreateDataSource {
238 name: "Invalid Name!".to_string(), ..valid_ds
240 };
241 assert!(invalid_ds_name.validate().is_err());
242
243 let invalid_ds_heartbeat = CreateDataSource {
244 heartbeat: -1, name: "valid_name_2".to_string(),
246 ..valid_ds
247 };
248 assert!(invalid_ds_heartbeat.validate().is_err());
249
250 let invalid_ds_min_max = CreateDataSource {
251 minimum: Some(100.0),
252 maximum: Some(50.0), name: "valid_name_3".to_string(),
254 ..valid_ds
255 };
256 assert!(invalid_ds_min_max.validate().is_err());
257
258 let invalid_ds_max = CreateDataSource {
260 minimum: Some(100.0),
261 maximum: Some(0.0),
262 name: "valid_name_5".to_string(),
263 ..valid_ds
264 };
265 assert!(invalid_ds_max.validate().is_err());
266
267 let valid_ds_max = CreateDataSource {
269 maximum: Some(100.0),
270 name: "valid_name_6".to_string(),
271 ..valid_ds
272 };
273 assert!(valid_ds_max.validate().is_ok());
274
275 let valid_ds_min = CreateDataSource {
277 minimum: Some(-100.0),
278 name: "valid_name_7".to_string(),
279 ..valid_ds
280 };
281 assert!(valid_ds_min.validate().is_ok());
282 }
283
284 #[test]
286 fn test_create_data_source_to_str() {
287 let ds = CreateDataSource {
288 name: "test_ds".to_string(),
289 minimum: Some(10.0),
290 maximum: Some(100.0),
291 heartbeat: 600,
292 serie_type: CreateDataSourceType::Gauge,
293 };
294 assert_eq!(ds.to_str(), "DS:test_ds:GAUGE:600:10:100");
295
296 let ds = CreateDataSource {
297 name: "test_ds".to_string(),
298 minimum: None,
299 maximum: None,
300 heartbeat: 600,
301 serie_type: CreateDataSourceType::Gauge,
302 };
303 assert_eq!(ds.to_str(), "DS:test_ds:GAUGE:600:U:U");
304 }
305
306 #[test]
308 fn test_create_round_robin_archive_validate() {
309 let valid_rra = CreateRoundRobinArchive {
310 consolidation_function: ConsolidationFunction::Average,
311 xfiles_factor: 0.5,
312 steps: 1,
313 rows: 100,
314 };
315 assert!(valid_rra.validate().is_ok());
316
317 let invalid_rra_xff = CreateRoundRobinArchive {
318 xfiles_factor: -0.1, ..valid_rra
320 };
321 assert!(invalid_rra_xff.validate().is_err());
322
323 let invalid_rra_steps = CreateRoundRobinArchive {
324 steps: 0, ..valid_rra
326 };
327 assert!(invalid_rra_steps.validate().is_err());
328
329 let invalid_rra_rows = CreateRoundRobinArchive {
330 rows: -100, ..valid_rra
332 };
333 assert!(invalid_rra_rows.validate().is_err());
334 }
335
336 #[test]
338 fn test_create_round_robin_archive_to_str() {
339 let rra = CreateRoundRobinArchive {
340 consolidation_function: ConsolidationFunction::Max,
341 xfiles_factor: 0.5,
342 steps: 1,
343 rows: 100,
344 };
345 assert_eq!(rra.to_str(), "RRA:MAX:0.5:1:100");
346 }
347
348 #[test]
350 fn test_create_arguments_validate() {
351 let valid_args = CreateArguments {
352 path: "valid_path".to_string(),
353 data_sources: vec![CreateDataSource {
354 name: "ds1".to_string(),
355 minimum: Some(0.0),
356 maximum: Some(100.0),
357 heartbeat: 300,
358 serie_type: CreateDataSourceType::Gauge,
359 }],
360 round_robin_archives: vec![CreateRoundRobinArchive {
361 consolidation_function: ConsolidationFunction::Average,
362 xfiles_factor: 0.5,
363 steps: 1,
364 rows: 100,
365 }],
366 start_timestamp: 1609459200,
367 step_seconds: 300,
368 };
369 assert!(valid_args.validate().is_ok());
370
371 let invalid_args_no_ds = CreateArguments {
372 data_sources: vec![],
373 path: "valid_path".to_string(),
374 ..valid_args
375 };
376 assert!(invalid_args_no_ds.validate().is_err());
377
378 let invalid_args_no_rra = CreateArguments {
379 round_robin_archives: vec![],
380 path: "valid_path".to_string(),
381 ..valid_args
382 };
383 assert!(invalid_args_no_rra.validate().is_err());
384 }
385
386 #[test]
388 fn test_create_arguments_to_str() {
389 let args = CreateArguments {
390 path: "test_path".to_string(),
391 data_sources: vec![CreateDataSource {
392 name: "ds1".to_string(),
393 minimum: Some(0.0),
394 maximum: Some(100.0),
395 heartbeat: 300,
396 serie_type: CreateDataSourceType::Gauge,
397 }],
398 round_robin_archives: vec![CreateRoundRobinArchive {
399 consolidation_function: ConsolidationFunction::Average,
400 xfiles_factor: 0.5,
401 steps: 1,
402 rows: 100,
403 }],
404 start_timestamp: 1609459200,
405 step_seconds: 300,
406 };
407 let expected_str =
408 "test_path.rrd -s 300 -b 1609459200 DS:ds1:GAUGE:300:0:100 RRA:AVERAGE:0.5:1:100";
409 assert_eq!(args.to_str(), expected_str);
410 }
411}