Example #1
0
        public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            _testService.BeforeNext(nameof(TestMiddleware2));
            await next(context, cancelToken);

            _testService.AfterNext(nameof(TestMiddleware2));
        }
        /// <summary>
        /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task
        /// generated by calling the next delegate in the request chain with the provided context.
        /// </summary>
        /// <param name="context">The invocation request governing data in this pipeline run.</param>
        /// <param name="next">The next delegate in the chain to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            FieldAuthorizationResult result = FieldAuthorizationResult.Default();

            if (context.IsValid)
            {
                // execute the authorization pipeline
                var authRequest = new GraphFieldAuthorizationRequest(context.Request);
                var authContext = new GraphFieldAuthorizationContext(context, authRequest);
                await _authPipeline.InvokeAsync(authContext, cancelToken).ConfigureAwait(false);

                result = authContext.Result ?? FieldAuthorizationResult.Default();

                // by default, deny any stati not explicitly declared as "successful" by this component.
                if (!result.Status.IsAuthorized())
                {
                    context.Messages.Critical(
                        $"Access Denied to field {context.Field.Route.Path}",
                        Constants.ErrorCodes.ACCESS_DENIED,
                        context.Request.Origin);
                }
            }

            if (!result.Status.IsAuthorized())
            {
                context.ResolvedSourceItems.AddRange(context.Request.DataSource.Items);
                context.ResolvedSourceItems.ForEach(x => x.Fail());
            }

            await next(context, cancelToken).ConfigureAwait(false);
        }
Example #3
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);
        }
 /// <summary>
 /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task
 /// generated by calling the next delegate in the request chain with the provided context.
 /// </summary>
 /// <param name="fieldRequest">The invocation request governing data in this pipeline run.</param>
 /// <param name="next">The next delegate in the chain to be invoked.</param>
 /// <param name="cancelToken">The cancel token.</param>
 /// <returns>Task.</returns>
 public Task InvokeAsync(
     GraphFieldExecutionContext fieldRequest,
     GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next,
     CancellationToken cancelToken = default)
 {
     Counter++;
     return(next(fieldRequest, cancelToken));
 }
Example #5
0
        /// <summary>
        /// Marks the end of a single resolver attempting to generate data according to its own specifications.
        /// </summary>
        /// <param name="context">The context outlining the resolution that is taking place.</param>
        public virtual void EndFieldResolution(GraphFieldExecutionContext context)
        {
            var endTime = _watch.ElapsedTicks;

            if (_resolverEntries.TryGetValue(context, out var entry))
            {
                entry.EndOffsetTicks = endTime;
            }
        }
Example #6
0
        /// <summary>
        /// Invokes this middleware component allowing it to perform its work against the supplied context.
        /// </summary>
        /// <param name="context">The context containing the request passed through the pipeline.</param>
        /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            if (context.IsValid && context.Result != null && !context.IsCancelled)
            {
                await this.ProcessDownStreamFieldContexts(context, cancelToken).ConfigureAwait(false);
            }

            await next(context, cancelToken).ConfigureAwait(false);
        }
Example #7
0
        public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            if (context.Field.Name.Contains("property1", System.StringComparison.OrdinalIgnoreCase))
            {
                throw new GraphExecutionException("Forced Exception for testing");
            }

            return(next(context, cancelToken));
        }
