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

graphql.nadel.enginekt.transform.query.NadelQueryTransformer.kt Maven / Gradle / Ivy

Go to download

Nadel is a Java library that combines multiple GrahpQL services together into one API.

The newest version!
package graphql.nadel.enginekt.transform.query

import graphql.nadel.Service
import graphql.nadel.enginekt.NadelExecutionContext
import graphql.nadel.enginekt.blueprint.NadelOverallExecutionBlueprint
import graphql.nadel.enginekt.plan.NadelExecutionPlan
import graphql.nadel.enginekt.transform.NadelTransform
import graphql.nadel.enginekt.transform.NadelTransformFieldResult
import graphql.nadel.enginekt.util.toBuilder
import graphql.normalized.ExecutableNormalizedField

class NadelQueryTransformer private constructor(
    private val executionBlueprint: NadelOverallExecutionBlueprint,
    private val service: Service,
    private val executionContext: NadelExecutionContext,
    private val executionPlan: NadelExecutionPlan,
    private val transformContext: TransformContext,
) {
    companion object {
        suspend fun transformQuery(
            executionBlueprint: NadelOverallExecutionBlueprint,
            service: Service,
            executionContext: NadelExecutionContext,
            executionPlan: NadelExecutionPlan,
            field: ExecutableNormalizedField,
        ): TransformResult {
            val transformContext = TransformContext()

            val transformer = NadelQueryTransformer(
                executionBlueprint,
                service,
                executionContext,
                executionPlan,
                transformContext,
            )
            val result = transformer.transform(field)
                .also { rootFields ->
                    transformer.fixParentRefs(parent = null, rootFields)
                }

            return TransformResult(
                result = result,
                artificialFields = transformContext.artificialFields,
                overallToUnderlyingFields = transformContext.overallToUnderlyingFields,
            )
        }
    }

    private data class TransformContext(
        val artificialFields: MutableList = mutableListOf(),
        val overallToUnderlyingFields: MutableMap> = mutableMapOf(),
    )

    data class TransformResult(
        /**
         * The transformed fields.
         */
        val result: List,
        /**
         * A list of fields that were added to the query that do not belong in the overall result.
         */
        val artificialFields: List,
        val overallToUnderlyingFields: Map>,
    )

    fun markArtificial(field: ExecutableNormalizedField) {
        transformContext.artificialFields.add(field)
    }

    /**
     * Helper for calling [transform] for all the given [fields].
     */
    suspend fun transform(
        fields: List,
    ): List {
        return fields.flatMap {
            transform(it)
        }
    }

    suspend fun transform(
        field: ExecutableNormalizedField,
    ): List {
        val transformationSteps: List> = executionPlan.transformationSteps[field]
            ?: return listOf(
                transformPlain(field)
            )

        return transform(field, transformationSteps)
    }

    private suspend fun transform(
        field: ExecutableNormalizedField,
        transformationSteps: List>,
    ): List {
        val transformResult = applyTransformationSteps(field, transformationSteps)

        val artificialFields = transformResult.artificialFields.map {
            it.toBuilder()
                .clearObjectTypesNames()
                .objectTypeNames(getUnderlyingTypeNames(it.objectTypeNames))
                .build()
        }

        val newField = listOfNotNull(
            transformResult.newField?.let {
                it.toBuilder()
                    .clearObjectTypesNames()
                    .objectTypeNames(getUnderlyingTypeNames(it.objectTypeNames))
                    .children(transform(it.children))
                    .build()
            },
        )

        transformContext.artificialFields.addAll(artificialFields)

        // Track overall -> underlying fields
        transformContext.overallToUnderlyingFields.compute(field) { _, oldValue ->
            (oldValue ?: emptyList()) + newField + artificialFields
        }

        return artificialFields + newField
    }

    /**
     * Transforms a field with no [NadelTransform]s associated with it.
     */
    private suspend fun transformPlain(field: ExecutableNormalizedField): ExecutableNormalizedField {
        return field.toBuilder()
            .clearObjectTypesNames()
            .objectTypeNames(getUnderlyingTypeNames(field.objectTypeNames))
            .children(transform(field.children))
            .build()
            .also { newField ->
                // Track overall -> underlying fields
                transformContext.overallToUnderlyingFields.compute(field) { _, oldValue ->
                    (oldValue ?: emptyList()) + newField
                }
            }
    }

    private suspend fun applyTransformationSteps(
        field: ExecutableNormalizedField,
        transformationSteps: List>,
    ): NadelTransformFieldResult {
        var fieldFromPreviousTransform: ExecutableNormalizedField = field
        var aggregatedTransformResult: NadelTransformFieldResult? = null
        for ((_, _, transform, state) in transformationSteps) {
            val transformResultForStep = transform.transformField(
                executionContext,
                this,
                executionBlueprint,
                service,
                fieldFromPreviousTransform,
                state,
            )
            aggregatedTransformResult = if (aggregatedTransformResult == null) {
                transformResultForStep
            } else {
                NadelTransformFieldResult(
                    transformResultForStep.newField,
                    aggregatedTransformResult.artificialFields + transformResultForStep.artificialFields,
                )
            }
            fieldFromPreviousTransform = transformResultForStep.newField ?: break
        }
        return aggregatedTransformResult!!
    }

    private fun getUnderlyingTypeNames(objectTypeNames: Collection): List {
        return objectTypeNames.map {
            executionBlueprint.getUnderlyingTypeName(service, overallTypeName = it)
        }
    }

    private fun fixParentRefs(
        parent: ExecutableNormalizedField?,
        transformFields: List,
    ) {
        transformFields.forEach {
            it.replaceParent(parent)
            fixParentRefs(parent = it, it.children)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy