/// <summary> /// Invoke the action item as an asyncronous operation. /// </summary> /// <param name="context">The context.</param> /// <param name="next">The next.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken = default) { // create a set of validation contexts for every incoming source graph item // to capture and validate every item regardless of it being successfully resolved or failed var validationContexts = context.Request.DataSource.Items.Select( x => new FieldValidationContext(_schema, x, context.Messages)); // begin profiling of this single field of data context.Metrics?.BeginFieldResolution(context); bool fieldShouldBeCanceled = false; if (context.IsValid) { // build a collection of invokable parameters from the supplied context var executionArguments = context .InvocationContext .Arguments .Merge(context.VariableData) .WithSourceData(context.Request.DataSource.Value); // resolve the field var resolutionContext = new FieldResolutionContext(context, context.Request, executionArguments); context.Logger?.FieldResolutionStarted(resolutionContext); var task = context.Field?.Resolver?.Resolve(resolutionContext, cancelToken); await task.ConfigureAwait(false); context.Messages.AddRange(resolutionContext.Messages); this.AssignResults(context, resolutionContext); fieldShouldBeCanceled = resolutionContext.IsCancelled; context.Logger?.FieldResolutionCompleted(resolutionContext); } if (fieldShouldBeCanceled) { context.Cancel(); context.Request.DataSource.Items.ForEach(x => x.Cancel()); } // validate the resolution of the field in whatever manner that means for its current state var completionProcessor = new FieldCompletionRuleProcessor(); completionProcessor.Execute(validationContexts); // end profiling of this single field of data context.Metrics?.EndFieldResolution(context); await next(context, cancelToken).ConfigureAwait(false); // validate the final result after all downstream middleware execute // in the standard pipeline this generally means all child fields have resolved var validationProcessor = new FieldValidationRuleProcessor(); validationProcessor.Execute(validationContexts); }
/// <summary> /// For any resolved, non-leaf items assigned to the result, pass each through the resolution pipeline /// and await their individual results. /// </summary> /// <param name="context">The "parent" context supplying data for downstream reslts.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> private async Task ProcessDownStreamFieldContexts(GraphFieldExecutionContext context, CancellationToken cancelToken) { if (context.InvocationContext.ChildContexts.Count == 0) { return; } // items resolved on this active context become the source for any downstream fields // --- // can never extract child fields from a null value (even if its valid for the item) // or one that isnt read for it List <GraphDataItem> allSourceItems = context .ResolvedSourceItems .SelectMany(x => x.FlattenListItemTree()) .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution) .ToList(); if (allSourceItems.Count == 0) { return; } // find a reference to the graph type for the field var graphType = _schema.KnownTypes.FindGraphType(context.Field.TypeExpression.TypeName); // theoretically it can't not be found, but you never know if (graphType == null) { var msg = $"Internal Server Error. When processing the results of '{context.Field.Route.Path}' no graph type on the target schema " + $"could be found for the type name '{context.Field.TypeExpression.TypeName}'. " + $"Unable to process the {allSourceItems.Count} item(s) generated."; context.Messages.Add( GraphMessageSeverity.Critical, Constants.ErrorCodes.EXECUTION_ERROR, msg, context.Request.Origin); context.Cancel(); return; } var pipelines = new List <Task>(); // Step 0 // ----------------------------------------------------------------------- // create a lookup of source items by concrete type known to the schema, for easy seperation to the individual // downstream child contexts var sourceItemLookup = this.MapExpectedConcreteTypeFromSourceItem(allSourceItems, graphType); foreach (var childInvocationContext in context.InvocationContext.ChildContexts) { // Step 1 // ---------------------------- // figure out which child items need to be processed through it IEnumerable <GraphDataItem> sourceItemsToInclude; if (childInvocationContext.ExpectedSourceType == null) { sourceItemsToInclude = allSourceItems; } else { // if no children match the required type of the children present, then skip it // this can happen quite often in the case of a union or an interface where multiple invocation contexts // are added to a plan for the same child field in case a parent returns a member of the union or an // implementer of the interface if (!sourceItemLookup.ContainsKey(childInvocationContext.ExpectedSourceType)) { continue; } sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType]; } // Step 1B // For any source items replace any virtual objects with defaults found // on the context for the field in question if (context.DefaultFieldSources.TryRetrieveSource(childInvocationContext.Field, out var defaultSource)) { sourceItemsToInclude = sourceItemsToInclude.Select((currentValue) => { if (currentValue.ResultData is VirtualResolvedObject) { currentValue.AssignResult(defaultSource); } return(currentValue); }); } // Step 2 // ---------------------------- // when the invocation is as a batch, create one execution context for all children // when its "per source" create a context for each child individually IEnumerable <GraphFieldExecutionContext> childContexts = this.CreateChildExecutionContexts( context, childInvocationContext, sourceItemsToInclude); // Step 3 // -------------------- // Fire off the contexts through the pipeline foreach (var childContext in childContexts) { var task = _fieldExecutionPipeline.InvokeAsync(childContext, cancelToken) .ContinueWith(invokeTask => { this.CaptureChildFieldExecutionResults(context, childContext, invokeTask); }); pipelines.Add(task); if (_awaitEachPipeline) { await task.ConfigureAwait(false); } } } // wait for every pipeline to finish await Task.WhenAll(pipelines).ConfigureAwait(false); // reawait to allow for unwrapping and throwing of internal exceptions if (!_awaitEachPipeline) { foreach (var task in pipelines.Where(x => x.IsFaulted)) { await task.ConfigureAwait(false); } } }
private void CancelPipeline(GraphFieldExecutionContext context) { context.Cancel(); context.Request.DataSource.Items.ForEach(x => x.Cancel()); }