Example #8
0
        /// <summary>
        /// Marks the start of a single resolver attempting to generate data according to its own specifications.
        /// </summary>
        /// <param name="context">The context outlining the resolution that is taking place.</param>
        public virtual void BeginFieldResolution(GraphFieldExecutionContext context)
        {
            var startTime = _watch.ElapsedTicks;
            var entry     = new ApolloMetricsEntry()
            {
                StartOffsetTicks = startTime,
            };

            _resolverEntries.TryAdd(context, entry);
        }
        /// <summary>
        /// Invokes this middleware component allowing it to perform its work against the supplied context.
        /// </summary>
        /// <param name="context">The context containing the request passed through the pipeline.</param>
        /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            // ensure that the data items on teh request match the field they are being executed against
            var field = context.Field;
            var expectedFieldGraphType = _schema.KnownTypes.FindGraphType(field);
            var dataSource             = context.Request.DataSource;

            // ensure the source being supplied
            // matches the expected source of the field being resolved
            if (context.InvocationContext.ExpectedSourceType != null)
            {
                var expectedSourceType = context.InvocationContext.ExpectedSourceType;
                var actualSourceType   = GraphValidation.EliminateWrappersFromCoreType(dataSource.Value.GetType());
                if (expectedSourceType != actualSourceType)
                {
                    var expectedSourceGraphType = _schema.KnownTypes.FindGraphType(expectedSourceType, TypeKind.OBJECT);
                    var analysis = _schema.KnownTypes.AnalyzeRuntimeConcreteType(expectedSourceGraphType, actualSourceType);
                    if (!analysis.ExactMatchFound)
                    {
                        throw new GraphExecutionException(
                                  $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                                  $"a source item of type '{dataSource.Value.GetType().FriendlyName()}' which could not be coerced " +
                                  $"to '{context.InvocationContext.ExpectedSourceType}' as requested by the target graph type '{expectedFieldGraphType.Name}'.");
                    }

                    if (context.Field.Mode == FieldResolutionMode.Batch && !(dataSource.GetType() is IEnumerable))
                    {
                        throw new GraphExecutionException(
                                  $"Operation failed. The field execution context for '{field.Route.Path}' was executed in batch mode " +
                                  $"but was not passed an {nameof(IEnumerable)} for its source data.");
                    }
                }
            }

            if (dataSource.Items.Count == 0)
            {
                var expected = context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem ? "1" :
                               "at least 1";

                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"0 items but expected {expected}.  (Field Mode: {field.Mode.ToString()})");
            }

            if (context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem &&
                dataSource.Items.Count != 1)
            {
                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})");
            }

            return(next(context, cancelToken));
        }
Example #10
0
        /// <summary>
        /// Invokes this middleware component allowing it to perform its work against the supplied context.
        /// </summary>
        /// <param name="context">The context containing the request passed through the pipeline.</param>
        /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            // ensure that the data items on teh request match the field they are being executed against
            var field      = context.Field;
            var dataSource = context.Request.DataSource;

            if (context.InvocationContext.ExpectedSourceType != null)
            {
                var expectedType = context.InvocationContext.ExpectedSourceType;
                if (context.Field.Mode == FieldResolutionMode.Batch)
                {
                    expectedType = typeof(List <>).MakeGenericType(expectedType);
                }

                if (dataSource.Value.GetType() != expectedType)
                {
                    throw new GraphExecutionException(
                              $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                              $"a source item of type '{dataSource.Value.GetType().FriendlyName()}' but expected '{context.InvocationContext.ExpectedSourceType}'.");
                }
            }

            if (dataSource.Items.Count == 0)
            {
                var expected = context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem ? "1" :
                               "at least 1";

                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"0 items but expected {expected}.  (Field Mode: {field.Mode.ToString()})");
            }

            if (context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem &&
                dataSource.Items.Count != 1)
            {
                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})");
            }

            return(next(context, cancelToken));
        }
