Esempio n. 1
0
        /// <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);
        }
Esempio n. 2
0
        /// <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);
                }
            }
        }
Esempio n. 3
0
 private void CancelPipeline(GraphFieldExecutionContext context)
 {
     context.Cancel();
     context.Request.DataSource.Items.ForEach(x => x.Cancel());
 }