use std::str::from_utf8;
use anyhow::{anyhow, Error};
use ariadne::{Config, Label, Report, ReportKind, Source};
use bytes::{BufMut, BytesMut};
use itertools::Either;
use logos::{Lexer, Logos};
use semver::Version;
use tracing::{trace, warn};
use crate::generators::Generator;
use crate::matchingrules::MatchingRule;
use crate::matchingrules::MatchingRule::NotEmpty;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ValueType {
Unknown,
String,
Number,
Integer,
Decimal,
Boolean
}
impl ValueType {
pub fn merge(self, other: ValueType) -> ValueType {
match (self, other) {
(ValueType::String, ValueType::String) => ValueType::String,
(ValueType::Number, ValueType::Number) => ValueType::Number,
(ValueType::Number, ValueType::Boolean) => ValueType::Number,
(ValueType::Number, ValueType::Unknown) => ValueType::Number,
(ValueType::Number, ValueType::Integer) => ValueType::Integer,
(ValueType::Number, ValueType::Decimal) => ValueType::Decimal,
(ValueType::Number, ValueType::String) => ValueType::String,
(ValueType::Integer, ValueType::Number) => ValueType::Integer,
(ValueType::Integer, ValueType::Boolean) => ValueType::Integer,
(ValueType::Integer, ValueType::Unknown) => ValueType::Integer,
(ValueType::Integer, ValueType::Integer) => ValueType::Integer,
(ValueType::Integer, ValueType::Decimal) => ValueType::Decimal,
(ValueType::Integer, ValueType::String) => ValueType::String,
(ValueType::Decimal, ValueType::Number) => ValueType::Decimal,
(ValueType::Decimal, ValueType::Boolean) => ValueType::Decimal,
(ValueType::Decimal, ValueType::Unknown) => ValueType::Decimal,
(ValueType::Decimal, ValueType::Integer) => ValueType::Decimal,
(ValueType::Decimal, ValueType::Decimal) => ValueType::Decimal,
(ValueType::Decimal, ValueType::String) => ValueType::String,
(ValueType::Boolean, ValueType::Number) => ValueType::Number,
(ValueType::Boolean, ValueType::Integer) => ValueType::Integer,
(ValueType::Boolean, ValueType::Decimal) => ValueType::Decimal,
(ValueType::Boolean, ValueType::Unknown) => ValueType::Boolean,
(ValueType::Boolean, ValueType::String) => ValueType::String,
(ValueType::Boolean, ValueType::Boolean) => ValueType::Boolean,
(ValueType::String, _) => ValueType::String,
(_, _) => other
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchingReference {
pub name: String
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchingRuleDefinition {
pub value: String,
pub value_type: ValueType,
pub rules: Vec<Either<MatchingRule, MatchingReference>>,
pub generator: Option<Generator>
}
impl MatchingRuleDefinition {
pub fn new(
value: String,
value_type: ValueType,
matching_rule: MatchingRule,
generator: Option<Generator>
) -> Self {
MatchingRuleDefinition {
value,
value_type,
rules: vec![ Either::Left(matching_rule) ],
generator
}
}
pub fn merge(&self, other: &MatchingRuleDefinition) -> MatchingRuleDefinition {
trace!("Merging {:?} with {:?}", self, other);
if !self.value.is_empty() && !other.value.is_empty() {
warn!("There are multiple matching rules with values for the same value. There is no \
reliable way to combine them, so the later value ('{}') will be ignored.", other.value)
}
if self.generator.is_some() && other.generator.is_some() {
warn!("There are multiple generators for the same value. There is no reliable way to combine \
them, so the later generator ({:?}) will be ignored.", other.generator)
}
MatchingRuleDefinition {
value: if self.value.is_empty() { other.value.clone() } else { self.value.clone() },
value_type: self.value_type.merge(other.value_type),
rules: [self.rules.clone(), other.rules.clone()].concat(),
generator: self.generator.as_ref().or_else(|| other.generator.as_ref()).cloned()
}
}
}
#[derive(Logos, Debug, PartialEq)]
enum MatcherDefinitionToken {
#[token("matching")]
Matching,
#[token("notEmpty")]
NotEmpty,
#[token("eachKey")]
EachKey,
#[token("eachValue")]
EachValue,
#[token("(")]
LeftBracket,
#[token(")")]
RightBracket,
#[token(",")]
Comma,
#[regex("'[^']*'")]
String,
#[regex("[a-zA-Z]+")]
Id,
#[regex("-?[0-9]+", |lex| lex.slice().parse())]
Int(i64),
#[regex(r"-?[0-9]\.[0-9]+")]
Decimal,
#[regex(r"\.[0-9]+")]
DecimalPart,
#[regex(r"true|false")]
Boolean,
#[regex(r"null")]
Null,
#[token("$")]
Dollar,
#[error]
#[regex(r"[ \t\n\f]+", logos::skip)]
Error
}
pub fn parse_matcher_def(v: &str) -> anyhow::Result<MatchingRuleDefinition> {
if v.is_empty() {
Err(anyhow!("Expected a matching rule definition, but got an empty string"))
} else {
let mut lex = MatcherDefinitionToken::lexer(v);
matching_definition(&mut lex, v)
}
}
pub fn is_matcher_def(v: &str) -> bool {
if v.is_empty() {
false
} else {
let mut lex = MatcherDefinitionToken::lexer(v);
let next = lex.next();
if let Some(token) = next {
if token == MatcherDefinitionToken::Matching || token == MatcherDefinitionToken::NotEmpty ||
token == MatcherDefinitionToken::EachKey || token == MatcherDefinitionToken::EachValue {
true
} else {
false
}
} else {
false
}
}
}
fn matching_definition(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<MatchingRuleDefinition> {
let mut value = matching_definition_exp(lex, v)?;
while let Some(next) = lex.next() {
if next == MatcherDefinitionToken::Comma {
value = value.merge(&matching_definition_exp(lex, v)?);
} else {
return Err(anyhow!("expected comma, got '{}'", lex.slice()));
}
}
let remainder = lex.remainder();
if !remainder.is_empty() {
Err(anyhow!("expected not more tokens, got '{}' with '{}' remaining", lex.slice(), remainder))
} else {
Ok(value)
}
}
fn matching_definition_exp(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<MatchingRuleDefinition> {
let next = lex.next();
if let Some(token) = next {
if token == MatcherDefinitionToken::Matching {
let (value, value_type, matching_rule, generator, reference) = parse_matching(lex, v)?;
if let Some(reference) = reference {
Ok(MatchingRuleDefinition {
value,
value_type: ValueType::Unknown,
rules: vec![ Either::Right(reference) ],
generator
})
} else {
Ok(MatchingRuleDefinition {
value,
value_type,
rules: vec![ Either::Left(matching_rule.unwrap()) ],
generator
})
}
} else if token == MatcherDefinitionToken::NotEmpty {
let (value, value_type) = parse_not_empty(lex, v)?;
Ok(MatchingRuleDefinition {
value,
value_type,
rules: vec![Either::Left(NotEmpty)],
generator: None
})
} else if token == MatcherDefinitionToken::EachKey {
let definition = parse_each_key(lex, v)?;
Ok(definition)
} else if token == MatcherDefinitionToken::EachValue {
let definition = parse_each_value(lex, v)?;
Ok(definition)
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a type of matching rule definition, but got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected a matching rule definition here"))
.with_note("valid matching rule definitions are: matching, notEmpty, eachKey, eachValue")
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a type of matching rule definition but got the end of the expression"))
.with_label(Label::new(("expression", span)).with_message("Expected a matching rule definition here"))
.with_note("valid matching rule definitions are: matching, notEmpty, eachKey, eachValue")
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn parse_each_value(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<MatchingRuleDefinition> {
let next = lex.next()
.ok_or_else(|| end_of_expression(v, "an opening bracket"))?;
if next == MatcherDefinitionToken::LeftBracket {
let result = matching_definition_exp(lex, v)?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "a closing bracket"))?;
if next == MatcherDefinitionToken::RightBracket {
Ok(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachValue(result)) ],
generator: None
})
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a closing bracket, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected a closing bracket before this"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected an opening bracket, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected an opening bracket before this"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn parse_each_key(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<MatchingRuleDefinition> {
let next = lex.next()
.ok_or_else(|| end_of_expression(v, "an opening bracket"))?;
if next == MatcherDefinitionToken::LeftBracket {
let result = matching_definition_exp(lex, v)?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "a closing bracket"))?;
if next == MatcherDefinitionToken::RightBracket {
Ok(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachKey(result)) ],
generator: None
})
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a closing bracket, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected a closing bracket before this"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected an opening bracket, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected an opening bracket before this"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn parse_not_empty(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType)> {
let next = lex.next().ok_or_else(|| anyhow!("expected '('"))?;
if next == MatcherDefinitionToken::LeftBracket {
let result = parse_primitive_value(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected ')'"))?;
if next == MatcherDefinitionToken::RightBracket {
Ok(result)
} else {
Err(anyhow!("expected closing bracket, got '{}'", lex.slice()))
}
} else {
Err(anyhow!("expected '(', got '{}'", lex.remainder()))
}
}
fn parse_matching(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
let next = lex.next().ok_or_else(|| anyhow!("expected '('"))?;
if next == MatcherDefinitionToken::LeftBracket {
let result = parse_matching_rule(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected ')'"))?;
if next == MatcherDefinitionToken::RightBracket {
Ok(result)
} else {
Err(anyhow!("expected closing bracket, got '{}'", lex.slice()))
}
} else {
Err(anyhow!("expected '(', got '{}'", lex.remainder()))
}
}
fn parse_matching_rule(lex: &mut logos::Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
let next = lex.next()
.ok_or_else(|| end_of_expression(v, "a matcher (equalTo, regex, etc.)"))?;
if next == MatcherDefinitionToken::Id {
match lex.slice() {
"equalTo" => parse_equality(lex, v),
"regex" => parse_regex(lex, v),
"type" => parse_type(lex, v),
"datetime" => parse_datetime(lex, v),
"date" => parse_date(lex, v),
"time" => parse_time(lex, v),
"include" => parse_include(lex, v),
"number" => parse_number(lex, v),
"integer" => parse_integer(lex, v),
"decimal" => parse_decimal(lex, v),
"boolean" => parse_boolean(lex, v),
"contentType" => parse_content_type(lex, v),
"semver" => parse_semver(lex, v),
_ => {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected the type of matcher, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("This is not a valid matcher type"))
.with_note("Valid matchers are: equalTo, regex, type, datetime, date, time, include, number, integer, decimal, boolean, contentType, semver")
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
} else if next == MatcherDefinitionToken::Dollar {
parse_reference(lex, v)
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected the type of matcher, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected a matcher (equalTo, regex, etc.) here"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn parse_reference(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
let name = parse_string(lex, v)?;
Ok((name.clone(), ValueType::Unknown, None, None, Some(MatchingReference { name })))
}
fn parse_semver(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
match Version::parse(value.as_str()) {
Ok(_) => Ok((value, ValueType::String, Some(MatchingRule::Semver), None, None)),
Err(err) => {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a semver compatible string, got {} - {}", lex.slice(), err))
.with_label(Label::new(("expression", span)).with_message("This is not a valid semver value"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
}
fn parse_equality(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let (value, value_type) = parse_primitive_value(lex, v)?;
Ok((value, value_type, Some(MatchingRule::Equality), None, None))
}
fn parse_regex(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let regex = parse_string(lex, v)?;
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value, ValueType::String, Some(MatchingRule::Regex(regex)), None, None))
}
fn parse_type(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let (value, value_type) = parse_primitive_value(lex, v)?;
Ok((value, value_type, Some(MatchingRule::Type), None, None))
}
fn parse_datetime(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let format = parse_string(lex, v)?;
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value, ValueType::String, Some(MatchingRule::Timestamp(format.clone())), Some(Generator::DateTime(Some(format), None)), None))
}
fn parse_date(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let format = parse_string(lex, v)?;
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value, ValueType::String, Some(MatchingRule::Date(format.clone())), Some(Generator::Date(Some(format), None)), None))
}
fn parse_time(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let format = parse_string(lex, v)?;
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value, ValueType::String, Some(MatchingRule::Time(format.clone())), Some(Generator::Time(Some(format), None)), None))
}
fn parse_include(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value.clone(), ValueType::String, Some(MatchingRule::Include(value)), None, None))
}
fn parse_content_type(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let ct = parse_string(lex, v)?;
parse_comma(lex, v)?;
let value = parse_string(lex, v)?;
Ok((value, ValueType::Unknown, Some(MatchingRule::ContentType(ct)), None, None))
}
fn parse_primitive_value(lex: &mut Lexer<MatcherDefinitionToken>, _v: &str) -> anyhow::Result<(String, ValueType)> {
let next = lex.next().ok_or_else(|| anyhow!("expected a primitive value"))?;
match next {
MatcherDefinitionToken::String => Ok((lex.slice().trim_matches('\'').to_string(), ValueType::String)),
MatcherDefinitionToken::Null => Ok((String::new(), ValueType::String)),
MatcherDefinitionToken::Int(_) => {
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
lex.next().ok_or_else(|| anyhow!("expected a number"))?;
Ok((format!("{}{}", int_part, lex.slice()), ValueType::Decimal))
} else {
Ok((lex.slice().to_string(), ValueType::Integer))
}
},
MatcherDefinitionToken::Decimal => Ok((lex.slice().to_string(), ValueType::Decimal)),
MatcherDefinitionToken::Boolean => Ok((lex.slice().to_string(), ValueType::Boolean)),
_ => Err(anyhow!("expected a primitive value, got '{}'", lex.slice()))
}
}
#[allow(clippy::if_same_then_else)]
fn parse_number(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected a number"))?;
if MatcherDefinitionToken::Decimal == next {
Ok((lex.slice().to_string(), ValueType::Number, Some(MatchingRule::Number), None, None))
} else if let MatcherDefinitionToken::Int(_) = next {
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
lex.next().ok_or_else(|| anyhow!("expected a number"))?;
Ok((format!("{}{}", int_part, lex.slice()), ValueType::Number, Some(MatchingRule::Number), None, None))
} else {
Ok((lex.slice().to_string(), ValueType::Number, Some(MatchingRule::Number), None, None))
}
} else {
Err(anyhow!("expected a number, got '{}'", lex.slice()))
}
}
fn parse_integer(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected an integer"))?;
if let MatcherDefinitionToken::Int(_) = next {
Ok((lex.slice().to_string(), ValueType::Integer, Some(MatchingRule::Integer), None, None))
} else {
Err(anyhow!("expected an integer, got '{}'", lex.slice()))
}
}
#[allow(clippy::if_same_then_else)]
fn parse_decimal(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected a decimal number"))?;
if let MatcherDefinitionToken::Int(_) = next {
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
lex.next().ok_or_else(|| anyhow!("expected a number"))?;
Ok((format!("{}{}", int_part, lex.slice()), ValueType::Decimal, Some(MatchingRule::Decimal), None, None))
} else {
Ok((lex.slice().to_string(), ValueType::Decimal, Some(MatchingRule::Decimal), None, None))
}
} else if MatcherDefinitionToken::Decimal == next {
Ok((lex.slice().to_string(), ValueType::Decimal, Some(MatchingRule::Decimal), None, None))
} else {
Err(anyhow!("expected a decimal number, got '{}'", lex.slice()))
}
}
fn parse_boolean(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType, Option<MatchingRule>, Option<Generator>, Option<MatchingReference>)> {
parse_comma(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected a boolean"))?;
if MatcherDefinitionToken::Boolean == next {
Ok((lex.slice().to_string(), ValueType::Boolean, Some(MatchingRule::Boolean), None, None))
} else {
Err(anyhow!("expected a boolean, got '{}'", lex.slice()))
}
}
fn parse_string(lex: &mut logos::Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<String> {
let next = lex.next().ok_or_else(|| end_of_expression(v, "a string"))?;
if next == MatcherDefinitionToken::String {
Ok(lex.slice().trim_matches('\'').to_string())
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a string value, got {}", lex.slice()))
.with_label(Label::new(("expression", span.clone())).with_message("Expected this to be a string"))
.with_note(format!("Surround the value in quotes: {}'{}'{}", &v[..span.start], lex.slice(), &v[span.end..]))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn parse_comma(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<()> {
let next = lex.next().ok_or_else(|| end_of_expression(v, "a comma"))?;
if next == MatcherDefinitionToken::Comma {
Ok(())
} else {
let mut buffer = BytesMut::new().writer();
let span = lex.span();
let report = Report::build(ReportKind::Error, "expression", span.start)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected a comma, got '{}'", lex.slice()))
.with_label(Label::new(("expression", span)).with_message("Expected a comma before this"))
.finish();
report.write(("expression", Source::from(v)), &mut buffer)?;
let message = from_utf8(&*buffer.get_ref())?.to_string();
Err(anyhow!(message))
}
}
fn end_of_expression(v: &str, expected: &str) -> Error {
let mut buffer = BytesMut::new().writer();
let i = v.len();
let report = Report::build(ReportKind::Error, "expression", i)
.with_config(Config::default().with_color(false))
.with_message(format!("Expected {}, got the end of the expression", expected))
.with_label(Label::new(("expression", i..i)).with_message(format!("Expected {} here", expected)))
.finish();
report.write(("expression", Source::from(v)), &mut buffer).unwrap();
let message = from_utf8(&*buffer.get_ref()).unwrap().to_string();
anyhow!(message)
}
#[cfg(test)]
mod test {
use expectest::prelude::*;
use trim_margin::MarginTrimmable;
use crate::generators::Generator::{Date, DateTime, Time};
use crate::matchingrules::MatchingRule;
use crate::matchingrules::MatchingRule::{Regex, Type};
use super::*;
macro_rules! as_string {
($e:expr) => {{ $e.map_err(|err| err.to_string()) }};
}
#[test]
fn does_not_start_with_matching() {
expect!(super::parse_matcher_def("")).to(be_err());
expect!(super::parse_matcher_def("a, b, c")).to(be_err());
expect!(super::parse_matcher_def("matching some other text")).to(be_err());
}
#[test]
fn parse_type_matcher() {
expect!(super::parse_matcher_def("matching(type,'Name')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Name".to_string(), ValueType::String, MatchingRule::Type, None)));
expect!(super::parse_matcher_def("matching( type, 'Name' )").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Name".to_string(), ValueType::String, MatchingRule::Type, None)));
expect!(super::parse_matcher_def("matching(type,123.4)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("123.4".to_string(), ValueType::Decimal, MatchingRule::Type, None)));
}
#[test]
fn parse_number_matcher() {
expect!(super::parse_matcher_def("matching(number,100)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("100".to_string(), ValueType::Number, MatchingRule::Number, None)));
expect!(super::parse_matcher_def("matching(number,200.22)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("200.22".to_string(), ValueType::Number, MatchingRule::Number, None)));
expect!(super::parse_matcher_def("matching(integer,100)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("100".to_string(), ValueType::Integer, MatchingRule::Integer, None)));
expect!(super::parse_matcher_def("matching(decimal,100)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("100".to_string(), ValueType::Decimal, MatchingRule::Decimal, None)));
expect!(super::parse_matcher_def("matching(decimal,100.22)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("100.22".to_string(), ValueType::Decimal, MatchingRule::Decimal, None)));
}
#[test]
fn parse_datetime_matcher() {
expect!(super::parse_matcher_def("matching(datetime, 'yyyy-MM-dd','2000-01-01')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("2000-01-01".to_string(),
ValueType::String,
MatchingRule::Timestamp("yyyy-MM-dd".to_string()),
Some(DateTime(Some("yyyy-MM-dd".to_string()), None)))));
expect!(super::parse_matcher_def("matching(date, 'yyyy-MM-dd','2000-01-01')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("2000-01-01".to_string(),
ValueType::String,
MatchingRule::Date("yyyy-MM-dd".to_string()),
Some(Date(Some("yyyy-MM-dd".to_string()), None)))));
expect!(super::parse_matcher_def("matching(time, 'HH:mm:ss','12:00:00')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("12:00:00".to_string(),
ValueType::String,
MatchingRule::Time("HH:mm:ss".to_string()),
Some(Time(Some("HH:mm:ss".to_string()), None)))));
}
#[test]
fn parse_regex_matcher() {
expect!(super::parse_matcher_def("matching(regex,'\\w+', 'Fred')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Fred".to_string(),
ValueType::String,
MatchingRule::Regex("\\w+".to_string()),
None)));
}
#[test]
fn parse_boolean_matcher() {
expect!(super::parse_matcher_def("matching(boolean,true)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("true".to_string(),
ValueType::Boolean,
MatchingRule::Boolean,
None)));
}
#[test]
fn parse_include_matcher() {
expect!(super::parse_matcher_def("matching(include,'Name')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Name".to_string(),
ValueType::String,
MatchingRule::Include("Name".to_string()),
None)));
}
#[test]
fn parse_equals_matcher() {
expect!(super::parse_matcher_def("matching(equalTo,'Name')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Name".to_string(),
ValueType::String,
MatchingRule::Equality,
None)));
expect!(super::parse_matcher_def("matching(equalTo,123.4)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("123.4".to_string(),
ValueType::Decimal,
MatchingRule::Equality,
None)));
}
#[test]
fn parse_content_type_matcher() {
expect!(super::parse_matcher_def("matching(contentType,'Name', 'Value')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Value".to_string(),
ValueType::Unknown,
MatchingRule::ContentType("Name".to_string()),
None)));
}
#[test]
fn parse_not_empty() {
expect!(super::parse_matcher_def("notEmpty('Value')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("Value".to_string(),
ValueType::String,
MatchingRule::NotEmpty,
None)));
expect!(super::parse_matcher_def("notEmpty(100)").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("100".to_string(),
ValueType::Integer,
MatchingRule::NotEmpty,
None)));
}
#[test]
fn parse_comma() {
expect!(super::parse_comma(&mut MatcherDefinitionToken::lexer(", notEmpty('Value')"), ", notEmpty('Value')")).to(be_ok());
let mut lex = super::MatcherDefinitionToken::lexer("100 notEmpty(100)");
lex.next();
expect!(as_string!(super::parse_comma(&mut lex, "100 notEmpty(100)"))).to(
be_err().value(
"|Error: Expected a comma, got 'notEmpty'
| â•â”€[expression:1:5]
| │
| 1 │ 100 notEmpty(100)
| · ────┬─── \u{0020}
| · ╰───── Expected a comma before this
|───╯
|
".trim_margin_with("|").unwrap()
));
let mut lex2 = super::MatcherDefinitionToken::lexer("100");
lex2.next();
expect!(as_string!(super::parse_comma(&mut lex2, "100"))).to(
be_err().value(
"|Error: Expected a comma, got the end of the expression
| â•â”€[expression:1:4]
| │
| 1 │ 100
| · │\u{0020}
| · ╰─ Expected a comma here
|───╯
|
".trim_margin_with("|").unwrap()
));
}
#[test]
fn merging_types() {
expect!(ValueType::String.merge(ValueType::Unknown)).to(be_equal_to(ValueType::String));
expect!(ValueType::Unknown.merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::Unknown.merge(ValueType::Number )).to(be_equal_to(ValueType::Number));
expect!(ValueType::Number .merge(ValueType::Unknown)).to(be_equal_to(ValueType::Number));
expect!(ValueType::Unknown.merge(ValueType::Integer)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Integer.merge(ValueType::Unknown)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Unknown.merge(ValueType::Decimal)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Decimal.merge(ValueType::Unknown)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Unknown.merge(ValueType::Boolean)).to(be_equal_to(ValueType::Boolean));
expect!(ValueType::Boolean.merge(ValueType::Unknown)).to(be_equal_to(ValueType::Boolean));
expect!(ValueType::Unknown.merge(ValueType::Unknown)).to(be_equal_to(ValueType::Unknown));
expect!(ValueType::String .merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::Number .merge(ValueType::Number )).to(be_equal_to(ValueType::Number));
expect!(ValueType::Integer.merge(ValueType::Integer)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Decimal.merge(ValueType::Decimal)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Boolean.merge(ValueType::Boolean)).to(be_equal_to(ValueType::Boolean));
expect!(ValueType::Number .merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::Integer.merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::Decimal.merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::Boolean.merge(ValueType::String )).to(be_equal_to(ValueType::String));
expect!(ValueType::String .merge(ValueType::Number )).to(be_equal_to(ValueType::String));
expect!(ValueType::String .merge(ValueType::Integer)).to(be_equal_to(ValueType::String));
expect!(ValueType::String .merge(ValueType::Decimal)).to(be_equal_to(ValueType::String));
expect!(ValueType::String .merge(ValueType::Boolean)).to(be_equal_to(ValueType::String));
expect!(ValueType::Number .merge(ValueType::Integer)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Number .merge(ValueType::Decimal)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Number .merge(ValueType::Boolean)).to(be_equal_to(ValueType::Number));
expect!(ValueType::Integer.merge(ValueType::Number )).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Integer.merge(ValueType::Decimal)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Integer.merge(ValueType::Boolean)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Decimal.merge(ValueType::Number )).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Decimal.merge(ValueType::Integer)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Decimal.merge(ValueType::Boolean)).to(be_equal_to(ValueType::Decimal));
expect!(ValueType::Boolean.merge(ValueType::Number )).to(be_equal_to(ValueType::Number));
expect!(ValueType::Boolean.merge(ValueType::Integer)).to(be_equal_to(ValueType::Integer));
expect!(ValueType::Boolean.merge(ValueType::Decimal)).to(be_equal_to(ValueType::Decimal));
}
#[test]
fn parse_semver_matcher() {
expect!(super::parse_matcher_def("matching(semver, '1.0.0')").unwrap()).to(
be_equal_to(MatchingRuleDefinition::new("1.0.0".to_string(),
ValueType::String,
MatchingRule::Semver,
None)));
expect!(as_string!(super::parse_matcher_def("matching(semver, '100')"))).to(
be_err().value(
"|Error: Expected a semver compatible string, got '100' - unexpected end of input while parsing major version number
| â•â”€[expression:1:18]
| │
| 1 │ matching(semver, '100')
| · ──┬── \u{0020}
| · ╰──── This is not a valid semver value
|───╯
|
".trim_margin().unwrap()));
expect!(as_string!(super::parse_matcher_def("matching(semver, 100)"))).to(
be_err().value(
"|Error: Expected a string value, got 100
| â•â”€[expression:1:18]
| │
| 1 │ matching(semver, 100)
| · ─┬─ \u{0020}
| · ╰─── Expected this to be a string
| ·\u{0020}
| · Note: Surround the value in quotes: matching(semver, '100')
|───╯
|
".trim_margin().unwrap()
));
}
#[test]
fn parse_matching_rule_test() {
let mut lex = super::MatcherDefinitionToken::lexer("type, '1.0.0')");
expect!(super::parse_matching_rule(&mut lex, "matching(type, '1.0.0')").unwrap()).to(
be_equal_to(("1.0.0".to_string(), ValueType::String, Some(Type), None, None)));
let mut lex = super::MatcherDefinitionToken::lexer("match(");
lex.next();
lex.next();
expect!(as_string!(super::parse_matching_rule(&mut lex, "matching("))).to(
be_err().value(
"|Error: Expected a matcher (equalTo, regex, etc.), got the end of the expression
| â•â”€[expression:1:10]
| │
| 1 │ matching(
| · │\u{0020}
| · ╰─ Expected a matcher (equalTo, regex, etc.) here
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("match(100, '100')");
lex.next();
lex.next();
expect!(as_string!(super::parse_matching_rule(&mut lex, "match(100, '100')"))).to(
be_err().value(
"|Error: Expected the type of matcher, got '100'
| â•â”€[expression:1:7]
| │
| 1 │ match(100, '100')
| · ─┬─ \u{0020}
| · ╰─── Expected a matcher (equalTo, regex, etc.) here
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("match(testABBC, '100')");
lex.next();
lex.next();
expect!(as_string!(super::parse_matching_rule(&mut lex, "match(testABBC, '100')"))).to(
be_err().value(
"|Error: Expected the type of matcher, got 'testABBC'
| â•â”€[expression:1:7]
| │
| 1 │ match(testABBC, '100')
| · ────┬─── \u{0020}
| · ╰───── This is not a valid matcher type
| ·\u{0020}
| · Note: Valid matchers are: equalTo, regex, type, datetime, date, time, include, number, integer, decimal, boolean, contentType, semver
|───╯
|
".trim_margin().unwrap()));
}
#[test]
fn parse_matching_rule_with_reference_test() {
let mut lex = super::MatcherDefinitionToken::lexer("$'bob'");
expect!(super::parse_matching_rule(&mut lex, "matching($'bob')").unwrap()).to(
be_equal_to(("bob".to_string(), ValueType::Unknown, None, None, Some(MatchingReference {
name: "bob".to_string()
}))));
let mut lex = super::MatcherDefinitionToken::lexer("match($");
lex.next();
lex.next();
expect!(as_string!(super::parse_matching_rule(&mut lex, "matching($"))).to(
be_err().value(
"|Error: Expected a string, got the end of the expression
| â•â”€[expression:1:11]
| │
| 1 │ matching($
| · │\u{0020}
| · ╰─ Expected a string here
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("match($100)");
lex.next();
lex.next();
expect!(as_string!(super::parse_matching_rule(&mut lex, "match($100)"))).to(
be_err().value(
"|Error: Expected a string value, got 100
| â•â”€[expression:1:8]
| │
| 1 │ match($100)
| · ─┬─ \u{0020}
| · ╰─── Expected this to be a string
| ·\u{0020}
| · Note: Surround the value in quotes: match($'100')
|───╯
|
".trim_margin().unwrap()));
}
#[test]
fn matching_definition_exp_test() {
let mut lex = super::MatcherDefinitionToken::lexer("notEmpty('test')");
expect!(super::matching_definition_exp(&mut lex, "notEmpty('test')")).to(
be_ok().value(MatchingRuleDefinition {
value: "test".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(NotEmpty) ],
generator: None
})
);
let mut lex = super::MatcherDefinitionToken::lexer("matching(regex, '.*', 'aaabbb')");
expect!(super::matching_definition_exp(&mut lex, "matching(regex, '.*', 'aaabbb')")).to(
be_ok().value(MatchingRuleDefinition {
value: "aaabbb".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(Regex(".*".to_string())) ],
generator: None
})
);
let mut lex = super::MatcherDefinitionToken::lexer("matching($'test')");
expect!(super::matching_definition_exp(&mut lex, "matching($'test')")).to(
be_ok().value(MatchingRuleDefinition {
value: "test".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Right(MatchingReference { name: "test".to_string() }) ],
generator: None
})
);
let mut lex = super::MatcherDefinitionToken::lexer("eachKey(matching(regex, '.*', 'aaabbb'))");
expect!(super::matching_definition_exp(&mut lex, "eachKey(matching(regex, '.*', 'aaabbb'))")).to(
be_ok().value(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachKey(MatchingRuleDefinition {
value: "aaabbb".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(Regex(".*".to_string())) ],
generator: None
})) ],
generator: None
})
);
let mut lex = super::MatcherDefinitionToken::lexer("eachValue(matching(regex, '.*', 'aaabbb'))");
expect!(super::matching_definition_exp(&mut lex, "eachValue(matching(regex, '.*', 'aaabbb'))")).to(
be_ok().value(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachValue(MatchingRuleDefinition {
value: "aaabbb".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(Regex(".*".to_string())) ],
generator: None
})) ],
generator: None
})
);
let mut lex = super::MatcherDefinitionToken::lexer("100");
lex.next();
expect!(as_string!(super::matching_definition_exp(&mut lex, "100"))).to(
be_err().value(
"|Error: Expected a type of matching rule definition but got the end of the expression
| â•â”€[expression:1:4]
| │
| 1 │ 100
| · │\u{0020}
| · ╰─ Expected a matching rule definition here
| ·\u{0020}
| · Note: valid matching rule definitions are: matching, notEmpty, eachKey, eachValue
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("somethingElse('to test')");
expect!(as_string!(super::matching_definition_exp(&mut lex, "somethingElse('to test')"))).to(
be_err().value(
"|Error: Expected a type of matching rule definition, but got 'somethingElse'
| â•â”€[expression:1:1]
| │
| 1 │ somethingElse('to test')
| · ──────┬────── \u{0020}
| · ╰──────── Expected a matching rule definition here
| ·\u{0020}
| · Note: valid matching rule definitions are: matching, notEmpty, eachKey, eachValue
|───╯
|
".trim_margin().unwrap()));
}
#[test]
fn parse_each_key_test() {
let mut lex = super::MatcherDefinitionToken::lexer("(matching($'bob'))");
expect!(super::parse_each_key(&mut lex, "(matching($'bob'))").unwrap()).to(
be_equal_to(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachKey(MatchingRuleDefinition {
value: "bob".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Right(MatchingReference { name: "bob".to_string() }) ],
generator: None }))
],
generator: None
}));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey");
lex.next();
expect!(as_string!(super::parse_each_key(&mut lex, "eachKey"))).to(
be_err().value(
"|Error: Expected an opening bracket, got the end of the expression
| â•â”€[expression:1:8]
| │
| 1 │ eachKey
| · │\u{0020}
| · ╰─ Expected an opening bracket here
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey matching");
lex.next();
expect!(as_string!(super::parse_each_key(&mut lex, "eachKey matching"))).to(
be_err().value(
"|Error: Expected an opening bracket, got 'matching'
| â•â”€[expression:1:9]
| │
| 1 │ eachKey matching
| · ────┬─── \u{0020}
| · ╰───── Expected an opening bracket before this
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey(matching(type, 'test') stuff");
lex.next();
expect!(as_string!(super::parse_each_key(&mut lex, "eachKey(matching(type, 'test') stuff"))).to(
be_err().value(
"|Error: Expected a closing bracket, got 'stuff'
| â•â”€[expression:1:32]
| │
| 1 │ eachKey(matching(type, 'test') stuff
| · ──┬── \u{0020}
| · ╰──── Expected a closing bracket before this
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey(matching(type, 'test')");
lex.next();
expect!(as_string!(super::parse_each_key(&mut lex, "eachKey(matching(type, 'test')"))).to(
be_err().value(
"|Error: Expected a closing bracket, got the end of the expression
| â•â”€[expression:1:31]
| │
| 1 │ eachKey(matching(type, 'test')
| · │\u{0020}
| · ╰─ Expected a closing bracket here
|───╯
|
".trim_margin().unwrap()));
}
#[test]
fn parse_each_value_test() {
let mut lex = super::MatcherDefinitionToken::lexer("(matching($'bob'))");
expect!(super::parse_each_value(&mut lex, "(matching($'bob'))").unwrap()).to(
be_equal_to(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Left(MatchingRule::EachValue(MatchingRuleDefinition {
value: "bob".to_string(),
value_type: ValueType::Unknown,
rules: vec![ Either::Right(MatchingReference { name: "bob".to_string() }) ],
generator: None }))
],
generator: None
}));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey");
lex.next();
expect!(as_string!(super::parse_each_value(&mut lex, "eachKey"))).to(
be_err().value(
"|Error: Expected an opening bracket, got the end of the expression
| â•â”€[expression:1:8]
| │
| 1 │ eachKey
| · │\u{0020}
| · ╰─ Expected an opening bracket here
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey matching");
lex.next();
expect!(as_string!(super::parse_each_value(&mut lex, "eachKey matching"))).to(
be_err().value(
"|Error: Expected an opening bracket, got 'matching'
| â•â”€[expression:1:9]
| │
| 1 │ eachKey matching
| · ────┬─── \u{0020}
| · ╰───── Expected an opening bracket before this
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey(matching(type, 'test') stuff");
lex.next();
expect!(as_string!(super::parse_each_value(&mut lex, "eachKey(matching(type, 'test') stuff"))).to(
be_err().value(
"|Error: Expected a closing bracket, got 'stuff'
| â•â”€[expression:1:32]
| │
| 1 │ eachKey(matching(type, 'test') stuff
| · ──┬── \u{0020}
| · ╰──── Expected a closing bracket before this
|───╯
|
".trim_margin().unwrap()));
let mut lex = super::MatcherDefinitionToken::lexer("eachKey(matching(type, 'test')");
lex.next();
expect!(as_string!(super::parse_each_value(&mut lex, "eachKey(matching(type, 'test')"))).to(
be_err().value(
"|Error: Expected a closing bracket, got the end of the expression
| â•â”€[expression:1:31]
| │
| 1 │ eachKey(matching(type, 'test')
| · │\u{0020}
| · ╰─ Expected a closing bracket here
|───╯
|
".trim_margin().unwrap()));
}
#[test_log::test]
fn parse_multiple_matcher_definitions() {
expect!(super::parse_matcher_def("eachKey(matching(regex, '\\$(\\.\\w+)+', '$.test.one')), eachValue(matching(type, null))").unwrap()).to(
be_equal_to(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![
Either::Left(MatchingRule::EachKey(MatchingRuleDefinition { value: "$.test.one".to_string(), value_type: ValueType::String, rules: vec![Either::Left(MatchingRule::Regex("\\$(\\.\\w+)+".to_string()))], generator: None } )),
Either::Left(MatchingRule::EachValue(MatchingRuleDefinition { value: "".to_string(), value_type: ValueType::Unknown, rules: vec![Either::Left(MatchingRule::Type)], generator: None } ))
],
generator: None
}));
}
#[test_log::test]
fn merge_definitions() {
let basic = MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None
};
let with_value = MatchingRuleDefinition {
value: "value".to_string(),
value_type: ValueType::Unknown,
rules: vec![],
generator: None
};
let with_type = MatchingRuleDefinition {
value: "value".to_string(),
value_type: ValueType::String,
rules: vec![],
generator: None
};
let with_generator = MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::String,
rules: vec![],
generator: Some(Generator::Date(None, None))
};
let with_matching_rule = MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Type) ],
generator: None
};
expect!(basic.merge(&basic)).to(be_equal_to(basic.clone()));
expect!(basic.merge(&with_value)).to(be_equal_to(with_value.clone()));
expect!(basic.merge(&with_type)).to(be_equal_to(with_type.clone()));
expect!(basic.merge(&with_generator)).to(be_equal_to(with_generator.clone()));
expect!(basic.merge(&with_matching_rule)).to(be_equal_to(with_matching_rule.clone()));
expect!(with_matching_rule.merge(&with_matching_rule)).to(be_equal_to(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Type), Either::Left(MatchingRule::Type) ],
generator: None
}));
let each_key = MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![
Either::Left(MatchingRule::EachKey(MatchingRuleDefinition {
value: "$.test.one".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Regex("\\$(\\.\\w+)+".to_string())) ],
generator: None
}))
],
generator: None
};
let each_value = MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![
Either::Left(MatchingRule::EachValue(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Type) ],
generator: None
}))
],
generator: None
};
expect!(each_key.merge(&each_value)).to(be_equal_to(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::Unknown,
rules: vec![
Either::Left(MatchingRule::EachKey(MatchingRuleDefinition {
value: "$.test.one".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Regex("\\$(\\.\\w+)+".to_string())) ],
generator: None
})),
Either::Left(MatchingRule::EachValue(MatchingRuleDefinition {
value: "".to_string(),
value_type: ValueType::String,
rules: vec![ Either::Left(MatchingRule::Type) ],
generator: None
}))
],
generator: None
}));
}
}