Skip to content

Commit

Permalink
feat: add capabilityi to build json body based on data class required…
Browse files Browse the repository at this point in the history
… constructor fields
  • Loading branch information
samukce committed Oct 21, 2022
1 parent 79d2174 commit 719f07b
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package au.com.dius.pact.consumer.dsl

import org.apache.commons.lang3.time.DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT
import java.time.ZonedDateTime
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure


class DslJsonBodyBuilder {
companion object {
private val ISO_PATTERN = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.pattern
}

/**
* Build a {@link LambdaDslJsonBody} based on the Data Object required constructor fields
*/
fun basedOnRequiredConstructorFields(kClass: KClass<*>): (LambdaDslJsonBody) -> Unit =
{ root: LambdaDslJsonBody ->
root.run {
val constructor = kClass.primaryConstructor
fillBasedOnConstructorFields(constructor, root)
}
}

private fun fillBasedOnConstructorFields(
constructor: KFunction<Any>?,
root: LambdaDslObject
) {
constructor?.parameters?.filterNot { it.isOptional }?.forEach {
when (val baseField = it.type.jvmErasure) {
String::class -> root.stringType(it.name)
Boolean::class -> root.booleanType(it.name)
Byte::class,
Short::class,
Int::class,
Long::class,
Float::class,
Number::class,
Double::class ->
root.numberType(it.name)
List::class -> root.array(it.name) {}
ZonedDateTime::class -> root.datetime(it.name, ISO_PATTERN)
else ->
root.`object`(it.name) { objDsl ->
objDsl.run {
fillBasedOnConstructorFields(
baseField.primaryConstructor,
objDsl
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package au.com.dius.pact.consumer.dsl

import kotlin.reflect.KClass

/**
* DSL function to simplify creating a [DslPart] generated from a [LambdaDslJsonBody].
*/
fun newJsonObject(body: LambdaDslJsonBody.() -> Unit): DslPart {
return LambdaDsl.newJsonBody { it.body() }.build()
}

/**
* DSL function to simplify creating a [DslPart] generated from a [LambdaDslJsonBody].
*/
fun newJsonObject(kClass: KClass<*>): DslPart {
return LambdaDsl.newJsonBody(DslJsonBodyBuilder().basedOnRequiredConstructorFields(kClass)).build()
}

/**
* DSL function to simplify creating a [DslPart] generated from a [LambdaDslJsonArray].
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package au.com.dius.pact.consumer.dsl

import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import kotlin.reflect.KClass
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.time.ZonedDateTime
import java.util.stream.Stream

internal class DslJsonBodyBuilderTest {
private fun basedOnConstructor(classTest: KClass<*>) =
DslJsonBodyBuilder().basedOnRequiredConstructorFields(classTest)

@ParameterizedTest
@MethodSource(value = ["stringPropertyOptionalProperties"])
internal fun `should not map string property optional with default constructor`(
classTest: KClass<*>
) {
val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(classTest))

val expectedBody =
LambdaDsl.newJsonBody { }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@ParameterizedTest
@MethodSource(value = ["stringPropertyNonOptionalProperties"])
internal fun `should map string property non-optional`(classTest: KClass<*>) {
val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(classTest))

val expectedBody =
LambdaDsl.newJsonBody { it.stringType("property") }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@Test
internal fun `should map string property non-optional with var`() {
data class StringObjectRequiredProperty(var property: String)

val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(StringObjectRequiredProperty::class))

val expectedBody =
LambdaDsl.newJsonBody { it.stringType("property") }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@Test
internal fun `should map boolean property non-optional`() {
data class BooleanObjectRequiredProperty(val property: Boolean)

val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(BooleanObjectRequiredProperty::class))

val expectedBody =
LambdaDsl.newJsonBody { it.booleanType("property") }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@ParameterizedTest
@MethodSource(value = ["numberPropertyNonOptional"])
internal fun `should map simple number property non-optional`(classTest: KClass<*>) {
val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(classTest))

val expectedBody =
LambdaDsl.newJsonBody { it.numberType("property") }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@Test
internal fun `should map zoned date time field for iso 8601`() {
data class DatetimeRequiredProperty(val property: ZonedDateTime)

val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(DatetimeRequiredProperty::class))

val expectedBody =
LambdaDsl.newJsonBody { it.datetime("property", "yyyy-MM-dd'T'HH:mm:ssZZ") }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@Test
internal fun `should map array field`() {
data class ListObjectRequiredProperty(val property: List<String>)

val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(ListObjectRequiredProperty::class))

val expectedBody =
LambdaDsl.newJsonBody { it.array("property") {} }

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

@Test
internal fun `should map inner object`() {
data class InnerObjectRequiredProperty(val property: String)
data class ObjectRequiredProperty(val inner: InnerObjectRequiredProperty)

val actualJsonBody = LambdaDsl.newJsonBody(basedOnConstructor(ObjectRequiredProperty::class))

val expectedBody =
LambdaDsl.newJsonBody { root ->
root.`object`("inner") { it.stringType("property") }
}

assertThat(actualJsonBody.pactDslObject.toString())
.isEqualTo(expectedBody.pactDslObject.toString())
}

companion object {
@JvmStatic
private fun numberPropertyNonOptional(): Stream<KClass<*>> {
data class ByteObjectNonRequiredProperty(val property: Byte)
data class ShortObjectNonRequiredProperty(val property: Short)
data class IntObjectNonRequiredProperty(val property: Int)
data class LongObjectNonRequiredProperty(val property: Long)
data class FloatObjectNonRequiredProperty(val property: Float)
data class DoubleObjectNonRequiredProperty(val property: Double)
data class NumberObjectNonRequiredProperty(val property: Number)

return Stream.of(
ByteObjectNonRequiredProperty::class,
ShortObjectNonRequiredProperty::class,
IntObjectNonRequiredProperty::class,
LongObjectNonRequiredProperty::class,
FloatObjectNonRequiredProperty::class,
DoubleObjectNonRequiredProperty::class,
NumberObjectNonRequiredProperty::class,
)
}

@JvmStatic
private fun stringPropertyOptionalProperties(): Stream<KClass<*>> {
data class StringObjectNonRequiredPropertyImmutable(val property: String = "")
data class StringObjectNonRequiredPropertyMutable(var property: String = "")

return Stream.of(
StringObjectNonRequiredPropertyImmutable::class,
StringObjectNonRequiredPropertyMutable::class,
)
}

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

return Stream.of(
StringObjectRequiredPropertyImmutable::class,
StringObjectRequiredPropertyMutable::class,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,17 @@ class ExtensionsTest {

assertThat(actualJson, equalTo(expectedJson))
}

@Test
fun `can use Kotlin DSL to create a Json body based on required constructor args`() {
data class DataClassObject(val string: String, val number: Number, val optional: String? = null)

val expectedJson = """
|{"number":100,"string":"string"}
|""".trimMargin().replace("\n", "")

val actualJson = newJsonObject(DataClassObject::class).body.toString()

assertThat(actualJson, equalTo(expectedJson))
}
}

0 comments on commit 719f07b

Please sign in to comment.