Skip to content

Commit

Permalink
fix: Implemented missing atLeast and atMost options with matching rul…
Browse files Browse the repository at this point in the history
…e definitions
  • Loading branch information
rholshausen committed Jan 30, 2024
1 parent 936cd34 commit 111ae79
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import au.com.dius.pact.core.model.matchingrules.EachValueMatcher
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher
import au.com.dius.pact.core.model.matchingrules.IncludeMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRule
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NotEmptyMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
Expand All @@ -26,10 +28,13 @@ class MatcherDefinitionLexer(expression: String): StringLexer(expression) {

fun matchInteger() = matchRegex(INTEGER_LITERAL).isNotEmpty()

fun matchWholeNumber() = matchRegex(NUMBER_LITERAL).isNotEmpty()

fun matchBoolean() = matchRegex(BOOLEAN_LITERAL).isNotEmpty()

companion object {
val INTEGER_LITERAL = Regex("^-?\\d+")
val NUMBER_LITERAL = Regex("^\\d+")
val DECIMAL_LITERAL = Regex("^-?\\d+\\.\\d+")
val BOOLEAN_LITERAL = Regex("^(true|false)")
}
Expand Down Expand Up @@ -90,23 +95,15 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {

// matchingDefinitionExp returns [ MatchingRuleDefinition value ] :
// (
// 'matching' LEFT_BRACKET matchingRule RIGHT_BRACKET {
// if ($matchingRule.reference != null) {
// $value = new MatchingRuleDefinition($matchingRule.value, $matchingRule.reference, $matchingRule.generator);
// } else {
// $value = new MatchingRuleDefinition($matchingRule.value, $matchingRule.rule, $matchingRule.generator);
// }
// }
// | 'notEmpty' LEFT_BRACKET primitiveValue RIGHT_BRACKET { $value = new MatchingRuleDefinition($primitiveValue.value, NotEmptyMatcher.INSTANCE, null).withType($primitiveValue.type); }
// | 'eachKey' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET { if ($e.value != null) { $value = new MatchingRuleDefinition(null, new EachKeyMatcher($e.value), null); } }
// | 'eachValue' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET {
// if ($e.value != null) {
// $value = new MatchingRuleDefinition(null, ValueType.Unknown, List.of((Either<MatchingRule, MatchingReference>) new Either.A(new EachValueMatcher($e.value))), null);
// }
// }
// 'matching' LEFT_BRACKET matchingRule RIGHT_BRACKET
// | 'notEmpty' LEFT_BRACKET primitiveValue RIGHT_BRACKET
// | 'eachKey' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET
// | 'eachValue' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET
// | 'atLeast' LEFT_BRACKET DIGIT+ RIGHT_BRACKET
// | 'atMost' LEFT_BRACKET DIGIT+ RIGHT_BRACKET
// )
// ;
@Suppress("ReturnCount")
@Suppress("ReturnCount", "LongMethod")
fun matchingDefinitionExp(): Result<MatchingRuleDefinition, String> {
return when {
lexer.matchString("matching") -> {
Expand Down Expand Up @@ -189,6 +186,38 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
Result.Err("Was expecting a '(' at index ${lexer.index}")
}
}
lexer.matchString("atLeast") -> {
if (matchChar('(')) {
when (val lengthResult = unsignedNumber()) {
is Result.Ok -> {
if (matchChar(')')) {
Result.Ok(MatchingRuleDefinition("", MinTypeMatcher(lengthResult.value), null))
} else {
Result.Err("Was expecting a ')' at index ${lexer.index}")
}
}
is Result.Err -> return lengthResult
}
} else {
Result.Err("Was expecting a '(' at index ${lexer.index}")
}
}
lexer.matchString("atMost") -> {
if (matchChar('(')) {
when (val lengthResult = unsignedNumber()) {
is Result.Ok -> {
if (matchChar(')')) {
Result.Ok(MatchingRuleDefinition("", MaxTypeMatcher(lengthResult.value), null))
} else {
Result.Err("Was expecting a ')' at index ${lexer.index}")
}
}
is Result.Err -> return lengthResult
}
} else {
Result.Err("Was expecting a '(' at index ${lexer.index}")
}
}
else -> Result.Err("Was expecting a matching rule definition type at index ${lexer.index}")
}
}
Expand Down Expand Up @@ -346,6 +375,15 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
Result.Err("Was expecting a ',' at index ${lexer.index}")
}

