Skip to content

Commit d4002ba

Browse files
committed
Release 0.6.2
- Append all adapters to the same instance of Moshi and use it for all marshall/unmarshall instead of JsonPojoUtils Signed-off-by: Gopal S Akshintala <[email protected]>
1 parent bf162c7 commit d4002ba

File tree

16 files changed

+214
-140
lines changed

16 files changed

+214
-140
lines changed

README.adoc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ endif::[]
1818
:pmtemplates: src/integrationTest/resources/pm-templates
1919
:imagesdir: docs/images
2020
:prewrap!:
21-
:revoman-version: 0.6.1
21+
:revoman-version: 0.6.2
2222

2323
'''
2424

@@ -423,15 +423,16 @@ TIP: If the execution halts due to any failure, the failure and failed step info
423423
[#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization]
424424
=== Type Safety with flexible JSON <- -> POJO marshalling/serialization and unmarshalling/deserialization
425425

426+
[.lead]
426427
Postman operates purely with JSON.
427428
When interoperating Postman with JVM,
428429
unmarshalling/deserialization of JSON into a POJO and vice versa helps in writing Type-safe JVM code and enhances the debugging experience on JVM.
430+
429431
ReṼoman internally uses a modern JSON library called https://github.com/square/moshi[**Moshi**].
430432
Simple types whose JSON structure aligns with the POJO data structure can directly be converted.
431433
But when the JSON structure may not align with the POJO,
432434
you may need a _Custom Type Adapter_ for Marshalling to JSON or Unmarshalling from JSON.
433-
Moshi has it covered for you with its advanced adapter mechanism and ReṼoman accepts Moshi adapters.
434-
Checkout these methods that help us interop between Postman and JVM:
435+
Moshi has it covered for you with its advanced adapter mechanism and ReṼoman accepts Moshi adapters via these config methods:
435436

436437
==== `globalSkipTypes()`
437438

@@ -445,8 +446,7 @@ where you can filter out these legacy types from Marshalling/Unmarshalling
445446
* You may use the bundled static factory methods named `RequestConfig.unmarshallResponse()` for expressiveness.
446447
* You can pass a `PreTxnStepPick` which is a `Predicate` used
447448
to qualify a step whose Request JSON payload needs to be unmarshalled/deserialized.
448-
* If you have set up `requestConfig()` once, wherever you wish to read the request in your <<#_pre_step_and_post_step_hooks,Pre-Step Hooks>>, you can call `stepReport.requestInfo.get().<TypeT>getTypedTxnObj()` which returns your request JSON as a Strong type.
449-
* If you don't configure it for a step, Moshi unmarshalls the step request JSON into default data structures like `LinkedHashMap`
449+
* If you have set up `requestConfig()` once in the config, wherever you wish to read the request in your <<#_pre_step_and_post_step_hooks,Pre-Step Hooks>>, you can call `stepReport.requestInfo.get().<TypeT>getTypedTxnObj()` which returns your request JSON as a Strong type.
450450

451451
==== `responseConfig()`
452452

@@ -456,7 +456,6 @@ to qualify a step whose Request JSON payload needs to be unmarshalled/deserializ
456456
* You can pass a `PostTxnStepPick` which is a `Predicate` used
457457
to qualify a step whose Response JSON payload needs to be unmarshalled/deserialized.
458458
* If you have set up `responseConfig()` once, wherever you wish to read or assert the response in your <<#_pre_step_and_post_step_hooks,Post-Step Hooks>>, you can call `stepReport.responseInfo.get().<TypeT>getTypedTxnObj()` which returns your response JSON as a Strong type to conveniently assert.
459-
* If you don't configure it for a step, Moshi unmarshalls the Step response JSON into default data structures like `LinkedHashMap`
460459

461460
[TIP]
462461
====
@@ -471,8 +470,11 @@ See `DiMorphicAdapter` in action from the test `compositeGraphResponseDiMorphicM
471470

472471
==== `globalCustomTypeAdapters()`
473472

474-
* These come handy when the same POJO/Data structure (e.g `ID`) is present across multiple Request or Response POJOs. These adapters compliment the custom adapters setup in `requestConfig()` or `responseConfig()` wherever these types are present.
475-
* But these adapters won't be used to marshall/unmarshall before or after each step execution. Which means you won't be able to Strong types in the debug view. Although, you can get them on-demand using the respective `getTypedObj()` method.
473+
* These come handy when the same POJO/Data structure (e.g. `ID`) is present across multiple Request or Response POJOs. These adapters compliment the custom adapters setup in `requestConfig()` or `responseConfig()` wherever these types are present.
474+
475+
NOTE: If you don't configure any adapters, you can either pass the adapter at runtime using the overload `stepReport.requestInfo.get().<TypeT>getTypedTxnObj(type, customAdapters, customAdaptersForType)` or Moshi by default unmarshalls the step's request/response JSON into a generic data structures like `LinkedHashMap`
476+
477+
CAUTION: All these adapters supplied from various config methods are appended to the same Moshi instance internally. Read more about link:https://github.com/square/moshi#composing-adapters[Moshi adapter composition]. This means you don't have to supply the adapters repeatedly for the same type. This also means your adapters may get overridden unexpectedly. Keep this in mind while troubleshooting any serialize/deserialize issues. If you wish to manage Moshi yourself, you can get a copy of internal Moshi via `pokeRundown.mutableEnv.moshiReVoman().internalMoshiCopy()`.
476478

477479
==== JSON Reader/Writer Utils to build Moshi adapters
478480

build.gradle.kts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ plugins {
1616
alias(libs.plugins.gradle.taskinfo)
1717
}
1818

19+
val mockitoAgent = configurations.create("mockitoAgent")
20+
1921
dependencies {
2022
api(libs.bundles.http4k)
2123
api(libs.moshix.adapters)
@@ -36,23 +38,24 @@ dependencies {
3638
compileOnly(libs.jetbrains.annotations)
3739
testImplementation(libs.truth)
3840
testImplementation(libs.json.assert)
41+
mockitoAgent(libs.mockito.core) { isTransitive = false }
3942
testImplementation(libs.mockk)
4043
}
4144

4245
testing {
4346
suites {
4447
val test by getting(JvmTestSuite::class) { useJUnitJupiter(libs.versions.junit.get()) }
45-
val integrationTest by
46-
registering(JvmTestSuite::class) {
47-
dependencies {
48-
implementation(project())
49-
implementation(libs.truth)
50-
implementation(libs.mockito.core)
51-
implementation(libs.spring.beans)
52-
implementation(libs.json.assert)
53-
implementation(libs.assertj.vavr)
54-
}
48+
49+
register<JvmTestSuite>("integrationTest") {
50+
dependencies {
51+
implementation(project())
52+
implementation(libs.truth)
53+
implementation(libs.mockito.core)
54+
implementation(libs.spring.beans)
55+
implementation(libs.json.assert)
56+
implementation(libs.assertj.vavr)
5557
}
58+
}
5659
}
5760
}
5861

buildSrc/src/main/kotlin/Config.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
* ************************************************************************************************
77
*/
88
const val GROUP_ID = "com.salesforce.revoman"
9-
const val VERSION = "0.6.1"
9+
const val VERSION = "0.6.2"
1010
const val ARTIFACT_ID = "revoman"
1111
const val STAGING_PROFILE_ID = "1ea0a23e61ba7d"

src/integrationTest/java/com/salesforce/revoman/integration/core/CoreUtils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse;
1818
import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse.Graph.ErrorGraph;
1919
import com.salesforce.revoman.output.report.StepReport;
20+
import java.util.List;
2021
import kotlin.collections.CollectionsKt;
2122

2223
public class CoreUtils {
@@ -39,7 +40,11 @@ public static ResponseConfig unmarshallCompositeGraphResponse() {
3940
public static void assertCompositeGraphResponseSuccess(StepReport stepReport) {
4041
final var responseTxnInfo = stepReport.responseInfo.get();
4142
final var graphResp =
42-
responseTxnInfo.<CompositeGraphResponse>getTypedTxnObj().getGraphs().get(0);
43+
responseTxnInfo
44+
.<CompositeGraphResponse>getTypedTxnObj(
45+
CompositeGraphResponse.class, List.of(CompositeGraphResponse.ADAPTER))
46+
.getGraphs()
47+
.get(0);
4348
assertTrue(
4449
graphResp.isSuccessful(),
4550
() -> {

src/integrationTest/java/com/salesforce/revoman/integration/pokemon/PokemonTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
129129
post(afterStepContainingHeader("postLog"), postLogHook))
130130
.dynamicEnvironment(dynamicEnvironment)
131131
.off());
132-
132+
133133
assertThat(pokeRundown.firstUnIgnoredUnsuccessfulStepReport().failure)
134134
.containsOnLeft(new PostStepHookFailure(RUNTIME_EXCEPTION));
135135
assertThat(pokeRundown.stepReports).hasSize(5);

src/main/kotlin/com/salesforce/revoman/ReVoman.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import com.salesforce.revoman.internal.exe.shouldStepBePicked
2626
import com.salesforce.revoman.internal.exe.unmarshallRequest
2727
import com.salesforce.revoman.internal.exe.unmarshallResponse
2828
import com.salesforce.revoman.internal.json.MoshiReVoman
29-
import com.salesforce.revoman.internal.json.initMoshi
29+
import com.salesforce.revoman.internal.json.MoshiReVoman.Companion.initMoshi
3030
import com.salesforce.revoman.internal.postman.Info
3131
import com.salesforce.revoman.internal.postman.PostmanSDK
3232
import com.salesforce.revoman.internal.postman.RegexReplacer
@@ -140,7 +140,12 @@ object ReVoman {
140140
logger.info { "***** Executing Step: $step *****" }
141141
val itemWithRegex = step.rawPMStep
142142
val preStepReport =
143-
StepReport(step, Right(TxnInfo(httpMsg = itemWithRegex.request.toHttpRequest())))
143+
StepReport(
144+
step,
145+
Right(
146+
TxnInfo(httpMsg = itemWithRegex.request.toHttpRequest(), moshiReVoman = moshiReVoman)
147+
),
148+
)
144149
pm.info = Info(step.name)
145150
pm.currentStepReport = preStepReport
146151
pm.rundown =
@@ -171,7 +176,13 @@ object ReVoman {
171176
pm.rundown = pm.rundown.copy(stepReports = pm.rundown.stepReports + sr)
172177
val item = regexReplacer.replaceVariablesInPmItem(itemWithRegex, pm)
173178
val httpRequest = item.request.toHttpRequest()
174-
fireHttpRequest(step, item.request.auth, httpRequest, kick.insecureHttp())
179+
fireHttpRequest(
180+
step,
181+
item.request.auth,
182+
httpRequest,
183+
kick.insecureHttp(),
184+
moshiReVoman,
185+
)
175186
.mapLeft { sr.copy(requestInfo = Left(it).toVavr()) }
176187
.map {
177188
sr.copy(

src/main/kotlin/com/salesforce/revoman/input/config/KickDef.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,10 @@ internal interface KickDef {
6767
fun requestConfig(): Set<RequestConfig>
6868

6969
@Value.Derived
70-
fun customTypeAdaptersFromRequestConfig():
71-
Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> =
70+
fun customTypeAdaptersFromRequestConfig(): Map<Type, Either<JsonAdapter<out Any>, Factory>> =
7271
requestConfig()
7372
.filter { it.customTypeAdapter != null }
74-
.groupBy({ it.objType }, { it.customTypeAdapter!! })
73+
.associate { it.objType to it.customTypeAdapter!! }
7574

7675
fun responseConfig(): Set<ResponseConfig>
7776

@@ -80,11 +79,10 @@ internal interface KickDef {
8079
responseConfig().groupBy { it.ifSuccess }
8180

8281
@Value.Derived
83-
fun customTypeAdaptersFromResponseConfig():
84-
Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> =
82+
fun customTypeAdaptersFromResponseConfig(): Map<Type, Either<JsonAdapter<out Any>, Factory>> =
8583
responseConfig()
8684
.filter { it.customTypeAdapter != null }
87-
.groupBy({ it.objType }, { it.customTypeAdapter!! })
85+
.associate { it.objType to it.customTypeAdapter!! }
8886

8987
fun globalCustomTypeAdapters(): List<Any>
9088

src/main/kotlin/com/salesforce/revoman/input/json/JsonPojoUtils.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
package com.salesforce.revoman.input.json
1111

1212
import com.salesforce.revoman.input.bufferFileInResources
13-
import com.salesforce.revoman.internal.json.buildMoshi
13+
import com.salesforce.revoman.internal.json.MoshiReVoman.Companion.initMoshi
1414
import com.squareup.moshi.JsonAdapter
1515
import com.squareup.moshi.JsonAdapter.Factory
1616
import io.vavr.control.Either
@@ -33,7 +33,7 @@ fun <PojoT : Any> jsonFileToPojo(
3333
pojoType: Type,
3434
jsonFilePath: String,
3535
customAdapters: List<Any> = emptyList(),
36-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
36+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
3737
skipTypes: Set<Class<out Any>> = emptySet(),
3838
): PojoT? {
3939
val jsonAdapter = initMoshi<PojoT>(customAdapters, customAdaptersWithType, skipTypes, pojoType)
@@ -52,7 +52,7 @@ fun <PojoT : Any> jsonFileToPojo(jsonFile: JsonFile<PojoT>): PojoT? =
5252
inline fun <reified PojoT : Any> jsonFileToPojo(
5353
jsonFilePath: String,
5454
customAdapters: List<Any> = emptyList(),
55-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
55+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
5656
skipTypes: Set<Class<out Any>> = emptySet(),
5757
): PojoT? =
5858
jsonFileToPojo(PojoT::class.java, jsonFilePath, customAdapters, customAdaptersWithType, skipTypes)
@@ -73,7 +73,7 @@ fun <PojoT : Any> jsonToPojo(
7373
pojoType: Type,
7474
jsonStr: String,
7575
customAdapters: List<Any> = emptyList(),
76-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
76+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
7777
skipTypes: Set<Class<out Any>> = emptySet(),
7878
): PojoT? {
7979
val jsonAdapter = initMoshi<PojoT>(customAdapters, customAdaptersWithType, skipTypes, pojoType)
@@ -92,7 +92,7 @@ fun <PojoT : Any> jsonToPojo(jsonString: JsonString<PojoT>): PojoT? =
9292
inline fun <reified PojoT : Any> jsonToPojo(
9393
jsonStr: String,
9494
customAdapters: List<Any> = emptyList(),
95-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
95+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
9696
skipTypes: Set<Class<out Any>> = emptySet(),
9797
): PojoT? =
9898
jsonToPojo(PojoT::class.java, jsonStr, customAdapters, customAdaptersWithType, skipTypes)
@@ -114,7 +114,7 @@ fun <PojoT : Any> pojoToJson(
114114
pojoType: Type,
115115
pojo: PojoT,
116116
customAdapters: List<Any> = emptyList(),
117-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
117+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
118118
skipTypes: Set<Class<out Any>> = emptySet(),
119119
indent: String? = " ",
120120
): String? {
@@ -135,7 +135,7 @@ fun <PojoT : Any> pojoToJson(config: Pojo<PojoT>): String? =
135135
inline fun <reified PojoT : Any> pojoToJson(
136136
pojo: PojoT,
137137
customAdapters: List<Any> = emptyList(),
138-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
138+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
139139
skipTypes: Set<Class<out Any>> = emptySet(),
140140
indent: String? = " ",
141141
): String? =
@@ -144,11 +144,11 @@ inline fun <reified PojoT : Any> pojoToJson(
144144
@SuppressWarnings("kotlin:S3923")
145145
private fun <PojoT : Any> initMoshi(
146146
customAdapters: List<Any> = emptyList(),
147-
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
147+
customAdaptersWithType: Map<Type, Either<JsonAdapter<out Any>, Factory>> = emptyMap(),
148148
skipTypes: Set<Class<out Any>> = emptySet(),
149149
pojoType: Type,
150150
): JsonAdapter<PojoT> =
151-
buildMoshi(customAdapters, customAdaptersWithType, skipTypes).build().adapter(pojoType)
151+
initMoshi(customAdapters, customAdaptersWithType, skipTypes).adapter(pojoType)
152152

153153
@PojoConfig
154154
@Value.Immutable
@@ -159,7 +159,7 @@ internal interface PojoDef<PojoT> {
159159

160160
fun customAdapters(): List<Any>
161161

162-
fun customAdaptersWithType(): Map<Type, List<Either<JsonAdapter<out Any>, Factory>>>
162+
fun customAdaptersWithType(): Map<Type, Either<JsonAdapter<out Any>, Factory>>
163163

164164
fun skipTypes(): Set<Class<out Any>>
165165

@@ -186,7 +186,7 @@ internal interface JsonConfig<PojoT> {
186186

187187
fun customAdapters(): List<Any>
188188

189-
fun customAdaptersWithType(): Map<Type, List<Either<JsonAdapter<out Any>, Factory>>>
189+
fun customAdaptersWithType(): Map<Type, Either<JsonAdapter<out Any>, Factory>>
190190

191191
fun skipTypes(): Set<Class<out Any>>
192192
}

src/main/kotlin/com/salesforce/revoman/internal/exe/HttpRequest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.salesforce.revoman.internal.exe
99

1010
import arrow.core.Either
11+
import com.salesforce.revoman.internal.json.MoshiReVoman
1112
import com.salesforce.revoman.internal.postman.template.Auth
1213
import com.salesforce.revoman.output.ExeType.HTTP_REQUEST
1314
import com.salesforce.revoman.output.report.Step
@@ -36,15 +37,16 @@ internal fun fireHttpRequest(
3637
auth: Auth?,
3738
httpRequest: Request,
3839
insecureHttp: Boolean,
40+
moshiReVoman: MoshiReVoman,
3941
): Either<HttpRequestFailure, TxnInfo<Response>> =
4042
runCatching(currentStep, HTTP_REQUEST) {
4143
// * NOTE gopala.akshintala 06/08/22: Preparing httpClient for each step,
4244
// * as there can be intermediate auths
4345
// ! TODO 29/01/24 gopala.akshintala: When would bearer token size be > 1?
4446
prepareHttpClient(auth?.bearer?.firstOrNull()?.value, insecureHttp)(httpRequest)
4547
}
46-
.mapLeft { HttpRequestFailure(it, TxnInfo(httpMsg = httpRequest)) }
47-
.map { TxnInfo(httpMsg = it) }
48+
.mapLeft { HttpRequestFailure(it, TxnInfo(httpMsg = httpRequest, moshiReVoman = moshiReVoman)) }
49+
.map { TxnInfo(httpMsg = it, moshiReVoman = moshiReVoman) }
4850

4951
private fun prepareHttpClient(bearerToken: String?, insecureHttp: Boolean): HttpHandler =
5052
DebuggingFilters.PrintRequestAndResponse()

src/main/kotlin/com/salesforce/revoman/internal/exe/UnmarshallRequest.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ internal fun unmarshallRequest(
3434
kick
3535
.requestConfig()
3636
.firstOrNull {
37-
it.preTxnStepPick.pick(currentStep, TxnInfo(httpMsg = httpRequest), pm.rundown)
37+
it.preTxnStepPick.pick(
38+
currentStep,
39+
TxnInfo(httpMsg = httpRequest, moshiReVoman = moshiReVoman),
40+
pm.rundown,
41+
)
3842
}
3943
?.also { logger.info { "$currentStep RequestConfig found : ${pprint(it)}" } }
4044
?.objType ?: Any::class.java
@@ -43,15 +47,20 @@ internal fun unmarshallRequest(
4347
runCatching(currentStep, UNMARSHALL_REQUEST) {
4448
pmRequest.body?.let { body -> moshiReVoman.fromJson<Any>(body.raw, requestType) }
4549
}
46-
.mapLeft { UnmarshallRequestFailure(it, TxnInfo(requestType, null, httpRequest)) }
50+
.mapLeft {
51+
UnmarshallRequestFailure(
52+
it,
53+
TxnInfo(requestType, null, httpRequest, moshiReVoman = moshiReVoman),
54+
)
55+
}
4756
else -> {
4857
// ! TODO 15/10/23 gopala.akshintala: xml2Json
4958
logger.info {
5059
"$currentStep No JSON found in the Request body or content-type header didn't match ${APPLICATION_JSON.value}"
5160
}
52-
Right(TxnInfo(httpMsg = httpRequest, isJson = false))
61+
Right(TxnInfo(httpMsg = httpRequest, isJson = false, moshiReVoman = moshiReVoman))
5362
}
54-
}.map { TxnInfo(requestType, it, pmRequest.toHttpRequest()) }
63+
}.map { TxnInfo(requestType, it, pmRequest.toHttpRequest(), moshiReVoman = moshiReVoman) }
5564
}
5665

5766
private val logger = KotlinLogging.logger {}

0 commit comments

Comments
 (0)