Skip to content

Commit

Permalink
feat: Add DSL methods to handle matching each key and value in a JSON…
Browse files Browse the repository at this point in the history
… object #1813
  • Loading branch information
rholshausen committed Aug 7, 2024
1 parent 3030270 commit 2bb8eef
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package au.com.dius.pact.consumer.junit5;

import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue;
import au.com.dius.pact.consumer.dsl.Matchers;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.model.V4Pact;
Expand Down Expand Up @@ -32,8 +32,8 @@ public V4Pact createFragment(PactDslWithProvider builder) {
.method("POST")
.body(newJsonBody(body ->
body.object("a", aObj -> {
aObj.eachKeyLike("prop1", PactDslJsonRootValue.stringMatcher("prop\\d+", "prop1"));
aObj.eachKeyLike("prop1", propObj -> propObj.stringType("value", "x"));
aObj.eachKeyMatching(Matchers.regexp("prop\\d+", "prop1"));
aObj.eachValueMatching("prop1", propObj -> propObj.stringType("value", "x"));
})).build())
.willRespondWith()
.status(200)
Expand All @@ -47,7 +47,7 @@ void runTest(MockServer mockServer) throws IOException {
" \"prop1\": {\n" +
" \"value\": \"x\"\n" +
" },\n" +
" \"prop\": {\n" +
" \"prop2\": {\n" +
" \"value\": \"y\"\n" +
" }\n" +
" }\n" +
Expand All @@ -57,5 +57,22 @@ void runTest(MockServer mockServer) throws IOException {
.execute()
.returnResponse();
assertThat(httpResponse.getCode(), is(200));

// This should make the test fail
// String json2 = "{\n" +
// " \"a\": {\n" +
// " \"prop1\": {\n" +
// " \"value\": \"x\"\n" +
// " },\n" +
// " \"prop\": {\n" +
// " \"value\": \"y\"\n" +
// " }\n" +
// " }\n" +
// "}";
// ClassicHttpResponse httpResponse2 = (ClassicHttpResponse) Request.post(mockServer.getUrl())
// .body(new StringEntity(json2, ContentType.APPLICATION_JSON))
// .execute()
// .returnResponse();
// assertThat(httpResponse2.getCode(), is(500));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1143,4 +1143,26 @@ public LambdaDslObject arrayContaining(String name, Consumer<LambdaDslJsonArray>
arrayContaining.closeArray();
return this;
}

/**
* Configures a matching rule for each key in the object.
* @param matcher Matcher to apply to each key
*/
public LambdaDslObject eachKeyMatching(Matcher matcher) {
object.eachKeyMatching(matcher);
return this;
}

/**
* Configures a matching rule for each value in the object, ignoring the keys.
* @param exampleKey Example key to use in the consumer test.
* @param nestedObject Nested object to match each value to.
*/
public LambdaDslObject eachValueMatching(String exampleKey, final Consumer<LambdaDslObject> nestedObject) {
final PactDslJsonBody objectLike = object.eachValueMatching(exampleKey);
final LambdaDslObject dslObject = new LambdaDslObject(objectLike);
nestedObject.accept(dslObject);
objectLike.closeObject();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import au.com.dius.pact.core.model.generators.RandomStringGenerator
import au.com.dius.pact.core.model.generators.RegexGenerator
import au.com.dius.pact.core.model.generators.TimeGenerator
import au.com.dius.pact.core.model.generators.UuidGenerator
import au.com.dius.pact.core.model.matchingrules.EachKeyMatcher
import au.com.dius.pact.core.model.matchingrules.EqualsIgnoreOrderMatcher
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRule
Expand All @@ -28,6 +29,7 @@ import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.model.matchingrules.ValuesMatcher
import au.com.dius.pact.core.model.matchingrules.expressions.MatchingRuleDefinition
import au.com.dius.pact.core.support.Json.toJson
import au.com.dius.pact.core.support.expressions.DataType.Companion.from
import au.com.dius.pact.core.support.json.JsonValue
Expand Down Expand Up @@ -2231,4 +2233,30 @@ open class PactDslJsonBody : DslPart {
else -> body
}
}

/**
* Applies a matching rule to each key in the object, ignoring the values.
*/
fun eachKeyMatching(matcher: Matcher): PactDslJsonBody {
val path = if (rootPath.endsWith(".")) rootPath.substring(0, rootPath.length - 1) else rootPath
val value = matcher.value.toString()
if (matcher.matcher != null) {
matchers.addRule(path, EachKeyMatcher(MatchingRuleDefinition(value, matcher.matcher!!, matcher.generator)))
}
if (!body.has(value)) {
when (val body = body) {
is JsonValue.Object -> body.add(value, JsonValue.Null)
else -> {}
}
}
return this
}

/**
* Applies matching rules to each value in the object, ignoring the keys.
*/
fun eachValueMatching(exampleKey: String): PactDslJsonBody {
val path = constructValidPath("*", rootPath)
return PactDslJsonBody("$path.", exampleKey, this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package au.com.dius.pact.consumer.dsl

import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.matchingrules.ArrayContainsMatcher
import au.com.dius.pact.core.model.matchingrules.EachKeyMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher
Expand All @@ -10,6 +11,7 @@ import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import au.com.dius.pact.core.model.matchingrules.RuleLogic
import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.model.matchingrules.ValuesMatcher
import au.com.dius.pact.core.model.matchingrules.expressions.MatchingRuleDefinition
import kotlin.Triple
import spock.lang.Issue
import spock.lang.Specification
Expand Down Expand Up @@ -435,4 +437,38 @@ class PactDslJsonBodySpec extends Specification {
new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER),
new RegexMatcher('\\d{5}', '90210')])
}

@Issue('#1813')
def 'matching each key'() {
when:
PactDslJsonBody body = new PactDslJsonBody()
.object('test')
.eachKeyMatching(Matchers.regexp('\\d+\\.\\d{2}', '2.01'))
.closeObject()
body.closeObject()

then:
body.toString() == '{"test":{"2.01":null}}'
body.matchers.matchingRules.keySet() == ['$.test'] as Set
body.matchers.matchingRules['$.test'] == new MatchingRuleGroup([
new EachKeyMatcher(new MatchingRuleDefinition('2.01', new RegexMatcher('\\d+\\.\\d{2}', '2.01'), null))
])
}

@Issue('#1813')
def 'matching each value'() {
when:
PactDslJsonBody body = new PactDslJsonBody()
.eachValueMatching('prop1')
.stringType('value', 'x')
.closeObject()
body.closeObject()

then:
body.toString() == '{"prop1":{"value":"x"}}'
body.matchers.matchingRules.keySet() == ['$.*.value'] as Set
body.matchers.matchingRules['$.*.value'] == new MatchingRuleGroup([
TypeMatcher.INSTANCE
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ fun constructValidPath(segment: String, rootPath: String): String {
val root = StringUtils.stripEnd(rootPath, ".")
if (segment.all { it.isDigit() }) {
"$root[$segment]"
} else if (segment.any { !validPathCharacter(it) }) {
} else if (segment != "*" && segment.any { !validPathCharacter(it) }) {
"$root['$segment']"
} else {
"$root.$segment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class PathExpressionsSpec extends Specification {
'a$' | 'a.b' || "a.b['a\$']"
'a b' | 'a.b' || "a.b['a b']"
'$a.b' | 'a.b' || "a.b['\$a.b']"
'*' | 'a.b' || 'a.b.*'
}

def 'construct path from tokens'() {
Expand Down

0 comments on commit 2bb8eef

Please sign in to comment.