All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.aws.sdk.kotlin.hll.dynamodbmapper.pipeline.internal.Operation.kt Maven / Gradle / Ivy

There is a newer version: 1.3.99-beta
Show newest version
/*
 * 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)