commonMain.aws.sdk.kotlin.hll.dynamodbmapper.pipeline.internal.Operation.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dynamodb-mapper-jvm Show documentation
Show all versions of dynamodb-mapper-jvm Show documentation
High level DynamoDbMapper client
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.hll.dynamodbmapper.pipeline.internal
import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.*
internal class Operation(
private val initialize: (HReq) -> HReqContextImpl,
private val serialize: (HReq, ItemSchema) -> LReq,
private val lowLevelInvoke: suspend (LReq) -> LRes,
private val deserialize: (LRes, ItemSchema) -> HRes,
interceptors: Collection,
) {
private val interceptors = interceptors.map {
// Will cause runtime ClassCastExceptions during interceptor invocation if the types don't match. Is that ok?
@Suppress("UNCHECKED_CAST")
it as Interceptor
}
suspend fun execute(hReq: HReq): HRes {
val hReqContext = doInitialize(hReq)
val lReqContext = doSerialize(hReqContext)
val lResContext = doLowLevelInvoke(lReqContext)
val hResContext = doDeserialize(lResContext)
return finalize(hResContext)
}
private fun > readOnlyHook(
input: I,
reverse: Boolean = false,
hook: Interceptor.(I) -> Unit,
) = interceptors.fold(input, reverse) { ctx, interceptor ->
runCatching {
interceptor.hook(ctx)
}.fold(
onSuccess = { ctx },
onFailure = { e -> ctx + e },
)
}.apply { error?.let { throw it } } // Throw error if present after executing all read-only hooks
private fun modifyHook(
input: I,
reverse: Boolean = false,
hook: Interceptor.(I) -> V,
): I where I : Combinable, I : ErrorCombinable {
var latestCtx = input
return runCatching {
interceptors.fold(latestCtx, reverse) { ctx, interceptor ->
latestCtx = ctx
val value = interceptor.hook(ctx)
ctx + value
}
}.fold(
onSuccess = { it },
onFailure = { e -> latestCtx + e },
)
}
private fun doInitialize(input: HReq): HReqContextImpl {
val ctx = initialize(input)
return readOnlyHook(ctx) { readAfterInitialization(it) }
}
private fun doSerialize(inputCtx: HReqContextImpl): LReqContextImpl {
val rbsCtx = modifyHook(inputCtx) { modifyBeforeSerialization(it) }
val serCtx = readOnlyHook(rbsCtx) { readBeforeSerialization(it) }
val serRes = serCtx.runCatching { serialize(serCtx.highLevelRequest, serCtx.serializeSchema) }
val lReq = serRes.getOrNull()
val rasCtx = serCtx + serRes.exceptionOrNull() + lReq
return readOnlyHook(rasCtx) { readAfterSerialization(it) }.solidify()
}
private suspend fun doLowLevelInvoke(
inputCtx: LReqContextImpl,
): LResContextImpl {
val rbiCtx = modifyHook(inputCtx) { modifyBeforeInvocation(it) }
val invCtx = readOnlyHook(rbiCtx) { readBeforeInvocation(it) }
val invRes = runCatching { lowLevelInvoke(invCtx.lowLevelRequest) }
val lRes = invRes.getOrNull()
val raiCtx = invCtx + invRes.exceptionOrNull() + lRes
return readOnlyHook(raiCtx, reverse = true) { readAfterInvocation(it) }.solidify()
}
private fun doDeserialize(
inputCtx: LResContextImpl,
): HResContextImpl {
val rbdCtx = modifyHook(inputCtx, reverse = true) { modifyBeforeDeserialization(it) }
val desCtx = readOnlyHook(rbdCtx, reverse = true) { readBeforeDeserialization(it) }
val desRes = desCtx.runCatching { deserialize(desCtx.lowLevelResponse, desCtx.deserializeSchema) }
val hRes = desRes.getOrNull()
val radCtx = desCtx + desRes.exceptionOrNull() + hRes
return readOnlyHook(radCtx, reverse = true) { readAfterDeserialization(it) }.solidify()
}
private fun finalize(inputCtx: HResContextImpl): HRes {
val raeCtx = modifyHook(inputCtx, reverse = true) { modifyBeforeCompletion(it) }
val finalCtx = readOnlyHook(raeCtx, reverse = true) { readBeforeCompletion(it) }
return finalCtx.highLevelResponse!!
}
}
private inline fun List.fold(initial: R, reverse: Boolean, operation: (R, T) -> R): R =
if (reverse) foldRight(initial) { curr, acc -> operation(acc, curr) } else fold(initial, operation)