@@ -20,7 +20,11 @@ import graphql.nadel.engine.util.JsonMap
2020import graphql.nadel.engine.util.queryPath
2121import graphql.normalized.ExecutableNormalizedField
2222import graphql.normalized.ExecutableNormalizedField.newNormalizedField
23+ import graphql.schema.GraphQLFieldsContainer
2324import graphql.schema.GraphQLInterfaceType
25+ import graphql.schema.GraphQLNamedType
26+ import graphql.schema.GraphQLSchema
27+ import graphql.schema.GraphQLType
2428import graphql.schema.GraphQLTypeUtil.unwrapAll
2529import graphql.schema.GraphQLUnionType
2630
@@ -68,9 +72,9 @@ class NadelNoInterfaceToObjectFragmentExpansionTransform : NadelTransform<State>
6872 ? : return null
6973
7074 // Do nothing for fields with instructions (rename/hydration/stub/…)
71- val hasFieldInstructions = runCatching {
72- executionBlueprint.getTypeNameToInstructionMap< NadelFieldInstruction >( overallField) .isNotEmpty()
73- }.getOrDefault( true )
75+ val hasFieldInstructions = overallField.objectTypeNames.any { objectTypeName ->
76+ executionBlueprint.fieldInstructions.get(objectTypeName, overallField.name)? .isNotEmpty() == true
77+ }
7478 if (hasFieldInstructions) {
7579 return null
7680 }
@@ -145,71 +149,70 @@ class NadelNoInterfaceToObjectFragmentExpansionTransform : NadelTransform<State>
145149
146150/* *
147151 * The exposed overall implementation names if [overallField] is relaxable, else `null`. Relaxable means: the
148- * parent is a single interface/union, the field is selectable at that level (an interface field, or
149- * `__typename`), the selection covers exactly the exposed members, and at least one underlying member is
150- * hidden. Doesn't consider the hint or field instructions - the caller does.
152+ * parent's output is one or more interfaces (or a single union), the field is selectable at that level (an
153+ * interface field present on every parent interface, or `__typename`), the selection covers exactly the exposed
154+ * members, and at least one underlying member is hidden. Doesn't consider the hint or field instructions - the
155+ * caller does.
151156 */
152157private fun computeExposedImplNamesIfRelaxable (
153158 executionBlueprint : NadelOverallExecutionBlueprint ,
154159 service : Service ,
155160 overallField : ExecutableNormalizedField ,
156161): Set <String >? {
157162 val parent = overallField.parent ? : return null
158-
159163 val engineSchema = executionBlueprint.engineSchema
160164 val isTypename = overallField.fieldName == TypeNameMetaFieldDef .name
161165
162- // getFieldDefinitions can throw while planning a hydration backing query; any failure => not a candidate.
163- val parentOutputTypes = runCatching {
164- parent.getFieldDefinitions(engineSchema)
165- .asSequence()
166- .map { unwrapAll(it.type) }
167- .toSet()
168- }.getOrNull() ? : return null
169-
170- // Exposed members, gated on the field being selectable at that level (union => __typename only).
171- val exposedOverallImplNames: Set <String >
172- val parentAbstractTypeName: String
173- when (val parentType = parentOutputTypes.singleOrNull()) {
174- is GraphQLInterfaceType -> {
175- if (! isTypename && parentType.getFieldDefinition(overallField.fieldName) == null ) {
166+ val parentOutputTypes = parent.objectTypeNames.map { parentTypeName ->
167+ val parentType = engineSchema.getType(parentTypeName) as ? GraphQLFieldsContainer ? : return null
168+ val parentFieldDef = parentType.getFieldDefinition(parent.fieldName) ? : return null
169+ unwrapAll(parentFieldDef.type)
170+ }.toSet()
171+
172+ val parentAbstractTypes: List <GraphQLNamedType > =
173+ if (parentOutputTypes.isNotEmpty() && parentOutputTypes.all { it is GraphQLInterfaceType }) {
174+ val interfaces = parentOutputTypes.filterIsInstance<GraphQLInterfaceType >()
175+ val fieldIsOnEveryInterface =
176+ isTypename || interfaces.all { it.getFieldDefinition(overallField.fieldName) != null }
177+ if (! fieldIsOnEveryInterface) {
176178 return null
177179 }
178- exposedOverallImplNames = engineSchema.getImplementations(parentType).map { it.name }.toSet()
179- parentAbstractTypeName = parentType.name
180- }
181- is GraphQLUnionType -> {
180+ interfaces
181+ } else {
182+ val union = parentOutputTypes.singleOrNull() as ? GraphQLUnionType ? : return null
182183 if (! isTypename) {
183184 return null
184185 }
185- exposedOverallImplNames = parentType.types.map { it.name }.toSet()
186- parentAbstractTypeName = parentType.name
186+ listOf (union)
187187 }
188- else -> return null
189- }
190188
189+ val exposedOverallImplNames =
190+ parentAbstractTypes.flatMap { abstractMemberNames(engineSchema, it) ? : return null }.toSet()
191191 // Means the client used fragments with explicit objects. don't relax.
192192 if (overallField.objectTypeNames.toSet() != exposedOverallImplNames) {
193193 return null
194194 }
195195
196- val underlyingTypeName = executionBlueprint.getUnderlyingTypeName(parentAbstractTypeName)
197- val underlyingMemberNames = when (val underlyingType = service.underlyingSchema.getType(underlyingTypeName)) {
198- is GraphQLInterfaceType -> service.underlyingSchema.getImplementations(underlyingType).map { it.name }
199- is GraphQLUnionType -> underlyingType.types.map { it.name }
200- else -> return null
196+ val underlyingMemberNames = parentAbstractTypes.flatMap { abstractType ->
197+ val underlyingTypeName = executionBlueprint.getUnderlyingTypeName(abstractType.name)
198+ val underlyingType = service.underlyingSchema.getType(underlyingTypeName)
199+ abstractMemberNames(service.underlyingSchema, underlyingType) ? : return null
201200 }
202- if (underlyingMemberNames.isEmpty()) {
203- return null
204- }
205-
206- // Nothing to hide unless a member is hidden (and graphql-java already prints bare when none is).
207- val hasHiddenImpl = underlyingMemberNames.any { underlyingName ->
201+ // Relax only if some underlying member is hidden from the overall (graphql-java already prints bare when none is).
202+ val hasHiddenMember = underlyingMemberNames.any { underlyingName ->
208203 executionBlueprint.getOverallTypeName(service, underlyingName) !in exposedOverallImplNames
209204 }
210- if (! hasHiddenImpl ) {
205+ if (! hasHiddenMember ) {
211206 return null
212207 }
213208
214209 return exposedOverallImplNames
215210}
211+
212+ /* * The implementation/member type names of an interface or union in [schema], or `null` if [type] is neither. */
213+ private fun abstractMemberNames (schema : GraphQLSchema , type : GraphQLType ? ): List <String >? =
214+ when (type) {
215+ is GraphQLInterfaceType -> schema.getImplementations(type).map { it.name }
216+ is GraphQLUnionType -> type.types.map { it.name }
217+ else -> null
218+ }
0 commit comments