@@ -18,21 +18,30 @@ package com.expediagroup.graphql.execution
1818
1919import graphql.ExecutionResult
2020import graphql.ExecutionResultImpl
21+ import graphql.GraphQLError
22+ import graphql.execution.AbsoluteGraphQLError
2123import graphql.execution.DataFetcherExceptionHandler
24+ import graphql.execution.DataFetcherResult
2225import graphql.execution.ExecutionContext
26+ import graphql.execution.ExecutionStepInfo
2327import graphql.execution.ExecutionStrategy
2428import graphql.execution.ExecutionStrategyParameters
2529import graphql.execution.FetchedValue
2630import graphql.execution.SimpleDataFetcherExceptionHandler
2731import graphql.execution.SubscriptionExecutionStrategy
32+ import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters
33+ import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters
34+ import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters
2835import graphql.execution.reactive.CompletionStageMappingPublisher
36+ import graphql.schema.GraphQLObjectType
2937import kotlinx.coroutines.flow.Flow
3038import kotlinx.coroutines.flow.map
3139import kotlinx.coroutines.future.await
3240import kotlinx.coroutines.reactive.asFlow
3341import org.reactivestreams.Publisher
3442import java.util.Collections
3543import java.util.concurrent.CompletableFuture
44+ import java.util.function.Consumer
3645
3746/* *
3847 * [SubscriptionExecutionStrategy] replacement that returns an [ExecutionResult]
@@ -54,11 +63,15 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec
5463 parameters : ExecutionStrategyParameters
5564 ): CompletableFuture <ExecutionResult > {
5665
66+ val instrumentation = executionContext.instrumentation
67+ val instrumentationParameters = InstrumentationExecutionStrategyParameters (executionContext, parameters)
68+ val executionStrategyCtx = instrumentation.beginExecutionStrategy(instrumentationParameters)
69+
5770 val sourceEventStream = createSourceEventStream(executionContext, parameters)
5871
5972 //
6073 // when the upstream source event stream completes, subscribe to it and wire in our adapter
61- return sourceEventStream.thenApply { sourceFlow ->
74+ val overallResult : CompletableFuture < ExecutionResult > = sourceEventStream.thenApply { sourceFlow ->
6275 if (sourceFlow == null ) {
6376 ExecutionResultImpl (null , executionContext.errors)
6477 } else {
@@ -68,6 +81,12 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec
6881 ExecutionResultImpl (returnFlow, executionContext.errors)
6982 }
7083 }
84+
85+ // dispatched the subscription query
86+ executionStrategyCtx.onDispatched(overallResult)
87+ overallResult.whenComplete(executionStrategyCtx::onCompleted)
88+
89+ return overallResult
7190 }
7291
7392 /*
@@ -118,15 +137,37 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec
118137 parameters : ExecutionStrategyParameters ,
119138 eventPayload : Any?
120139 ): CompletableFuture <ExecutionResult > {
121- val newExecutionContext = executionContext.transform { builder -> builder.root(eventPayload) }
140+ val instrumentation = executionContext.instrumentation
122141
142+ val newExecutionContext = executionContext.transform { builder ->
143+ builder
144+ .root(eventPayload)
145+ .resetErrors()
146+ }
123147 val newParameters = firstFieldOfSubscriptionSelection(parameters)
124- val fetchedValue = FetchedValue .newFetchedValue().fetchedValue(eventPayload)
125- .rawFetchedValue(eventPayload)
126- .localContext(parameters.localContext)
127- .build()
128- return completeField(newExecutionContext, newParameters, fetchedValue).fieldValue
148+ val subscribedFieldStepInfo = createSubscribedFieldStepInfo(executionContext, newParameters)
149+
150+ val i13nFieldParameters = InstrumentationFieldParameters (executionContext, subscribedFieldStepInfo.fieldDefinition, subscribedFieldStepInfo)
151+ val subscribedFieldCtx = instrumentation.beginSubscribedFieldEvent(i13nFieldParameters)
152+
153+ val fetchedValue = unboxPossibleDataFetcherResult(newExecutionContext, parameters, eventPayload)
154+
155+ val fieldValueInfo = completeField(newExecutionContext, newParameters, fetchedValue)
156+ val overallResult = fieldValueInfo
157+ .fieldValue
129158 .thenApply { executionResult -> wrapWithRootFieldName(newParameters, executionResult) }
159+
160+ // dispatch instrumentation so they can know about each subscription event
161+ subscribedFieldCtx.onDispatched(overallResult)
162+ overallResult.whenComplete(subscribedFieldCtx::onCompleted)
163+
164+ // allow them to instrument each ER should they want to
165+ val i13ExecutionParameters = InstrumentationExecutionParameters (
166+ executionContext.executionInput, executionContext.graphQLSchema, executionContext.instrumentationState)
167+
168+ return overallResult.thenCompose { executionResult ->
169+ instrumentation.instrumentExecutionResult(executionResult, i13ExecutionParameters)
170+ }
130171 }
131172
132173 private fun wrapWithRootFieldName (
@@ -154,4 +195,50 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec
154195 val fieldPath = parameters.path.segment(ExecutionStrategy .mkNameForPath(firstField.singleField))
155196 return parameters.transform { builder -> builder.field(firstField).path(fieldPath) }
156197 }
198+
199+ private fun createSubscribedFieldStepInfo (
200+ executionContext : ExecutionContext ,
201+ parameters : ExecutionStrategyParameters
202+ ): ExecutionStepInfo {
203+ val field = parameters.field.singleField
204+ val parentType = parameters.executionStepInfo.unwrappedNonNullType as GraphQLObjectType
205+ val fieldDef = getFieldDef(executionContext.graphQLSchema, parentType, field)
206+ return createExecutionStepInfo(executionContext, parameters, fieldDef, parentType)
207+ }
208+
209+ /* *
210+ * java->kotlin copy of [ExecutionStrategy.unboxPossibleDataFetcherResult] where it's package-private
211+ */
212+ private fun unboxPossibleDataFetcherResult (
213+ executionContext : ExecutionContext ,
214+ parameters : ExecutionStrategyParameters ,
215+ result : Any?
216+ ): FetchedValue {
217+ return if (result is DataFetcherResult <* >) {
218+ if (result.isMapRelativeErrors) {
219+ result.errors.stream()
220+ .map { relError: GraphQLError ? -> AbsoluteGraphQLError (parameters, relError) }
221+ .forEach { error: AbsoluteGraphQLError ? -> executionContext.addError(error) }
222+ } else {
223+ result.errors.forEach(Consumer { error: GraphQLError ? -> executionContext.addError(error) })
224+ }
225+ var localContext = result.localContext
226+ if (localContext == null ) {
227+ // if the field returns nothing then they get the context of their parent field
228+ localContext = parameters.localContext
229+ }
230+ FetchedValue .newFetchedValue()
231+ .fetchedValue(executionContext.valueUnboxer.unbox(result.data))
232+ .rawFetchedValue(result.data)
233+ .errors(result.errors)
234+ .localContext(localContext)
235+ .build()
236+ } else {
237+ FetchedValue .newFetchedValue()
238+ .fetchedValue(executionContext.valueUnboxer.unbox(result))
239+ .rawFetchedValue(result)
240+ .localContext(parameters.localContext)
241+ .build()
242+ }
243+ }
157244}
0 commit comments