Skip to content

Commit

Permalink
chore(pact_models): Improve the matching rule expression parser error…
Browse files Browse the repository at this point in the history
… messages
  • Loading branch information
rholshausen committed Aug 1, 2024
1 parent 77814ae commit 2ca9a9a
Showing 1 changed file with 23 additions and 22 deletions.
45 changes: 23 additions & 22 deletions rust/pact_models/src/matchingrules/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ use bytes::{BufMut, BytesMut};
use itertools::Either;
use logos::{Lexer, Logos, Span};
use semver::Version;
use tracing::{trace, warn};
use tracing::{instrument, trace, warn};

use crate::generators::Generator;
use crate::matchingrules::MatchingRule;
Expand Down Expand Up @@ -288,6 +288,7 @@ enum MatcherDefinitionToken {
/// * `matching(type,'Name')` - type matcher
/// * `matching(number,100)` - number matcher
/// * `matching(datetime, 'yyyy-MM-dd','2000-01-01')` - datetime matcher with format string
#[instrument(level = "debug", ret)]
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"))
Expand Down Expand Up @@ -517,10 +518,10 @@ fn parse_each_key(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::R

// LEFT_BRACKET primitiveValue RIGHT_BRACKET
fn parse_not_empty(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType)> {
let next = lex.next().ok_or_else(|| anyhow!("expected '('"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "'('"))?;
if let Ok(MatcherDefinitionToken::LeftBracket) = next {
let result = parse_primitive_value(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected ')'"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "')'"))?;
if let Ok(MatcherDefinitionToken::RightBracket) = next {
Ok(result)
} else {
Expand All @@ -533,17 +534,17 @@ fn parse_not_empty(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::

// LEFT_BRACKET matchingRule RIGHT_BRACKET
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 '('"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "'('"))?;
if let Ok(MatcherDefinitionToken::LeftBracket) = next {
let result = parse_matching_rule(lex, v)?;
let next = lex.next().ok_or_else(|| anyhow!("expected ')'"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "')'"))?;
if let Ok(MatcherDefinitionToken::RightBracket) = next {
Ok(result)
} else {
Err(anyhow!("expected closing bracket, got '{}'", lex.slice()))
Err(anyhow!(error_message(lex, v, "Expected a closing bracket", "Expected a closing bracket before this")?))
}
} else {
Err(anyhow!("expected '(', got '{}'", lex.remainder()))
Err(anyhow!(error_message(lex, v, "Expected an opening bracket", "Expected an opening bracket before this")?))
}
}

Expand Down Expand Up @@ -721,8 +722,8 @@ fn parse_content_type(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyho
// }
// | 'null'
// ;
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"))?;
fn parse_primitive_value(lex: &mut Lexer<MatcherDefinitionToken>, v: &str) -> anyhow::Result<(String, ValueType)> {
let next = lex.next().ok_or_else(|| end_of_expression(v, "expected a primitive value"))?;
match next {
Ok(MatcherDefinitionToken::String) => Ok((lex.slice().trim_matches('\'').to_string(), ValueType::String)),
Ok(MatcherDefinitionToken::Null) => Ok((String::new(), ValueType::String)),
Expand All @@ -731,7 +732,7 @@ fn parse_primitive_value(lex: &mut Lexer<MatcherDefinitionToken>, _v: &str) -> a
// remaining pattern if it is a decimal
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
let _ = lex.next().ok_or_else(|| anyhow!("expected a number"))?;
let _ = lex.next().ok_or_else(|| end_of_expression(v, "expected a number"))?;
Ok((format!("{}{}", int_part, lex.slice()), ValueType::Decimal))
} else {
Ok((lex.slice().to_string(), ValueType::Integer))
Expand All @@ -742,81 +743,81 @@ fn parse_primitive_value(lex: &mut Lexer<MatcherDefinitionToken>, _v: &str) -> a
// remaining pattern if it is a decimal
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
let _ = lex.next().ok_or_else(|| anyhow!("expected a number"))?;
let _ = lex.next().ok_or_else(|| end_of_expression(v, "expected a number"))?;
Ok((format!("{}{}", int_part, lex.slice()), ValueType::Decimal))
} else {
Ok((lex.slice().to_string(), ValueType::Integer))
}
},
Ok(MatcherDefinitionToken::Decimal) => Ok((lex.slice().to_string(), ValueType::Decimal)),
Ok(MatcherDefinitionToken::Boolean) => Ok((lex.slice().to_string(), ValueType::Boolean)),
_ => Err(anyhow!("expected a primitive value, got '{}'", lex.slice()))
_ => Err(anyhow!(error_message(lex, v, "Expected a primitive value", "Expected a primitive value here")?))
}
}

// COMMA val=( DECIMAL_LITERAL | INTEGER_LITERAL ) { $value = $val.getText(); $type = ValueType.Number; }
#[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"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "expected a number"))?;
if let Ok(MatcherDefinitionToken::Decimal) = next {
Ok((lex.slice().to_string(), ValueType::Number, Some(MatchingRule::Number), None, None))
} else if let Ok(MatcherDefinitionToken::Int(_) | MatcherDefinitionToken::Num(_)) = next {
// Logos is returning an INT token when a Decimal should match. We need to now parse the
// remaining pattern if it is a decimal
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
let _ = lex.next().ok_or_else(|| anyhow!("expected a number"))?;
let _ = lex.next().ok_or_else(|| end_of_expression(v, "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()))
Err(anyhow!(error_message(lex, v, "Expected a number", "Expected a number here")?))
}
}

// COMMA val=INTEGER_LITERAL { $value = $val.getText(); $type = ValueType.Integer; }
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"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "expected an integer"))?;
if let Ok(MatcherDefinitionToken::Int(_) | MatcherDefinitionToken::Num(_)) = next {
Ok((lex.slice().to_string(), ValueType::Integer, Some(MatchingRule::Integer), None, None))
} else {
Err(anyhow!("expected an integer, got '{}'", lex.slice()))
Err(anyhow!(error_message(lex, v, "Expected an integer", "Expected an integer here")?))
}
}

// COMMA val=DECIMAL_LITERAL { $value = $val.getText(); $type = ValueType.Decimal; }
#[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"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "expected a decimal number"))?;
if let Ok(MatcherDefinitionToken::Int(_) | MatcherDefinitionToken::Num(_)) = next {
// Logos is returning an INT token when a Decimal should match. We need to now parse the
// remaining pattern if it is a decimal
if lex.remainder().starts_with('.') {
let int_part = lex.slice();
let _ = lex.next().ok_or_else(|| anyhow!("expected a number"))?;
let _ = lex.next().ok_or_else(|| end_of_expression(v, "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 let Ok(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()))
Err(anyhow!(error_message(lex, v, "Expected a decimal number", "Expected a decimal number here")?))
}
}

// COMMA BOOLEAN_LITERAL { $rule = BooleanMatcher.INSTANCE; $value = $BOOLEAN_LITERAL.getText(); $type = ValueType.Boolean; }
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"))?;
let next = lex.next().ok_or_else(|| end_of_expression(v, "expected a boolean"))?;
if let Ok(MatcherDefinitionToken::Boolean) = next {
Ok((lex.slice().to_string(), ValueType::Boolean, Some(MatchingRule::Boolean), None, None))
} else {
Err(anyhow!("expected a boolean, got '{}'", lex.slice()))
Err(anyhow!(error_message(lex, v, "Expected a boolean", "Expected a boolean here")?))
}
}

Expand Down

0 comments on commit 2ca9a9a

Please sign in to comment.