Skip to content

Commit

Permalink
Shadow service typename filter (#637)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnawf authored Dec 2, 2024
1 parent a90fbb1 commit 6bd6d50
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 14 deletions.
10 changes: 9 additions & 1 deletion lib/src/main/java/graphql/nadel/NadelExecutionHints.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package graphql.nadel
import graphql.nadel.hints.AllDocumentVariablesHint
import graphql.nadel.hints.LegacyOperationNamesHint
import graphql.nadel.hints.NadelDeferSupportHint
import graphql.nadel.hints.NadelServiceTypenameShadowingHint
import graphql.nadel.hints.NadelSharedTypeRenamesHint
import graphql.nadel.hints.NadelShortCircuitEmptyQueryHint
import graphql.nadel.hints.NadelValidationBlueprintHint
import graphql.nadel.hints.NadelVirtualTypeSupportHint
import graphql.nadel.hints.NewResultMergerAndNamespacedTypename

Expand All @@ -17,6 +17,7 @@ data class NadelExecutionHints(
val sharedTypeRenames: NadelSharedTypeRenamesHint,
val shortCircuitEmptyQuery: NadelShortCircuitEmptyQueryHint,
val virtualTypeSupport: NadelVirtualTypeSupportHint,
val serviceTypenameShadowing: NadelServiceTypenameShadowingHint,
) {
/**
* Returns a builder with the same field values as this object.
Expand All @@ -36,6 +37,7 @@ data class NadelExecutionHints(
private var shortCircuitEmptyQuery = NadelShortCircuitEmptyQueryHint { false }
private var sharedTypeRenames = NadelSharedTypeRenamesHint { false }
private var virtualTypeSupport = NadelVirtualTypeSupportHint { false }
private var serviceTypenameShadowing = NadelServiceTypenameShadowingHint.default

constructor()

Expand Down Expand Up @@ -81,6 +83,11 @@ data class NadelExecutionHints(
return this
}

fun serviceTypenameShadowing(flag: NadelServiceTypenameShadowingHint): Builder {
serviceTypenameShadowing = flag
return this
}

fun build(): NadelExecutionHints {
return NadelExecutionHints(
legacyOperationNames,
Expand All @@ -90,6 +97,7 @@ data class NadelExecutionHints(
sharedTypeRenames,
shortCircuitEmptyQuery,
virtualTypeSupport,
serviceTypenameShadowing,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ inline fun <reified T : NadelFieldInstruction> NadelOverallExecutionBlueprint.ge

internal class NadelOverallExecutionBlueprintMigrator(
private val hint: NadelValidationBlueprintHint,
private val old: NadelOverallExecutionBlueprint,
private val new: Lazy<NadelOverallExecutionBlueprint?>,
val old: NadelOverallExecutionBlueprint,
val new: Lazy<NadelOverallExecutionBlueprint?>,
) : NadelOverallExecutionBlueprint {
private val impl: NadelOverallExecutionBlueprint
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import graphql.nadel.engine.NadelExecutionContext
import graphql.nadel.engine.NadelServiceExecutionContext
import graphql.nadel.engine.blueprint.IntrospectionService
import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprint
import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprintMigrator
import graphql.nadel.engine.transform.NadelServiceTypeFilterTransform.State
import graphql.nadel.engine.transform.artificial.NadelAliasHelper
import graphql.nadel.engine.transform.query.NadelQueryTransformer
import graphql.nadel.engine.transform.result.NadelResultInstruction
import graphql.nadel.engine.transform.result.json.JsonNodes
import graphql.nadel.engine.util.resolveObjectTypes
import graphql.nadel.engine.util.toBuilder
import graphql.nadel.util.getLogger
import graphql.normalized.ExecutableNormalizedField
import graphql.normalized.ExecutableNormalizedField.newNormalizedField

Expand Down Expand Up @@ -63,6 +65,8 @@ import graphql.normalized.ExecutableNormalizedField.newNormalizedField
* - service-types-are-completely-filtered.yml
*/
class NadelServiceTypeFilterTransform : NadelTransform<State> {
private val logger = getLogger<NadelServiceTypeFilterTransform>()

data class State(
val aliasHelper: NadelAliasHelper,
val typeNamesOwnedByService: Set<String>,
Expand All @@ -89,17 +93,49 @@ class NadelServiceTypeFilterTransform : NadelTransform<State> {
}

val typeNamesOwnedByService = executionBlueprint.getOverAllTypeNamesForService(service)
// Add underlying type names as well to handle combination of hydration and renames.
// Transforms are applied to hydration fields as well, and those fields always reference
// elements from the underlying schema
val underlyingTypeNamesOwnedByService = executionBlueprint.getUnderlyingTypeNamesForService(service)

val fieldObjectTypeNamesOwnedByService = overallField.objectTypeNames
.filter {
// it is MUCH quicker to compare membership in 2 sets rather than
// concat 1 giant set and then check
it in typeNamesOwnedByService
|| it in underlyingTypeNamesOwnedByService
|| (executionContext.hints.sharedTypeRenames(service) && executionBlueprint.getUnderlyingTypeName(it) in underlyingTypeNamesOwnedByService)
.filter { objectTypeName ->
if (executionBlueprint is NadelOverallExecutionBlueprintMigrator && executionContext.hints.serviceTypenameShadowing.isShadowingEnabled()) {
val oldDecision = isTypeOwnedByService(
objectTypeName,
executionContext,
service,
executionBlueprint.old,
)

try {
val newDecision = executionBlueprint.new.value?.let { newBlueprint ->
isTypeOwnedByService(
objectTypeName,
executionContext,
service,
newBlueprint,
)
}

if (newDecision != null && oldDecision != newDecision) {
executionContext.hints.serviceTypenameShadowing.onMismatch(
oldDecision,
newDecision,
service,
objectTypeName,
overallField,
)
}
} catch (e: Exception) {
logger.error("Error shadowing service typename filter transform", e)
}

oldDecision
} else {
isTypeOwnedByService(
objectTypeName,
executionContext,
service,
executionBlueprint,
)
}
}

// All types are owned by service
Expand All @@ -108,7 +144,6 @@ class NadelServiceTypeFilterTransform : NadelTransform<State> {
return null
}


return State(
aliasHelper = NadelAliasHelper.forField(
tag = "type_filter",
Expand All @@ -120,6 +155,25 @@ class NadelServiceTypeFilterTransform : NadelTransform<State> {
)
}

private fun isTypeOwnedByService(
objectTypeName: String,
executionContext: NadelExecutionContext,
service: Service,
executionBlueprint: NadelOverallExecutionBlueprint,
): Boolean {
val typeNamesOwnedByService = executionBlueprint.getOverAllTypeNamesForService(service)
// Add underlying type names as well to handle combination of hydration and renames.
// Transforms are applied to hydration fields as well, and those fields always reference
// elements from the underlying schema
val underlyingTypeNamesOwnedByService = executionBlueprint.getUnderlyingTypeNamesForService(service)

// it is MUCH quicker to compare membership in 2 sets rather than
// concat 1 giant set and then check
return objectTypeName in typeNamesOwnedByService
|| objectTypeName in underlyingTypeNamesOwnedByService
|| (executionContext.hints.sharedTypeRenames(service) && executionBlueprint.getUnderlyingTypeName(objectTypeName) in underlyingTypeNamesOwnedByService)
}

override suspend fun transformField(
executionContext: NadelExecutionContext,
serviceExecutionContext: NadelServiceExecutionContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package graphql.nadel.hints

import graphql.nadel.Service
import graphql.normalized.ExecutableNormalizedField

interface NadelServiceTypenameShadowingHint {
fun isShadowingEnabled(): Boolean

fun onMismatch(
oldDecision: Boolean,
newDecision: Boolean,
service: Service,
objectTypeName: String,
field: ExecutableNormalizedField,
)

companion object {
internal val default = object : NadelServiceTypenameShadowingHint {
override fun isShadowingEnabled(): Boolean {
return false
}

override fun onMismatch(
oldDecision: Boolean,
newDecision: Boolean,
service: Service,
objectTypeName: String,
field: ExecutableNormalizedField,
) {
}
}
}
}

0 comments on commit 6bd6d50

Please sign in to comment.