From b4ff3a854f9f80c08558b754ec35874348db87fe Mon Sep 17 00:00:00 2001 From: Samuel Date: Fri, 21 Oct 2022 16:56:16 +0200 Subject: [PATCH] feat: protect json body generation against loop for cicly reference --- .../pact/consumer/dsl/DslJsonBodyBuilder.kt | 18 ++-- .../com/dius/pact/consumer/dsl/Extensions.kt | 3 +- .../consumer/dsl/DslJsonBodyBuilderTest.kt | 88 ++++++++++++++++++- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilder.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilder.kt index 9e634cbbb9..0d8e19ae63 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilder.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilder.kt @@ -14,19 +14,20 @@ class DslJsonBodyBuilder { } /** - * Build a {@link LambdaDslJsonBody} based on the Data Object required constructor fields + * Build a {@link LambdaDslJsonBody} based on the required constructor fields */ fun basedOnRequiredConstructorFields(kClass: KClass<*>): (LambdaDslJsonBody) -> Unit = { root: LambdaDslJsonBody -> root.run { val constructor = kClass.primaryConstructor - fillBasedOnConstructorFields(constructor, root) + fillBasedOnConstructorFields(constructor, root, setOf(kClass)) } } private fun fillBasedOnConstructorFields( constructor: KFunction?, - root: LambdaDslObject + root: LambdaDslObject, + alreadyProcessedObject: Set> = setOf() ) { constructor?.parameters?.filterNot { it.isOptional }?.forEach { when (val baseField = it.type.jvmErasure) { @@ -45,10 +46,13 @@ class DslJsonBodyBuilder { else -> root.`object`(it.name) { objDsl -> objDsl.run { - fillBasedOnConstructorFields( - baseField.primaryConstructor, - objDsl - ) + if (!alreadyProcessedObject.contains(baseField)){ + fillBasedOnConstructorFields( + baseField.primaryConstructor, + objDsl, + alreadyProcessedObject + baseField + ) + } } } } diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/Extensions.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/Extensions.kt index 28ee5851af..4075915321 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/Extensions.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/dsl/Extensions.kt @@ -10,7 +10,8 @@ fun newJsonObject(body: LambdaDslJsonBody.() -> Unit): DslPart { } /** - * DSL function to simplify creating a [DslPart] generated from a [LambdaDslJsonBody]. + * DSL function to simplify creating a [DslPart] generated from a [LambdaDslJsonBody] + * based on a required constructor fields for a give [KClass]. */ fun newJsonObject(kClass: KClass<*>): DslPart { return LambdaDsl.newJsonBody(DslJsonBodyBuilder().basedOnRequiredConstructorFields(kClass)).build() diff --git a/consumer/src/test/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilderTest.kt b/consumer/src/test/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilderTest.kt index 799b3a94d7..246f60411e 100644 --- a/consumer/src/test/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilderTest.kt +++ b/consumer/src/test/kotlin/au/com/dius/pact/consumer/dsl/DslJsonBodyBuilderTest.kt @@ -118,8 +118,92 @@ internal class DslJsonBodyBuilderTest { .isEqualTo(expectedBody.pactDslObject.toString()) } + @Test + internal fun `should map inner object multiple occurrences`() { + data class InnerObjectRequiredProperty(val property: String) + data class ObjectRequiredProperty(val inner: InnerObjectRequiredProperty, + val second: InnerObjectRequiredProperty) + + val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(ObjectRequiredProperty::class)) + + val expectedBody = + LambdaDsl.newJsonBody { root -> + root.`object`("inner") { + it.stringType("property") + } + root.`object`("second") { + it.stringType("property") + } + } + + assertThat(actualJsonBody.pactDslObject.toString()) + .isEqualTo(expectedBody.pactDslObject.toString()) + } + + data class InnerObjectRequiredProperty(val property: ObjectRequiredProperty) + data class ObjectRequiredProperty(val inner: InnerObjectRequiredProperty) + @Test + internal fun `should map inner object with loop reference for the first level`() { + val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(ObjectRequiredProperty::class)) + + val expectedBody = + LambdaDsl.newJsonBody { root -> + root.`object`("inner") { it.`object`("property") { } } + } + + assertThat(actualJsonBody.pactDslObject.toString()) + .isEqualTo(expectedBody.pactDslObject.toString()) + } + + data class ThirdProperty(val dependOnFirst: FirstProperty, val property: String) + data class SecondProperty(val third: ThirdProperty) + data class FirstProperty(val second: SecondProperty) + @Test + internal fun `should map inner object with loop reference keeping other fields`() { + val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(FirstProperty::class)) + + val expectedBody = + LambdaDsl.newJsonBody { root -> + root.`object`("second") { + it.`object`("third") { loop -> + loop.`object`("dependOnFirst") { } + loop.stringType("property") + } + } + } + + assertThat(actualJsonBody.pactDslObject.toString()) + .isEqualTo(expectedBody.pactDslObject.toString()) + } + + @Test + internal fun `should map inner object reusing the same class internally`() { + data class CommonClass(val name: String) + data class ThirdToUseCommonProperty(val common: CommonClass) + data class SecondToUseCommonProperty(val third: ThirdToUseCommonProperty, val common: CommonClass) + data class FirstToUseCommonProperty(val second: SecondToUseCommonProperty) + + val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(FirstToUseCommonProperty::class)) + + val expectedBody = + LambdaDsl.newJsonBody { root -> + root.`object`("second") { + it.`object`("third") { loop -> + loop.`object`("common") { third -> third.stringType("name") } + } + it.`object`("common") { loop -> + loop.stringType("name") + } + } + } + + assertThat(actualJsonBody.pactDslObject.toString()) + .isEqualTo(expectedBody.pactDslObject.toString()) + } + companion object { @JvmStatic + @Suppress("UnusedPrivateMember") private fun numberPropertyNonOptional(): Stream> { data class ByteObjectNonRequiredProperty(val property: Byte) data class ShortObjectNonRequiredProperty(val property: Short) @@ -141,6 +225,7 @@ internal class DslJsonBodyBuilderTest { } @JvmStatic + @Suppress("UnusedPrivateMember") private fun stringPropertyOptionalProperties(): Stream> { data class StringObjectNonRequiredPropertyImmutable(val property: String = "") data class StringObjectNonRequiredPropertyMutable(var property: String = "") @@ -152,6 +237,7 @@ internal class DslJsonBodyBuilderTest { } @JvmStatic + @Suppress("UnusedPrivateMember") private fun stringPropertyNonOptionalProperties(): Stream> { data class StringObjectRequiredPropertyImmutable(val property: String) data class StringObjectRequiredPropertyMutable(var property: String) @@ -162,4 +248,4 @@ internal class DslJsonBodyBuilderTest { ) } } -} \ No newline at end of file +}