Skip to content

Commit d7d3030

Browse files
committed
fix: Matching rule paths for fields with only digits should not be written as indices #1851
1 parent 5dba442 commit d7d3030

File tree

5 files changed

+66
-39
lines changed

5 files changed

+66
-39
lines changed

Diff for: consumer/junit/src/test/java/au/com/dius/pact/consumer/junit/PactDslJsonBodyTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ protected RequestResponsePact createPact(PactDslWithProvider builder) {
5454
"$.numbers[4].id",
5555
"$.numbers[4].timestamp",
5656
"$.numbers[4].date_of_birth",
57-
"$[2].id",
58-
"$[2].v1"
57+
"$.2.id",
58+
"$.2.v1"
5959
);
6060

6161
return pact;

Diff for: consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/PactDslJsonBody.kt

+34-34
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import java.util.regex.Pattern
5353
*/
5454
@Suppress("LargeClass", "TooManyFunctions", "SpreadOperator")
5555
open class PactDslJsonBody : DslPart {
56-
override var body: JsonValue
56+
override var body: JsonValue = JsonValue.Object()
5757

5858
/**
5959
* Constructs a new body as a root
@@ -624,7 +624,7 @@ open class PactDslJsonBody : DslPart {
624624
else -> {}
625625
}
626626

627-
matchers.addRules(constructValidPath(name, rootPath), listOf(
627+
matchers.addRules(constructValidPath(name, rootPath, false), listOf(
628628
NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER),
629629
RegexMatcher(regex, example.toString())
630630
))
@@ -654,7 +654,7 @@ open class PactDslJsonBody : DslPart {
654654
else -> {}
655655
}
656656

657-
matchers.addRules(constructValidPath(name, rootPath), listOf(
657+
matchers.addRules(constructValidPath(name, rootPath, false), listOf(
658658
NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL),
659659
RegexMatcher(regex, example.toString())
660660
))
@@ -683,7 +683,7 @@ open class PactDslJsonBody : DslPart {
683683
else -> {}
684684
}
685685

686-
matchers.addRules(constructValidPath(name, rootPath), listOf(
686+
matchers.addRules(constructValidPath(name, rootPath, false), listOf(
687687
NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER),
688688
RegexMatcher(regex, example.toString())
689689
))
@@ -828,7 +828,7 @@ open class PactDslJsonBody : DslPart {
828828
* @param format datetime format
829829
*/
830830
fun datetime(name: String, format: String): PactDslJsonBody {
831-
val path = constructValidPath(name, rootPath)
831+
val path = constructValidPath(name, rootPath, false)
832832
generators.addGenerator(Category.BODY, path, DateTimeGenerator(format, null))
833833
val formatter = DateTimeFormatter.ofPattern(format).withZone(ZoneId.systemDefault())
834834
matchers.addRule(path, matchTimestamp(format))
@@ -887,7 +887,7 @@ open class PactDslJsonBody : DslPart {
887887
}
888888

889889
val formatter = DateTimeFormatter.ofPattern(format).withZone(timeZone.toZoneId())
890-
matchers.addRule(constructValidPath(name, rootPath), matchTimestamp(format))
890+
matchers.addRule(constructValidPath(name, rootPath, false), matchTimestamp(format))
891891

892892
when (val body = body) {
893893
is JsonValue.Object -> body.add(name,
@@ -943,7 +943,7 @@ open class PactDslJsonBody : DslPart {
943943
}
944944

945945
val formatter = DateTimeFormatter.ofPattern(format).withZone(timeZone.toZoneId())
946-
matchers.addRule(constructValidPath(name, rootPath), matchTimestamp(format))
946+
matchers.addRule(constructValidPath(name, rootPath, false), matchTimestamp(format))
947947

948948
when (val body = body) {
949949
is JsonValue.Object -> body.add(name,
@@ -966,7 +966,7 @@ open class PactDslJsonBody : DslPart {
966966
@JvmOverloads
967967
fun date(name: String = "date"): PactDslJsonBody {
968968
val pattern = DateFormatUtils.ISO_DATE_FORMAT.pattern
969-
val path = constructValidPath(name, rootPath)
969+
val path = constructValidPath(name, rootPath, false)
970970
generators.addGenerator(Category.BODY, path, DateGenerator(pattern, null))
971971
matchers.addRule(path, matchDate(pattern))
972972

@@ -990,7 +990,7 @@ open class PactDslJsonBody : DslPart {
990990
* @param format date format to match
991991
*/
992992
fun date(name: String, format: String): PactDslJsonBody {
993-
val path = constructValidPath(name, rootPath)
993+
val path = constructValidPath(name, rootPath, false)
994994
generators.addGenerator(Category.BODY, path, DateGenerator(format, null))
995995
val instance = FastDateFormat.getInstance(format)
996996
matchers.addRule(path, matchDate(format))
@@ -1045,7 +1045,7 @@ open class PactDslJsonBody : DslPart {
10451045
}
10461046

10471047
val instance = FastDateFormat.getInstance(format, timeZone)
1048-
matchers.addRule(constructValidPath(name, rootPath), matchDate(format))
1048+
matchers.addRule(constructValidPath(name, rootPath, false), matchDate(format))
10491049

10501050
when (val body = body) {
10511051
is JsonValue.Object -> body.add(name, JsonValue.StringValue(instance.format(examples[0]).toCharArray()))
@@ -1077,7 +1077,7 @@ open class PactDslJsonBody : DslPart {
10771077
}
10781078

10791079
val formatter = DateTimeFormatter.ofPattern(format)
1080-
matchers.addRule(constructValidPath(name, rootPath), matchDate(format))
1080+
matchers.addRule(constructValidPath(name, rootPath, false), matchDate(format))
10811081

10821082
when (val body = body) {
10831083
is JsonValue.Object -> body.add(name, JsonValue.StringValue(formatter.format(examples[0]).toCharArray()))
@@ -1102,7 +1102,7 @@ open class PactDslJsonBody : DslPart {
11021102
@JvmOverloads
11031103
fun time(name: String = "time"): PactDslJsonBody {
11041104
val pattern = DateFormatUtils.ISO_TIME_FORMAT.pattern
1105-
val path = constructValidPath(name, rootPath)
1105+
val path = constructValidPath(name, rootPath, false)
11061106
generators.addGenerator(Category.BODY, path, TimeGenerator(pattern, null))
11071107
matchers.addRule(path, matchTime(pattern))
11081108

@@ -1126,7 +1126,7 @@ open class PactDslJsonBody : DslPart {
11261126
* @param format time format to match
11271127
*/
11281128
fun time(name: String, format: String): PactDslJsonBody {
1129-
val path = constructValidPath(name, rootPath)
1129+
val path = constructValidPath(name, rootPath, false)
11301130
generators.addGenerator(Category.BODY, path, TimeGenerator(format, null))
11311131
matchers.addRule(path, matchTime(format))
11321132

@@ -1183,7 +1183,7 @@ open class PactDslJsonBody : DslPart {
11831183
}
11841184

11851185
val instance = FastDateFormat.getInstance(format, timeZone)
1186-
matchers.addRule(constructValidPath(name, rootPath), matchTime(format))
1186+
matchers.addRule(constructValidPath(name, rootPath, false), matchTime(format))
11871187

11881188
when (val body = body) {
11891189
is JsonValue.Object -> body.add(name, JsonValue.StringValue(instance.format(examples[0]).toCharArray()))
@@ -1203,7 +1203,7 @@ open class PactDslJsonBody : DslPart {
12031203
* @param name attribute name
12041204
*/
12051205
fun ipAddress(name: String): PactDslJsonBody {
1206-
matchers.addRule(constructValidPath(name, rootPath), regexp("(\\d{1,3}\\.)+\\d{1,3}"))
1206+
matchers.addRule(constructValidPath(name, rootPath, false), regexp("(\\d{1,3}\\.)+\\d{1,3}"))
12071207

12081208
when (val body = body) {
12091209
is JsonValue.Object -> body.add(name, JsonValue.StringValue("127.0.0.1".toCharArray()))
@@ -1223,7 +1223,7 @@ open class PactDslJsonBody : DslPart {
12231223
* @param name field name
12241224
*/
12251225
override fun `object`(name: String): PactDslJsonBody {
1226-
return PactDslJsonBody(constructValidPath(name, rootPath) + ".", name, this)
1226+
return PactDslJsonBody(constructValidPath(name, rootPath, false) + ".", name, this)
12271227
}
12281228

12291229
override fun `object`(): PactDslJsonBody {
@@ -1236,7 +1236,7 @@ open class PactDslJsonBody : DslPart {
12361236
* @param value DSL Part to set the value as
12371237
*/
12381238
fun `object`(name: String, value: DslPart): PactDslJsonBody {
1239-
val base = constructValidPath(name, rootPath)
1239+
val base = constructValidPath(name, rootPath, false)
12401240
if (value is PactDslJsonBody) {
12411241
val obj = PactDslJsonBody(base, name, this, value)
12421242
putObjectPrivate(obj)
@@ -1350,7 +1350,7 @@ open class PactDslJsonBody : DslPart {
13501350
}
13511351

13521352
override fun eachLike(name: String, obj: DslPart): PactDslJsonBody {
1353-
val base = constructValidPath(name, rootPath)
1353+
val base = constructValidPath(name, rootPath, false)
13541354
matchers.addRule(base, TypeMatcher)
13551355
val parent = PactDslJsonArray(base, name, this, true)
13561356
if (obj is PactDslJsonBody) {
@@ -1375,7 +1375,7 @@ open class PactDslJsonBody : DslPart {
13751375
* @param numberExamples number of examples to generate
13761376
*/
13771377
override fun eachLike(name: String, numberExamples: Int): PactDslJsonBody {
1378-
val path = constructValidPath(name, rootPath)
1378+
val path = constructValidPath(name, rootPath, false)
13791379
matchers.addRule(path, TypeMatcher)
13801380
val parent = PactDslJsonArray(path, name, this, true)
13811381
parent.numberExamples = numberExamples
@@ -1394,7 +1394,7 @@ open class PactDslJsonBody : DslPart {
13941394
*/
13951395
@JvmOverloads
13961396
fun eachLike(name: String, value: PactDslJsonRootValue, numberExamples: Int = 1): PactDslJsonBody {
1397-
val path = constructValidPath(name, rootPath)
1397+
val path = constructValidPath(name, rootPath, false)
13981398
matchers.addRule(path, TypeMatcher)
13991399
val parent = PactDslJsonArray(path, name, this, true)
14001400
parent.numberExamples = numberExamples
@@ -1416,7 +1416,7 @@ open class PactDslJsonBody : DslPart {
14161416
}
14171417

14181418
override fun minArrayLike(name: String, size: Int, obj: DslPart): PactDslJsonBody {
1419-
val base = constructValidPath(name, rootPath)
1419+
val base = constructValidPath(name, rootPath, false)
14201420
matchers.addRule(base, matchMin(size))
14211421
val parent = PactDslJsonArray(base, name, this, true)
14221422
if (obj is PactDslJsonBody) {
@@ -1442,7 +1442,7 @@ open class PactDslJsonBody : DslPart {
14421442
String.format("Number of example %d is less than the minimum size of %d",
14431443
numberExamples, size)
14441444
}
1445-
val path = constructValidPath(name, rootPath)
1445+
val path = constructValidPath(name, rootPath, false)
14461446
matchers.addRule(path, matchMin(size))
14471447
val parent = PactDslJsonArray(path, name, this, true)
14481448
parent.numberExamples = numberExamples
@@ -1479,7 +1479,7 @@ open class PactDslJsonBody : DslPart {
14791479
String.format("Number of example %d is less than the minimum size of %d",
14801480
numberExamples, size)
14811481
}
1482-
val path = constructValidPath(name, rootPath)
1482+
val path = constructValidPath(name, rootPath, false)
14831483
matchers.addRule(path, matchMin(size))
14841484
val parent = PactDslJsonArray(path, name, this, true)
14851485
parent.numberExamples = numberExamples
@@ -1501,7 +1501,7 @@ open class PactDslJsonBody : DslPart {
15011501
}
15021502

15031503
override fun maxArrayLike(name: String, size: Int, obj: DslPart): PactDslJsonBody {
1504-
val base = constructValidPath(name, rootPath)
1504+
val base = constructValidPath(name, rootPath, false)
15051505
matchers.addRule(base, matchMax(size))
15061506
val parent = PactDslJsonArray(base, name, this, true)
15071507
if (obj is PactDslJsonBody) {
@@ -1527,7 +1527,7 @@ open class PactDslJsonBody : DslPart {
15271527
String.format("Number of example %d is more than the maximum size of %d",
15281528
numberExamples, size)
15291529
}
1530-
val path = constructValidPath(name, rootPath)
1530+
val path = constructValidPath(name, rootPath, false)
15311531
matchers.addRule(path, matchMax(size))
15321532
val parent = PactDslJsonArray(path, name, this, true)
15331533
parent.numberExamples = numberExamples
@@ -1564,7 +1564,7 @@ open class PactDslJsonBody : DslPart {
15641564
String.format("Number of example %d is more than the maximum size of %d",
15651565
numberExamples, size)
15661566
}
1567-
val path = constructValidPath(name, rootPath)
1567+
val path = constructValidPath(name, rootPath, false)
15681568
matchers.addRule(path, matchMax(size))
15691569
val parent = PactDslJsonArray(path, name, this, true)
15701570
parent.numberExamples = numberExamples
@@ -1578,7 +1578,7 @@ open class PactDslJsonBody : DslPart {
15781578
*/
15791579
@JvmOverloads
15801580
fun id(name: String = "id"): PactDslJsonBody {
1581-
val path = constructValidPath(name, rootPath)
1581+
val path = constructValidPath(name, rootPath, false)
15821582
generators.addGenerator(Category.BODY, path, RandomIntGenerator(0, Int.MAX_VALUE))
15831583
matchers.addRule(path, TypeMatcher)
15841584

@@ -1757,7 +1757,7 @@ open class PactDslJsonBody : DslPart {
17571757
}
17581758

17591759
override fun eachArrayLike(name: String, numberExamples: Int): PactDslJsonArray {
1760-
val path = constructValidPath(name, rootPath)
1760+
val path = constructValidPath(name, rootPath, false)
17611761
matchers.addRule(path, TypeMatcher)
17621762
val parent = PactDslJsonArray(path, name, this, true)
17631763
parent.numberExamples = numberExamples
@@ -1781,7 +1781,7 @@ open class PactDslJsonBody : DslPart {
17811781
String.format("Number of example %d is more than the maximum size of %d",
17821782
numberExamples, size)
17831783
}
1784-
val path = constructValidPath(name, rootPath)
1784+
val path = constructValidPath(name, rootPath, false)
17851785
matchers.addRule(path, matchMax(size))
17861786
val parent = PactDslJsonArray(path, name, this, true)
17871787
parent.numberExamples = numberExamples
@@ -1806,7 +1806,7 @@ open class PactDslJsonBody : DslPart {
18061806
String.format("Number of example %d is less than the minimum size of %d",
18071807
numberExamples, size)
18081808
}
1809-
val path = constructValidPath(name, rootPath)
1809+
val path = constructValidPath(name, rootPath, false)
18101810
matchers.addRule(path, matchMin(size))
18111811
val parent = PactDslJsonArray(path, name, this, true)
18121812
parent.numberExamples = numberExamples
@@ -2027,7 +2027,7 @@ open class PactDslJsonBody : DslPart {
20272027

20282028
override fun minMaxArrayLike(name: String, minSize: Int, maxSize: Int, obj: DslPart): PactDslJsonBody {
20292029
validateMinAndMaxAndExamples(minSize, maxSize, minSize)
2030-
val base = constructValidPath(name, rootPath)
2030+
val base = constructValidPath(name, rootPath, false)
20312031
matchers.addRule(base, matchMinMax(minSize, maxSize))
20322032
val parent = PactDslJsonArray(base, name, this, true)
20332033
if (obj is PactDslJsonBody) {
@@ -2049,7 +2049,7 @@ open class PactDslJsonBody : DslPart {
20492049

20502050
override fun minMaxArrayLike(name: String, minSize: Int, maxSize: Int, numberExamples: Int): PactDslJsonBody {
20512051
validateMinAndMaxAndExamples(minSize, maxSize, numberExamples)
2052-
val path = constructValidPath(name, rootPath)
2052+
val path = constructValidPath(name, rootPath, false)
20532053
matchers.addRule(path, matchMinMax(minSize, maxSize))
20542054
val parent = PactDslJsonArray(path, name, this, true)
20552055
parent.numberExamples = numberExamples
@@ -2092,7 +2092,7 @@ open class PactDslJsonBody : DslPart {
20922092
maxSize: Int
20932093
): PactDslJsonArray {
20942094
validateMinAndMaxAndExamples(minSize, maxSize, numberExamples)
2095-
val path = constructValidPath(name, rootPath)
2095+
val path = constructValidPath(name, rootPath, false)
20962096
matchers.addRule(path, matchMinMax(minSize, maxSize))
20972097
val parent = PactDslJsonArray(path, name, this, true)
20982098
parent.numberExamples = numberExamples
@@ -2140,7 +2140,7 @@ open class PactDslJsonBody : DslPart {
21402140
numberExamples: Int
21412141
): PactDslJsonBody {
21422142
validateMinAndMaxAndExamples(minSize, maxSize, numberExamples)
2143-
val path = constructValidPath(name, rootPath)
2143+
val path = constructValidPath(name, rootPath, false)
21442144
matchers.addRule(path, matchMinMax(minSize, maxSize))
21452145
val parent = PactDslJsonArray(path, name, this, true)
21462146
parent.numberExamples = numberExamples

Diff for: consumer/src/test/groovy/au/com/dius/pact/consumer/dsl/LambdaDslJsonBodySpec.groovy

+19
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,23 @@ class LambdaDslJsonBodySpec extends Specification {
115115
then:
116116
oldDsl.body.toString() == newDsl.body.toString()
117117
}
118+
119+
@Issue('#1851')
120+
def 'body with keys with only digits'() {
121+
when:
122+
def result = newJsonBody(o -> {
123+
o.object('1234567890', o2 -> {
124+
o2.eachLike('name', a -> {
125+
a.stringType('@class', 'Test')
126+
})
127+
})
128+
}).build()
129+
130+
then:
131+
result.body.toString() == '{"1234567890":{"name":[{"@class":"Test"}]}}'
132+
result.matchers.matchingRules == [
133+
"\$.1234567890.name": new MatchingRuleGroup([ TypeMatcher.INSTANCE ]),
134+
"\$.1234567890.name[*].@class": new MatchingRuleGroup([ TypeMatcher.INSTANCE ])
135+
]
136+
}
118137
}

Diff for: core/model/src/main/kotlin/au/com/dius/pact/core/model/PathExpressions.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,13 @@ fun parsePath(path: String): List<PathToken> {
202202
/**
203203
* This will combine the root path and the path segment to make a valid resulting path
204204
*/
205-
fun constructValidPath(segment: String, rootPath: String): String {
205+
fun constructValidPath(segment: String, rootPath: String, numbersAreIndices: Boolean = true): String {
206206
return when {
207207
rootPath.isEmpty() -> segment
208208
segment.isEmpty() -> rootPath
209209
else -> {
210210
val root = StringUtils.stripEnd(rootPath, ".")
211-
if (segment.all { it.isDigit() }) {
211+
if (numbersAreIndices && segment.all { it.isDigit() }) {
212212
"$root[$segment]"
213213
} else if (segment != "*" && segment.any { !validPathCharacter(it) }) {
214214
"$root['$segment']"

Diff for: core/model/src/test/groovy/au/com/dius/pact/core/model/PathExpressionsSpec.groovy

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package au.com.dius.pact.core.model
22

3+
import spock.lang.Issue
34
import spock.lang.Specification
45
import spock.lang.Unroll
56

@@ -169,7 +170,7 @@ class PathExpressionsSpec extends Specification {
169170
@Unroll
170171
def 'Constructing valid path expressions'() {
171172
expect:
172-
PathExpressionsKt.constructValidPath(segment, root) == result
173+
PathExpressionsKt.constructValidPath(segment, root, true) == result
173174

174175
where:
175176

@@ -184,6 +185,13 @@ class PathExpressionsSpec extends Specification {
184185
'a b' | 'a.b' || "a.b['a b']"
185186
'$a.b' | 'a.b' || "a.b['\$a.b']"
186187
'*' | 'a.b' || 'a.b.*'
188+
'1234' | 'a.b' || 'a.b[1234]'
189+
}
190+
191+
@Issue('#1851')
192+
def 'Constructing valid path expressions where numbers are not considered indices'() {
193+
expect:
194+
PathExpressionsKt.constructValidPath('1234', 'a.b', false) == 'a.b.1234'
187195
}
188196

189197
def 'construct path from tokens'() {

0 commit comments

Comments
 (0)