Skip to content

Commit 111ae79

Browse files
committed
fix: Implemented missing atLeast and atMost options with matching rule definitions
1 parent 936cd34 commit 111ae79

File tree

2 files changed

+114
-20
lines changed

2 files changed

+114
-20
lines changed

core/model/src/main/kotlin/au/com/dius/pact/core/model/matchingrules/expressions/MatcherDefinition.kt

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import au.com.dius.pact.core.model.matchingrules.EachValueMatcher
99
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher
1010
import au.com.dius.pact.core.model.matchingrules.IncludeMatcher
1111
import au.com.dius.pact.core.model.matchingrules.MatchingRule
12+
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
13+
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
1214
import au.com.dius.pact.core.model.matchingrules.NotEmptyMatcher
1315
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
1416
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
@@ -26,10 +28,13 @@ class MatcherDefinitionLexer(expression: String): StringLexer(expression) {
2628

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

31+
fun matchWholeNumber() = matchRegex(NUMBER_LITERAL).isNotEmpty()
32+
2933
fun matchBoolean() = matchRegex(BOOLEAN_LITERAL).isNotEmpty()
3034

3135
companion object {
3236
val INTEGER_LITERAL = Regex("^-?\\d+")
37+
val NUMBER_LITERAL = Regex("^\\d+")
3338
val DECIMAL_LITERAL = Regex("^-?\\d+\\.\\d+")
3439
val BOOLEAN_LITERAL = Regex("^(true|false)")
3540
}
@@ -90,23 +95,15 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
9095

9196
// matchingDefinitionExp returns [ MatchingRuleDefinition value ] :
9297
// (
93-
// 'matching' LEFT_BRACKET matchingRule RIGHT_BRACKET {
94-
// if ($matchingRule.reference != null) {
95-
// $value = new MatchingRuleDefinition($matchingRule.value, $matchingRule.reference, $matchingRule.generator);
96-
// } else {
97-
// $value = new MatchingRuleDefinition($matchingRule.value, $matchingRule.rule, $matchingRule.generator);
98-
// }
99-
// }
100-
// | 'notEmpty' LEFT_BRACKET primitiveValue RIGHT_BRACKET { $value = new MatchingRuleDefinition($primitiveValue.value, NotEmptyMatcher.INSTANCE, null).withType($primitiveValue.type); }
101-
// | 'eachKey' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET { if ($e.value != null) { $value = new MatchingRuleDefinition(null, new EachKeyMatcher($e.value), null); } }
102-
// | 'eachValue' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET {
103-
// if ($e.value != null) {
104-
// $value = new MatchingRuleDefinition(null, ValueType.Unknown, List.of((Either<MatchingRule, MatchingReference>) new Either.A(new EachValueMatcher($e.value))), null);
105-
// }
106-
// }
98+
// 'matching' LEFT_BRACKET matchingRule RIGHT_BRACKET
99+
// | 'notEmpty' LEFT_BRACKET primitiveValue RIGHT_BRACKET
100+
// | 'eachKey' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET
101+
// | 'eachValue' LEFT_BRACKET e=matchingDefinitionExp RIGHT_BRACKET
102+
// | 'atLeast' LEFT_BRACKET DIGIT+ RIGHT_BRACKET
103+
// | 'atMost' LEFT_BRACKET DIGIT+ RIGHT_BRACKET
107104
// )
108105
// ;
109-
@Suppress("ReturnCount")
106+
@Suppress("ReturnCount", "LongMethod")
110107
fun matchingDefinitionExp(): Result<MatchingRuleDefinition, String> {
111108
return when {
112109
lexer.matchString("matching") -> {
@@ -189,6 +186,38 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
189186
Result.Err("Was expecting a '(' at index ${lexer.index}")
190187
}
191188
}
189+
lexer.matchString("atLeast") -> {
190+
if (matchChar('(')) {
191+
when (val lengthResult = unsignedNumber()) {
192+
is Result.Ok -> {
193+
if (matchChar(')')) {
194+
Result.Ok(MatchingRuleDefinition("", MinTypeMatcher(lengthResult.value), null))
195+
} else {
196+
Result.Err("Was expecting a ')' at index ${lexer.index}")
197+
}
198+
}
199+
is Result.Err -> return lengthResult
200+
}
201+
} else {
202+
Result.Err("Was expecting a '(' at index ${lexer.index}")
203+
}
204+
}
205+
lexer.matchString("atMost") -> {
206+
if (matchChar('(')) {
207+
when (val lengthResult = unsignedNumber()) {
208+
is Result.Ok -> {
209+
if (matchChar(')')) {
210+
Result.Ok(MatchingRuleDefinition("", MaxTypeMatcher(lengthResult.value), null))
211+
} else {
212+
Result.Err("Was expecting a ')' at index ${lexer.index}")
213+
}
214+
}
215+
is Result.Err -> return lengthResult
216+
}
217+
} else {
218+
Result.Err("Was expecting a '(' at index ${lexer.index}")
219+
}
220+
}
192221
else -> Result.Err("Was expecting a matching rule definition type at index ${lexer.index}")
193222
}
194223
}
@@ -346,6 +375,15 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
346375
Result.Err("Was expecting a ',' at index ${lexer.index}")
347376
}
348377

