Skip to content

Commit

Permalink
feat: protect json body generation against loop for cicly reference
Browse files Browse the repository at this point in the history
  • Loading branch information
samukce authored and rholshausen committed Nov 25, 2022
1 parent e256c0c commit f449da5
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Any>?,
root: LambdaDslObject
root: LambdaDslObject,
alreadyProcessedObject: Set<KClass<*>> = setOf()
) {
constructor?.parameters?.filterNot { it.isOptional }?.forEach {
when (val baseField = it.type.jvmErasure) {
Expand All @@ -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
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<KClass<*>> {
data class ByteObjectNonRequiredProperty(val property: Byte)
data class ShortObjectNonRequiredProperty(val property: Short)
Expand All @@ -141,6 +225,7 @@ internal class DslJsonBodyBuilderTest {
}

@JvmStatic
@Suppress("UnusedPrivateMember")
private fun stringPropertyOptionalProperties(): Stream<KClass<*>> {
data class StringObjectNonRequiredPropertyImmutable(val property: String = "")
data class StringObjectNonRequiredPropertyMutable(var property: String = "")
Expand All @@ -152,6 +237,7 @@ internal class DslJsonBodyBuilderTest {
}

@JvmStatic
@Suppress("UnusedPrivateMember")
private fun stringPropertyNonOptionalProperties(): Stream<KClass<*>> {
data class StringObjectRequiredPropertyImmutable(val property: String)
data class StringObjectRequiredPropertyMutable(var property: String)
Expand Down

0 comments on commit f449da5

Please sign in to comment.