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 authored and rholshausen committed Nov 25, 2022
1 parent 6bd3f24 commit 7083f8c
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 1 deletion.
1 change: 1 addition & 0 deletions consumer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
dependencies {
api project(path: ":core:model", configuration: 'default')
api project(path: ":core:matchers", configuration: 'default')

compile 'com.googlecode.java-diff-utils:diffutils:1.3.0',
'dk.brics.automaton:automaton:1.11-8',
"org.apache.httpcomponents:httpclient:${project.httpClientVersion}"
Expand Down
2 changes: 2 additions & 0 deletions consumer/java8/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
dependencies {
api project(path: ":consumer", configuration: 'default')

implementation "org.apache.commons:commons-lang3:${project.commonsLang3Version}"

testImplementation "org.junit.jupiter:junit-jupiter:${project.junit5Version}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit5Version}"
testRuntime "ch.qos.logback:logback-classic:${project.logbackVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.pactfoundation.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
Expand Up @@ -2,13 +2,22 @@ package io.pactfoundation.consumer.dsl

import au.com.dius.pact.consumer.dsl.DslPart

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 io.pactfoundation.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))
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ groovyVersion=3.0.9
groovy2Version=2.5.10
kotlinVersion=1.3.72
httpBuilderVersion=1.0.4
commonsLang3Version=3.4
commonsLang3Version=3.12.0
httpClientVersion=4.5.13
scalaVersion=2.13.2
specs2Version=4.9.4
Expand Down

0 comments on commit 7083f8c

Please sign in to comment.