Example #11
0
        /// <summary>
        /// Assigns the results of resolving the field to the items on the execution context.
        /// </summary>
        /// <param name="executionContext">The execution context.</param>
        /// <param name="resolutionContext">The resolution context.</param>
        private void AssignResults(GraphFieldExecutionContext executionContext, FieldResolutionContext resolutionContext)
        {
            // transfer the result to the execution context
            // then deteremine what (if any) data items can be updated from its value
            executionContext.Result = resolutionContext.Result;

            if (executionContext.Field.Mode == FieldResolutionMode.PerSourceItem)
            {
                if (executionContext.Request.DataSource.Items.Count == 1)
                {
                    var item = executionContext.Request.DataSource.Items[0];
                    executionContext.ResolvedSourceItems.Add(item);
                    item.AssignResult(resolutionContext.Result);
                    return;
                }

                throw new GraphExecutionException(
                          $"When attempting to resolve the field '{executionContext.Field.Route.Path}' an unexpected error occured and the request was teriminated.",
                          executionContext.Request.Origin,
                          new InvalidOperationException(
                              $"The field '{executionContext.Field.Route.Parent}' has a resolution mode of '{nameof(FieldResolutionMode.PerSourceItem)}' " +
                              $"but the execution context contains {executionContext.Request.DataSource.Items.Count} source items. The runtime is unable to determine which " +
                              "item to assign the resultant value to."));
            }
            else if (executionContext.Field.Mode == FieldResolutionMode.Batch)
            {
                var batchProcessor = new BatchResultProcessor(
                    executionContext.Field,
                    executionContext.Request.DataSource.Items,
                    executionContext.Request.Origin);

                var itemsWithAssignedData = batchProcessor.Resolve(executionContext.Result);
                executionContext.ResolvedSourceItems.AddRange(itemsWithAssignedData);
                executionContext.Messages.AddRange(batchProcessor.Messages);
                return;
            }

            throw new ArgumentOutOfRangeException(
                      nameof(executionContext.Field.Mode),
                      $"The execution mode for field '{executionContext.Field.Route.Path}' cannot be resolved " +
                      $"by {nameof(InvokeFieldResolverMiddleware<TSchema>)}. (Mode: {executionContext.Field.Mode.ToString()})");
        }
Example #12
0
        /// <summary>
        /// Inspects both the childcontext and the invoked task to determine
        /// if any messages were found or any unhandled exceptions were thrown then
        /// captures the results into the parent context for upstream processing or
        /// reporting.
        /// </summary>
        /// <param name="parentContext">The parent context that invoked the child.</param>
        /// <param name="childContext">The child context that was invoked.</param>
        /// <param name="childExecutionTask">The actual task representing the child
        /// pipeline.</param>
        private void CaptureChildFieldExecutionResults(
            GraphFieldExecutionContext parentContext,
            GraphFieldExecutionContext childContext,
            Task childExecutionTask)
        {
            // capture any messages that the child context may have internally reported
            parentContext.Messages.AddRange(childContext.Messages);

            // inspect the task for faulting and capture any exceptions as critical errors
            if (childExecutionTask.IsFaulted)
            {
                var exception = childExecutionTask.UnwrapException();
                exception = exception ?? new Exception(
                    "Unknown Error. The child field execution pipeline indicated a fault but did not " +
                    "provide a reason for the failure.");

                parentContext.Messages.Critical(
                    $"Processing field '{childContext.Field.Name}' of '{parentContext.Field.Route}' resulted in a critical failure. See exception for details.",
                    Constants.ErrorCodes.EXECUTION_ERROR,
                    childContext.InvocationContext.Origin,
                    exception);
            }
        }
Example #13
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);
                }
            }
        }
Example #14
0
 private void CancelPipeline(GraphFieldExecutionContext context)
 {
     context.Cancel();
     context.Request.DataSource.Items.ForEach(x => x.Cancel());
 }
