use std::fmt;
use std::fmt::{Debug, Display};
use validator::{Validate, ValidationError};
pub mod collector;
fn validate_type_id(v: &str) -> Result<(), ValidationError> {
if v.is_empty() {
return Err(ValidationError::new("Empty type_id field"));
};
if v.chars().filter(|x| x == &'.').count() != 1
|| v.chars().next() == Some('.')
|| v.chars().last() == Some('.')
{
return Err(ValidationError::new(
"Requires one single dot in the middle of type_id field",
));
}
if v.matches(&[' ', '\t', '\n', '\r', '\\', '\'', '"', ','])
.count()
!= 0
{
return Err(ValidationError::new("Illegal character"));
}
Ok(())
}
fn validate_id(v: &str) -> Result<(), ValidationError> {
if v.is_empty() {
return Err(ValidationError::new("Empty id field"));
};
if v.matches(&['.', ' ', '\t', '\n', '\r', '\\', '\'', '"', ','])
.count()
!= 0
{
return Err(ValidationError::new("Illegal character"));
}
Ok(())
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum Instruction {
CHART,
DIMENSION,
BEGIN,
SET,
END,
FLUSH,
DISABLE,
VARIABLE,
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum Algorithm {
absolute,
incremental,
percentage_of_absolute_row, percentage_of_incremental_row, }
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let replaced = format!("{:?}", self).replace("_", "-");
write!(f, "{}", replaced)
}
}
#[cfg(test)]
mod algorithm_tests {
use super::Algorithm;
#[test]
fn algorithm_kebab_display_output() {
let a = Algorithm::percentage_of_absolute_row;
assert_eq!(a.to_string(), "percentage-of-absolute-row");
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum DimensionOption {
obsolete,
hidden,
}
#[derive(Debug, Default, Clone, Validate)]
pub struct Dimension<'a> {
#[validate(custom = "validate_id")]
pub id: &'a str,
pub name: &'a str,
pub algorithm: Option<Algorithm>,
pub multiplier: Option<i32>,
pub divisor: Option<i32>,
pub options: Vec<DimensionOption>,
}
impl<'a> fmt::Display for Dimension<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let all_fields = format!(
"{:?} {:?} {:?} {:?} {:?} {:?} {:?}",
Instruction::DIMENSION,
self.id,
self.name,
some_to_textfield(&self.algorithm),
some_to_textfield(&self.multiplier),
some_to_textfield(&self.divisor),
options_to_textfield(&self.options),
);
write!(f, "{}", all_fields.trim_end_matches(" \"\""))
}
}
#[cfg(test)]
mod dimension_tests {
use super::Algorithm;
use super::Dimension;
use super::DimensionOption;
use pretty_assertions::assert_eq;
use validator::Validate;
#[test]
fn dimension_display_output() {
let d = Dimension {
id: "test_id",
name: "test_name",
..Dimension::default()
};
assert_eq!(d.to_string(), r#"DIMENSION "test_id" "test_name""#);
}
#[test]
fn dimension_validate_id() {
let d = Dimension {
id: "contains_dot.",
..Dimension::default()
};
assert!(d.validate().is_err());
let d = Dimension {
id: "contains space",
..Dimension::default()
};
assert!(d.validate().is_err());
let d = Dimension {
id: r#"contains"quote"#,
..Dimension::default()
};
assert!(d.validate().is_err());
let d = Dimension {
id: r#"contains\backslash"#,
..Dimension::default()
};
assert!(d.validate().is_err());
let d = Dimension {
id: "legitim",
..Dimension::default()
};
d.validate().unwrap()
}
#[test]
fn dimension_display_output_missing_name() {
let d = Dimension {
id: "test_id",
..Dimension::default()
};
assert_eq!(d.to_string(), r#"DIMENSION "test_id""#);
}
#[test]
fn dimension_display_output_with_algorithm() {
let d = Dimension {
id: "test_id",
name: "test_name",
algorithm: Some(Algorithm::percentage_of_absolute_row),
..Dimension::default()
};
assert_eq!(
d.to_string(),
r#"DIMENSION "test_id" "test_name" "percentage-of-absolute-row""#
);
}
#[test]
fn dimension_display_output_with_options() {
let d = Dimension {
id: "test_label",
options: vec![DimensionOption::obsolete, DimensionOption::hidden],
..Dimension::default()
};
assert_eq!(
d.to_string(),
r#"DIMENSION "test_label" "" "" "" "" "obsolete hidden""#
);
}
#[test]
fn dimension_display_output_with_multiplier() {
let d = Dimension {
id: "test_id",
name: "test_name",
algorithm: Some(Algorithm::absolute),
multiplier: Some(42),
..Dimension::default()
};
assert_eq!(
d.to_string(),
r#"DIMENSION "test_id" "test_name" "absolute" "42""#
);
}
#[test]
fn dimension_display_output_empty_inner_fields() {
let d = Dimension {
id: "test_string",
divisor: Some(42),
..Dimension::default()
};
assert_eq!(d.to_string(), r#"DIMENSION "test_string" "" "" "" "42""#);
}
#[test]
fn dimension_clone() {
let d = Dimension {
id: "test_id",
algorithm: Some(Algorithm::absolute),
options: vec![DimensionOption::obsolete],
..Dimension::default()
};
let clone = d.clone();
assert_eq!(
clone.to_string(),
r#"DIMENSION "test_id" "" "absolute" "" "" "obsolete""#
);
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum ChartType {
line,
area,
stacked,
}
impl fmt::Display for ChartType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum ChartOption {
obsolete,
detail,
store_first,
hidden,
}
#[derive(Debug, Default, Clone, Validate)]
pub struct Chart<'a> {
#[validate(custom = "validate_type_id")]
pub type_id: &'a str,
pub name: &'a str,
pub title: &'a str,
pub units: &'a str,
pub familiy: &'a str,
pub context: &'a str,
pub charttype: Option<ChartType>,
pub priority: Option<u64>,
pub update_every: Option<u64>,
pub options: Vec<ChartOption>,
pub plugin: &'a str,
pub module: &'a str,
}
impl<'a> fmt::Display for Chart<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let all_fields = format!(
"{:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}",
Instruction::CHART,
self.type_id,
self.name,
self.title,
self.units,
self.familiy,
self.context,
some_to_textfield(&self.charttype),
some_to_textfield(&self.priority),
some_to_textfield(&self.update_every),
options_to_textfield(&self.options),
self.plugin,
self.module
);
write!(
f,
"{}",
all_fields
.trim_end_matches(" \"\"")
.replace("\" \"", "\" \"")
.trim()
)
}
}
#[cfg(test)]
mod chart_tests {
use super::Chart;
use super::ChartOption;
use super::ChartType;
use pretty_assertions::assert_eq;
use validator::Validate;
#[test]
fn minimal_chart() {
let c = Chart {
type_id: "test_type.id",
name: "test_name",
title: "caption_text",
units: "test_units",
..Chart::default()
};
assert_eq!(
c.to_string(),
r#"CHART "test_type.id" "test_name" "caption_text" "test_units""#
);
}
#[test]
fn chart_manadatory_field_output() {
let c = Chart {
type_id: "test_type.id",
..Chart::default()
};
assert_eq!(c.to_string(), r#"CHART "test_type.id" "" "" """#);
}
#[test]
fn chart_validate_test_id() {
let c = Chart {
type_id: "test_type.id",
..Chart::default()
};
assert!(c.validate().is_ok());
let c = Chart {
type_id: "double.dot.id",
..Chart::default()
};
assert!(c.validate().is_err());
let c = Chart {
type_id: ".start_dot",
..Chart::default()
};
assert!(c.validate().is_err());
let c = Chart {
type_id: "end_dot.",
..Chart::default()
};
assert!(c.validate().is_err());
let c = Chart {
type_id: "nodot",
..Chart::default()
};
assert!(c.validate().is_err());
}
#[test]
fn chart_defaults() {
let c = Chart {
type_id: "test_type.id",
name: "test_name",
title: "test_title",
units: "test_units",
charttype: Some(ChartType::area),
options: vec![ChartOption::hidden, ChartOption::obsolete],
module: "module_name",
..Chart::default()
};
let clone = c.clone();
assert_eq!(
clone.to_string(),
r#"CHART "test_type.id" "test_name" "test_title" "test_units" "" "" "area" "" "" "hidden obsolete" "" "module_name""#
);
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum Scope {
GLOBAL,
HOST,
LOCAL,
CHART,
}
#[derive(Debug, Default, Clone)]
pub struct Variable<'a> {
pub scope: Option<Scope>,
pub name: &'a str,
pub value: f64,
}
impl<'a> fmt::Display for Variable<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
format!(
"VARIABLE {}{} = {}",
match &self.scope {
Some(s) => format!("{:?} ", s),
_ => "".to_owned(),
},
self.name,
self.value
)
)
}
}
#[cfg(test)]
mod variable_tests {
use super::Scope;
use super::Variable;
#[test]
fn minimal_variable_output() {
let v = Variable {
scope: None,
name: "test_name",
value: 0f64,
};
assert_eq!(v.to_string(), "VARIABLE test_name = 0");
}
#[test]
fn scoped_variable_output() {
let v = Variable {
scope: Some(Scope::GLOBAL),
name: "test_name",
value: 3.14f64,
};
assert_eq!(v.to_string(), "VARIABLE GLOBAL test_name = 3.14");
}
}
#[derive(Debug, Default, Clone)]
pub struct Begin<'a> {
pub type_id: &'a str,
pub microseconds: Option<u128>,
}
impl<'a> fmt::Display for Begin<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
format!(
"{:?} {:?}{}",
Instruction::BEGIN,
self.type_id,
match &self.microseconds {
Some(us) => format!(" {}", us),
_ => "".to_owned(),
}
)
)
}
}
#[cfg(test)]
mod begin_tests {
use super::Begin;
#[test]
fn begin_output() {
let b = Begin {
type_id: "test_type.id",
..Default::default()
};
assert_eq!(b.to_string(), r#"BEGIN "test_type.id""#);
}
#[test]
fn begin_with_us_output() {
let b = Begin {
type_id: "test_type.id",
microseconds: Some(42),
};
assert_eq!(b.to_string(), r#"BEGIN "test_type.id" 42"#);
}
}
#[derive(Debug, Default, Clone)]
pub struct Set<'a> {
pub id: &'a str,
pub value: Option<i64>,
}
impl<'a> fmt::Display for Set<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
format!(
"{:?} {:?} ={}",
Instruction::SET,
self.id,
match &self.value {
Some(v) => format!(" {}", v),
_ => "".to_owned(),
}
)
)
}
}
#[cfg(test)]
mod set_tests {
use super::Set;
#[test]
fn incomplete_set_output() {
let s = Set {
id: "test_id",
..Default::default()
};
assert_eq!(s.to_string(), r#"SET "test_id" ="#);
}
#[test]
fn set_with_value_output() {
let s = Set {
id: "test_id",
value: Some(-42),
};
assert_eq!(s.to_string(), r#"SET "test_id" = -42"#);
}
}
fn some_to_textfield<T: Display>(opt: &Option<T>) -> String {
match opt {
Some(o) => format!("{}", o),
_ => format!(""),
}
}
fn options_to_textfield<T: Debug>(opts: &Vec<T>) -> String {
opts.iter()
.map(|o| format!("{:?}", o))
.collect::<Vec<String>>()
.join(" ")
}