private fun unsignedNumber(): Result<Int, String> {
lexer.skipWhitespace()
return if (lexer.matchWholeNumber()) {
Result.Ok(lexer.lastMatch!!.toInt())
} else {
Result.Err("Was expecting an unsigned number at index ${lexer.index}")
}
}

private fun matchEqualOrType(equalTo: Boolean) = if (matchChar(',')) {
when (val primitiveValueResult = primitiveValue()) {
is Result.Ok -> {
Expand Down Expand Up @@ -435,10 +473,10 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
}

// primitiveValue returns [ String value, ValueType type ] :
// string { $value = $string.contents; $type = ValueType.String; }
// | v=DECIMAL_LITERAL { $value = $v.getText(); $type = ValueType.Decimal; }
// | v=INTEGER_LITERAL { $value = $v.getText(); $type = ValueType.Integer; }
// | v=BOOLEAN_LITERAL { $value = $v.getText(); $type = ValueType.Boolean; }
// string
// | v=DECIMAL_LITERAL
// | v=INTEGER_LITERAL
// | v=BOOLEAN_LITERAL
// ;
fun primitiveValue(): Result<Pair<String?, ValueType>, String> {
lexer.skipWhitespace()
Expand Down Expand Up @@ -490,7 +528,7 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
}
}

@Suppress("ComplexMethod")
@Suppress("ComplexMethod", "LongMethod")
fun processRawString(rawString: String): Result<String, String> {
val buffer = StringBuilder(rawString.length)
val chars = rawString.chars().iterator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.EachKeyMatcher
import au.com.dius.pact.core.model.matchingrules.EachValueMatcher
import au.com.dius.pact.core.model.matchingrules.IncludeMatcher
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
import au.com.dius.pact.core.model.matchingrules.NotEmptyMatcher
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
Expand Down Expand Up @@ -216,4 +218,58 @@ class MatchingDefinitionParserSpec extends Specification {
parser.processRawString('\\u000') instanceof Result.Err
parser.processRawString('\\u{000') instanceof Result.Err
}

def 'parse atLeast matcher'() {
expect:
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).value ==
new MatchingRuleDefinition("", ValueType.Unknown, [ Either.a(new MinTypeMatcher(value)) ], null)

where:

expression | value
"atLeast(100)" | 100
"atLeast( 22 )" | 22
}

def 'invalid atLeast matcher'() {
expect:
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).errorValue() == error

where:

expression | error
'atLeast' | 'Error parsing expression: Was expecting a \'(\' at index 7'
'atLeast(' | 'Error parsing expression: Was expecting an unsigned number at index 8'
'atLeast()' | 'Error parsing expression: Was expecting an unsigned number at index 8'
'atLeast(100' | 'Error parsing expression: Was expecting a \')\' at index 11'
'atLeast(-10)' | 'Error parsing expression: Was expecting an unsigned number at index 8'
'atLeast(0.1)' | 'Error parsing expression: Was expecting a \')\' at index 9'
}

def 'parse atMost matcher'() {
expect:
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).value ==
new MatchingRuleDefinition("", ValueType.Unknown, [ Either.a(new MaxTypeMatcher(value)) ], null)

where:

expression | value
"atMost(100)" | 100
"atMost( 22 )" | 22
}

def 'invalid atMost matcher'() {
expect:
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).errorValue() == error

where:

expression | error
'atMost' | 'Error parsing expression: Was expecting a \'(\' at index 6'
'atMost(' | 'Error parsing expression: Was expecting an unsigned number at index 7'
'atMost()' | 'Error parsing expression: Was expecting an unsigned number at index 7'
'atMost(100' | 'Error parsing expression: Was expecting a \')\' at index 10'
'atMost(-10)' | 'Error parsing expression: Was expecting an unsigned number at index 7'
'atMost(0.1)' | 'Error parsing expression: Was expecting a \')\' at index 8'
}
}

0 comments on commit 111ae79

Please sign in to comment.