Example #15
0
        /// <summary>
        /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task
        /// generated by calling the next delegate in the request chain with the provided context.
        /// </summary>
        /// <param name="context">The invocation request governing data in this pipeline run.</param>
        /// <param name="next">The next delegate in the chain to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            IEnumerable <IDirectiveInvocationContext> directives = context.Request.InvocationContext.Directives ?? Enumerable.Empty <IDirectiveInvocationContext>();

            if (context.IsValid)
            {
                // generate requests contexts for each directive to be processed
                bool cancelPipeline = false;
                foreach (var directive in directives)
                {
                    var directiveArguments = directive.Arguments.Merge(context.VariableData);
                    var request            = new GraphDirectiveRequest(
                        directive.Directive,
                        directive.Location,
                        directive.Origin,
                        context.Request.Items);

                    var beforeResolutionRequest = request.ForLifeCycle(
                        DirectiveLifeCycle.BeforeResolution,
                        context.Request.DataSource);

                    var directiveContext = new DirectiveResolutionContext(context, beforeResolutionRequest, directiveArguments);

                    await this.ExecuteDirective(directiveContext, cancelToken).ConfigureAwait(false);

                    context.Messages.AddRange(directiveContext.Messages);
                    cancelPipeline = cancelPipeline || directiveContext.IsCancelled;
                }

                if (cancelPipeline)
                {
                    this.CancelPipeline(context);
                }
            }

            // ---------------------------------
            // continue the pipeline
            // ---------------------------------
            await next(context, cancelToken).ConfigureAwait(false);

            // ---------------------------------
            // execute after completion directives
            // ---------------------------------
            if (context.IsValid)
            {
                bool cancelPipeline = false;
                foreach (var directive in directives)
                {
                    var directiveArguments = directive.Arguments.Merge(context.VariableData);
                    var request            = new GraphDirectiveRequest(
                        directive.Directive,
                        directive.Location,
                        directive.Origin,
                        context.Request.Items);

                    var afterResolutionRequest = request.ForLifeCycle(
                        DirectiveLifeCycle.AfterResolution,
                        context.Request.DataSource);

                    var directiveContext = new DirectiveResolutionContext(context, afterResolutionRequest, directiveArguments);

                    await this.ExecuteDirective(directiveContext, cancelToken).ConfigureAwait(false);

                    context.Messages.AddRange(directiveContext.Messages);

                    cancelPipeline = cancelPipeline || directiveContext.IsCancelled;
                }

                if (cancelPipeline)
                {
                    this.CancelPipeline(context);
                }
            }
        }
Example #16
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;
            }

            var pipelines = new List <Task>();

            // 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
            IEnumerable <GraphDataItem> allSourceItems = context
                                                         .ResolvedSourceItems
                                                         .SelectMany(x => x.FlattenListItemTree())
                                                         .Where(x => x.ResultData != null && x.Status == FieldItemResolutionStatus.NeedsChildResolution);

            if (!allSourceItems.Any())
            {
                return;
            }

            // create a lookup of source item by result type for easy seperation to the individual
            // downstream child contexts
            var sourceItemLookup = allSourceItems.ToLookup(x => x.ResultData.GetType());

            IEnumerable <GraphFieldExecutionContext> childContexts = null;

            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.Contains(childInvocationContext.ExpectedSourceType))
                    {
                        continue;
                    }

                    sourceItemsToInclude = sourceItemLookup[childInvocationContext.ExpectedSourceType];
                }

                // 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
                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 =>
                    {
                        context.Messages.AddRange(childContext.Messages);
                    });

                    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);
                }
            }
        }
Example #17
0
 /// <summary>
 /// Executes the field pipeline for the given request, returning the raw, unaltered response.
 /// </summary>
 /// <param name="context">The context.</param>
 /// <returns>Task&lt;IGraphPipelineResponse&gt;.</returns>
 public async Task ExecuteField(GraphFieldExecutionContext context)
 {
     var pipeline = this.ServiceProvider.GetService <ISchemaPipeline <TSchema, GraphFieldExecutionContext> >();
     await pipeline.InvokeAsync(context, default).ConfigureAwait(false);
 }