378+
private fun unsignedNumber(): Result<Int, String> {
379+
lexer.skipWhitespace()
380+
return if (lexer.matchWholeNumber()) {
381+
Result.Ok(lexer.lastMatch!!.toInt())
382+
} else {
383+
Result.Err("Was expecting an unsigned number at index ${lexer.index}")
384+
}
385+
}
386+
349387
private fun matchEqualOrType(equalTo: Boolean) = if (matchChar(',')) {
350388
when (val primitiveValueResult = primitiveValue()) {
351389
is Result.Ok -> {
@@ -435,10 +473,10 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
435473
}
436474

437475
// primitiveValue returns [ String value, ValueType type ] :
438-
// string { $value = $string.contents; $type = ValueType.String; }
439-
// | v=DECIMAL_LITERAL { $value = $v.getText(); $type = ValueType.Decimal; }
440-
// | v=INTEGER_LITERAL { $value = $v.getText(); $type = ValueType.Integer; }
441-
// | v=BOOLEAN_LITERAL { $value = $v.getText(); $type = ValueType.Boolean; }
476+
// string
477+
// | v=DECIMAL_LITERAL
478+
// | v=INTEGER_LITERAL
479+
// | v=BOOLEAN_LITERAL
442480
// ;
443481
fun primitiveValue(): Result<Pair<String?, ValueType>, String> {
444482
lexer.skipWhitespace()
@@ -490,7 +528,7 @@ class MatcherDefinitionParser(private val lexer: MatcherDefinitionLexer) {
490528
}
491529
}
492530

493-
@Suppress("ComplexMethod")
531+
@Suppress("ComplexMethod", "LongMethod")
494532
fun processRawString(rawString: String): Result<String, String> {
495533
val buffer = StringBuilder(rawString.length)
496534
val chars = rawString.chars().iterator()

core/model/src/test/groovy/au/com/dius/pact/core/model/matchingrules/expressions/MatchingDefinitionParserSpec.groovy

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import au.com.dius.pact.core.model.matchingrules.DateMatcher
55
import au.com.dius.pact.core.model.matchingrules.EachKeyMatcher
66
import au.com.dius.pact.core.model.matchingrules.EachValueMatcher
77
import au.com.dius.pact.core.model.matchingrules.IncludeMatcher
8+
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher
9+
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
810
import au.com.dius.pact.core.model.matchingrules.NotEmptyMatcher
911
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher
1012
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
@@ -216,4 +218,58 @@ class MatchingDefinitionParserSpec extends Specification {
216218
parser.processRawString('\\u000') instanceof Result.Err
217219
parser.processRawString('\\u{000') instanceof Result.Err
218220
}
221+
222+
def 'parse atLeast matcher'() {
223+
expect:
224+
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).value ==
225+
new MatchingRuleDefinition("", ValueType.Unknown, [ Either.a(new MinTypeMatcher(value)) ], null)
226+
227+
where:
228+
229+
expression | value
230+
"atLeast(100)" | 100
231+
"atLeast( 22 )" | 22
232+
}
233+
234+
def 'invalid atLeast matcher'() {
235+
expect:
236+
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).errorValue() == error
237+
238+
where:
239+
240+
expression | error
241+
'atLeast' | 'Error parsing expression: Was expecting a \'(\' at index 7'
242+
'atLeast(' | 'Error parsing expression: Was expecting an unsigned number at index 8'
243+
'atLeast()' | 'Error parsing expression: Was expecting an unsigned number at index 8'
244+
'atLeast(100' | 'Error parsing expression: Was expecting a \')\' at index 11'
245+
'atLeast(-10)' | 'Error parsing expression: Was expecting an unsigned number at index 8'
246+
'atLeast(0.1)' | 'Error parsing expression: Was expecting a \')\' at index 9'
247+
}
248+
249+
def 'parse atMost matcher'() {
250+
expect:
251+
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).value ==
252+
new MatchingRuleDefinition("", ValueType.Unknown, [ Either.a(new MaxTypeMatcher(value)) ], null)
253+
254+
where:
255+
256+
expression | value
257+
"atMost(100)" | 100
258+
"atMost( 22 )" | 22
259+
}
260+
261+
def 'invalid atMost matcher'() {
262+
expect:
263+
MatchingRuleDefinition.parseMatchingRuleDefinition(expression).errorValue() == error
264+
265+
where:
266+
267+
expression | error
268+
'atMost' | 'Error parsing expression: Was expecting a \'(\' at index 6'
269+
'atMost(' | 'Error parsing expression: Was expecting an unsigned number at index 7'
270+
'atMost()' | 'Error parsing expression: Was expecting an unsigned number at index 7'
271+
'atMost(100' | 'Error parsing expression: Was expecting a \')\' at index 10'
272+
'atMost(-10)' | 'Error parsing expression: Was expecting an unsigned number at index 7'
273+
'atMost(0.1)' | 'Error parsing expression: Was expecting a \')\' at index 8'
274+
}
219275
}

0 commit comments

Comments
 (0)