graphql.nadel.engine.transform.result.NadelResultTransformer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nadel Show documentation
Show all versions of nadel Show documentation
Nadel is a Java library that combines multiple GrahpQL services together into one API.
package graphql.nadel.engine.transform.result
import graphql.nadel.Service
import graphql.nadel.ServiceExecutionResult
import graphql.nadel.engine.NadelExecutionContext
import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprint
import graphql.nadel.engine.plan.NadelExecutionPlan
import graphql.nadel.engine.transform.result.json.JsonNodes
import graphql.nadel.engine.util.JsonMap
import graphql.nadel.engine.util.MutableJsonMap
import graphql.nadel.engine.util.queryPath
import graphql.normalized.ExecutableNormalizedField
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
internal class NadelResultTransformer(private val executionBlueprint: NadelOverallExecutionBlueprint) {
suspend fun transform(
executionContext: NadelExecutionContext,
executionPlan: NadelExecutionPlan,
artificialFields: List,
overallToUnderlyingFields: Map>,
service: Service,
result: ServiceExecutionResult,
): ServiceExecutionResult {
val nodes = JsonNodes(result.data)
val deferredInstructions = ArrayList>>()
coroutineScope {
for ((field, steps) in executionPlan.transformationSteps) {
// This can be null if we did not end up sending the field e.g. for hydration
val underlyingFields = overallToUnderlyingFields[field]
if (underlyingFields.isNullOrEmpty()) {
continue
}
for (step in steps) {
deferredInstructions.add(
async {
step.transform.getResultInstructions(
executionContext,
executionBlueprint,
service,
field,
underlyingFields.first().parent,
result,
step.state,
nodes,
)
},
)
}
}
deferredInstructions.add(
async {
getRemoveArtificialFieldInstructions(artificialFields, nodes)
},
)
}
val instructions = deferredInstructions
.awaitAll()
.flatten()
mutate(result, instructions)
return result
}
private fun mutate(result: ServiceExecutionResult, instructions: List) {
instructions.forEach { transformation ->
when (transformation) {
is NadelResultInstruction.Set -> process(transformation)
is NadelResultInstruction.Remove -> process(transformation)
is NadelResultInstruction.AddError -> process(transformation, result.errors)
}
}
}
private fun process(
instruction: NadelResultInstruction.Set,
) {
@Suppress("UNCHECKED_CAST")
val map = instruction.subject.value as? MutableJsonMap ?: return
map[instruction.key.value] = instruction.newValue?.value
}
private fun process(
instruction: NadelResultInstruction.Remove,
) {
@Suppress("UNCHECKED_CAST")
val map = instruction.subject.value as? MutableJsonMap ?: return
map.remove(instruction.key.value)
}
private fun process(
instruction: NadelResultInstruction.AddError,
errors: List,
) {
val newError = instruction.error.toSpecification()
val mutableErrors = errors.asMutable()
mutableErrors.add(newError)
}
private fun getRemoveArtificialFieldInstructions(
artificialFields: List,
nodes: JsonNodes,
): List {
return artificialFields
.asSequence()
.flatMap { field ->
nodes.getNodesAt(
queryPath = field.queryPath.dropLast(1),
flatten = true,
).map { parentNode ->
NadelResultInstruction.Remove(
subject = parentNode,
key = NadelResultKey(field.resultKey),
)
}
}
.toList()
}
}
internal fun Map.asMutable(): MutableMap {
return this as? MutableMap ?: throw NotMutableError()
}
private fun List.asMutable(): MutableList {
return this as? MutableList ?: throw NotMutableError()
}
private class NotMutableError : RuntimeException("Data was required to be mutable but was not")
© 2015 - 2025 Weber Informatics LLC | Privacy Policy