Example #18
0
        private async Task ExecuteOperation(GraphQueryExecutionContext context)
        {
            // create a cancelation sourc irrespective of the required timeout for exeucting this operation
            // this allows for indication of why a task was canceled (timeout or other user driven reason)
            // vs just "it was canceled" which allows for tighter error messages in the response.
            var operation        = context.QueryOperation;
            var fieldInvocations = new List <FieldPipelineInvocation>();

            // Convert the supplied variable values to usable objects of the type expression
            // of the chosen operation
            var variableResolver = new ResolvedVariableGenerator(_schema, operation);
            var variableData     = variableResolver.Resolve(context.Request.VariableData);
            var cancelSource     = new CancellationTokenSource();

            try
            {
                // begin a field execution pipeline for each top level field
                foreach (var invocationContext in operation.FieldContexts)
                {
                    var path = new SourcePath();
                    path.AddFieldName(invocationContext.Name);

                    object dataSourceValue;

                    // fetch the source data value to use for the field invocation
                    // attempt to retrieve from the master context if it was supplied by the pipeline
                    // invoker, otherwise generate a root source
                    if (!context.DefaultFieldSources.TryRetrieveSource(invocationContext.Field, out dataSourceValue))
                    {
                        dataSourceValue = this.GenerateRootSourceData(operation.OperationType);
                    }

                    var topLevelDataItem = new GraphDataItem(invocationContext, dataSourceValue, path);

                    var sourceData = new GraphFieldDataSource(dataSourceValue, path, topLevelDataItem);

                    var fieldRequest = new GraphFieldRequest(
                        invocationContext,
                        sourceData,
                        new SourceOrigin(invocationContext.Origin.Location, path),
                        context.Items);

                    var fieldContext = new GraphFieldExecutionContext(
                        context,
                        fieldRequest,
                        variableData,
                        context.DefaultFieldSources);

                    var fieldTask = _fieldExecutionPipeline.InvokeAsync(fieldContext, cancelSource.Token);

                    var pipelineInvocation = new FieldPipelineInvocation()
                    {
                        Task         = fieldTask,
                        DataItem     = topLevelDataItem,
                        FieldContext = fieldContext,
                    };

                    fieldInvocations.Add(pipelineInvocation);

                    // top level mutation operatons must be executed in sequential order
                    // https://graphql.github.io/graphql-spec/June2018/#sec-Mutation
                    if (_awaitEachTask || operation.OperationType == GraphCollection.Mutation)
                    {
                        await fieldTask.ConfigureAwait(false);
                    }
                }

                // await all the outstanding tasks or a configured timeout
                var fieldPipelineTasksWrapper = Task.WhenAll(fieldInvocations.Select(x => x.Task));
                var timeOutTask = Task.Delay(_timeoutMs, cancelSource.Token);

                var completedTask = await Task.WhenAny(fieldPipelineTasksWrapper, timeOutTask).ConfigureAwait(false);

                var isTimedOut = completedTask == timeOutTask;
                var cancelationWasRequested = cancelSource.IsCancellationRequested;

                if (!isTimedOut)
                {
                    // Field resolutions completed within the timeout period.
                    // Consider that the task may have faulted or been canceled causing them to complete incorrectly.
                    // "re-await" so that any exceptions/cancellation are rethrown correctly.
                    // and not aggregated under the `WhenAll/WhenAny` task from above
                    // https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout
                    foreach (var invocation in fieldInvocations)
                    {
                        await invocation.Task.ConfigureAwait(false);

                        // load the reslts of each field (in order) to the context
                        // for further processing
                        context.FieldResults.Add(invocation.DataItem);
                        context.Messages.AddRange(invocation.FieldContext.Messages);
                    }
                }
                else
                {
                    // when the timeout finishes first, process the cancel token in case any outstanding tasks are running
                    // helps in cases where the timeout finished first but any of the field resolutions are perhaps stuck open
                    // instruct all outstanding tasks to clean them selves up at the earlest possible point
                    if (!cancelationWasRequested)
                    {
                        cancelSource.Cancel();
                    }
                }

                if (cancelationWasRequested)
                {
                    context.Messages.Critical("The execution was canceled prior to completion of the requested query.", Constants.ErrorCodes.OPERATION_CANCELED);
                }
                else if (isTimedOut)
                {
                    context.Messages.Critical($"The execution timed out prior to completion of the requested query. (Total Time: {_timeoutMs}ms)", Constants.ErrorCodes.OPERATION_CANCELED);
                }
            }
            finally
            {
                cancelSource.Dispose();
            }
        }
 public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
 {
     _testService.BeforeNext(this.Id);
     return(next(context, cancelToken));
 }
 public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
 {
     return(Task.FromException(new TestMiddlewareException()));
 }
