use crate::Type;
use lazy_regex::{lazy_regex, Lazy, Regex};
pub static COMMAS_SPACES: Lazy<Regex> = lazy_regex!("[, ]");
pub static COMMAS_SPACES_EQUALS: Lazy<Regex> = lazy_regex!("[, =]");
pub static QUOTES_SLASHES: Lazy<Regex> = lazy_regex!(r#"["\\]"#);
pub static SLASHES: Lazy<Regex> = lazy_regex!(r#"(\\|,| |=|")"#);
pub enum LineProtoTerm<'a> {
Measurement(&'a str), TagKey(&'a str), TagValue(&'a Type), FieldKey(&'a str), FieldValue(&'a Type), }
impl LineProtoTerm<'_> {
pub fn escape(self) -> String {
use LineProtoTerm::*;
match self {
Measurement(x) => Self::escape_any(x, &COMMAS_SPACES),
TagKey(x) | FieldKey(x) => Self::escape_any(x, &COMMAS_SPACES_EQUALS),
FieldValue(x) => Self::escape_field_value(x, false),
TagValue(x) => Self::escape_tag_value(x),
}
}
pub fn escape_v2(self) -> String {
use LineProtoTerm::*;
match self {
Measurement(x) => Self::escape_any(x, &COMMAS_SPACES),
TagKey(x) | FieldKey(x) => Self::escape_any(x, &COMMAS_SPACES_EQUALS),
FieldValue(x) => Self::escape_field_value(x, true),
TagValue(x) => Self::escape_tag_value(x),
}
}
fn escape_field_value(v: &Type, use_v2: bool) -> String {
use Type::*;
match v {
Boolean(v) => {
if *v {
"true"
} else {
"false"
}
}
.to_string(),
Float(v) => v.to_string(),
SignedInteger(v) => format!("{v}i"),
UnsignedInteger(v) => {
if use_v2 {
format!("{v}u")
} else {
format!("{v}i")
}
}
Text(v) => format!(r#""{}""#, Self::escape_any(v, "ES_SLASHES)),
}
}
fn escape_tag_value(v: &Type) -> String {
use Type::*;
match v {
Boolean(v) => {
if *v {
"true"
} else {
"false"
}
}
.into(),
Float(v) => v.to_string(),
SignedInteger(v) => v.to_string(),
UnsignedInteger(v) => v.to_string(),
Text(v) => Self::escape_any(v, &SLASHES),
}
}
fn escape_any(s: &str, re: &Regex) -> String {
re.replace_all(s, r"\$0").to_string()
}
}
#[cfg(test)]
mod test {
use crate::query::line_proto_term::LineProtoTerm::*;
use crate::Type;
#[test]
fn test() {
assert_eq!(TagValue(&Type::Boolean(true)).escape(), r#"true"#);
assert_eq!(TagValue(&Type::Float(1.8324f64)).escape(), r#"1.8324"#);
assert_eq!(TagValue(&Type::SignedInteger(-1i64)).escape(), r#"-1"#);
assert_eq!(TagValue(&Type::UnsignedInteger(1u64)).escape(), r#"1"#);
assert_eq!(
TagValue(&Type::Text("this is my special string".into())).escape(),
r"this\ is\ my\ special\ string"
);
assert_eq!(
TagValue(&Type::Text("a tag w=i th == tons of escapes".into())).escape(),
r"a\ tag\ w\=i\ th\ \=\=\ tons\ of\ escapes"
);
assert_eq!(
TagValue(&Type::Text("no_escapes".into())).escape(),
r#"no_escapes"#
);
assert_eq!(
TagValue(&Type::Text("some,commas,here".into())).escape(),
r"some\,commas\,here"
);
assert_eq!(Measurement(r#"wea", ther"#).escape(), r#"wea"\,\ ther"#);
assert_eq!(TagKey(r"locat\ ,=ion").escape(), r"locat\\ \,\=ion");
assert_eq!(FieldValue(&Type::Boolean(true)).escape(), r#"true"#);
assert_eq!(FieldValue(&Type::Boolean(false)).escape(), r#"false"#);
assert_eq!(FieldValue(&Type::Float(0.0)).escape(), r#"0"#);
assert_eq!(FieldValue(&Type::Float(-0.1)).escape(), r#"-0.1"#);
assert_eq!(FieldValue(&Type::SignedInteger(0)).escape(), r#"0i"#);
assert_eq!(FieldValue(&Type::SignedInteger(83)).escape(), r#"83i"#);
assert_eq!(FieldValue(&Type::UnsignedInteger(0)).escape(), r#"0i"#);
assert_eq!(FieldValue(&Type::UnsignedInteger(83)).escape(), r#"83i"#);
assert_eq!(FieldValue(&Type::UnsignedInteger(0)).escape_v2(), r#"0u"#);
assert_eq!(FieldValue(&Type::UnsignedInteger(83)).escape_v2(), r#"83u"#);
assert_eq!(FieldValue(&Type::Text("".into())).escape(), r#""""#);
assert_eq!(FieldValue(&Type::Text("0".into())).escape(), r#""0""#);
assert_eq!(FieldValue(&Type::Text("\"".into())).escape(), r#""\"""#);
assert_eq!(
FieldValue(&Type::Text(r#"locat"\ ,=ion"#.into())).escape(),
r#""locat\"\\ ,=ion""#
);
}
#[test]
fn test_empty_tag_value() {
assert_eq!(TagValue(&Type::Text("".into())).escape(), r#""#);
}
}