Example #21
0
        /// <summary>
        /// Using the child context being invoked, this method creates the execution contexts in a manner
        /// that is expected by the invocation context be that 1 per each item, or 1 for a collective set of items being batched.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="childInvocationContext">The child invocation context.</param>
        /// <param name="sourceItemsToInclude">The source items to include.</param>
        /// <returns>IEnumerable&lt;GraphFieldExecutionContext&gt;.</returns>
        private IEnumerable <GraphFieldExecutionContext> CreateChildExecutionContexts(
            GraphFieldExecutionContext context,
            IGraphFieldInvocationContext childInvocationContext,
            IEnumerable <GraphDataItem> sourceItemsToInclude)
        {
            if (childInvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem)
            {
                foreach (var sourceItem in sourceItemsToInclude)
                {
                    var child = sourceItem.AddChildField(childInvocationContext);

                    var dataSource = new GraphFieldDataSource(sourceItem.ResultData, child.Origin.Path, child);
                    var request    = new GraphFieldRequest(childInvocationContext, dataSource, child.Origin, context.Request.Items);
                    yield return(new GraphFieldExecutionContext(
                                     context,
                                     request,
                                     context.VariableData,
                                     context.DefaultFieldSources));
                }
            }
            else if (childInvocationContext.Field.Mode == FieldResolutionMode.Batch)
            {
                // remove any potential indexers from the path to this batch operation
                // in general this will be acted on a collection of items, attempt to remove
                // the first found instance of an indexer in the chain to indicate the path to the batch
                //
                // items may be declared as:        Top.Parent[0].BatchField, Top.Parent[1].BatchField
                // alter the canonical path to be:  Top.Parent.BatchField
                var fieldPath = sourceItemsToInclude.First().Origin.Path.Clone();
                while (fieldPath.IsIndexedItem)
                {
                    fieldPath = fieldPath.MakeParent();
                }

                fieldPath.AddFieldName(childInvocationContext.Field.Name);
                var batchOrigin = new SourceOrigin(context.Request.Origin.Location, fieldPath);

                // create a list to house the raw source data being passed for the batch
                // this is the IEnumerable<T> required as an input to any batch resolver
                var sourceArgumentType = childInvocationContext.Field.Arguments.SourceDataArgument?.ObjectType ?? typeof(object);
                var sourceListType     = typeof(List <>).MakeGenericType(sourceArgumentType);
                var sourceDataList     = InstanceFactory.CreateInstance(sourceListType) as IList;

                // create a list of all the GraphDataItems representing the field
                // being resolved per input item
                var sourceItemList = new List <GraphDataItem>();

                foreach (var item in sourceItemsToInclude)
                {
                    var childField = item.AddChildField(childInvocationContext);
                    sourceDataList.Add(item.ResultData);
                    sourceItemList.Add(childField);
                }

                var dataSource = new GraphFieldDataSource(
                    sourceDataList,
                    fieldPath,
                    sourceItemList);

                var request = new GraphFieldRequest(childInvocationContext, dataSource, batchOrigin, context.Request.Items);
                yield return(new GraphFieldExecutionContext(
                                 context,
                                 request,
                                 context.VariableData,
                                 context.DefaultFieldSources));
            }
            else
            {
                throw new ArgumentOutOfRangeException(
                          nameof(childInvocationContext.Field.Mode),
                          $"The execution mode for field '{childInvocationContext.Field.Route.Path}' cannot be processed " +
                          $"by {nameof(ProcessChildFieldsMiddleware<TSchema>)}. (Mode: {childInvocationContext.Field.Mode.ToString()})");
            }
        }