/// <summary>
        /// Adds or replaces the source data supplied to the field request.
        /// </summary>
        /// <param name="sourceData">The source data.</param>
        /// <param name="path">An optional to mock where in the graph query the data originated.</param>
        /// <returns>MockFieldRequest.</returns>
        public FieldContextBuilder AddSourceData(object sourceData, SourcePath path = null)
        {
            path = path ?? SourcePath.None;
            var item       = new GraphDataItem(_mockInvocationContext.Object, sourceData, path);
            var dataSource = new GraphFieldDataSource(sourceData, path, item);

            _mockRequest.Setup(x => x.DataSource).Returns(dataSource);
            return(this);
        }
示例#2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="GraphFieldRequest" /> class.
 /// </summary>
 /// <param name="invocationContext">The invocation context that defines how hte field
 /// should be processed according to the query plan.</param>
 /// <param name="dataSource">The data source containing the the source input data to the field as well as
 /// the graph items referenced by said input data.</param>
 /// <param name="origin">The location in the source query where this field request was generated.</param>
 /// <param name="items">A collection of meta data items to carry with this request.</param>
 public GraphFieldRequest(
     IGraphFieldInvocationContext invocationContext,
     GraphFieldDataSource dataSource,
     SourceOrigin origin,
     MetaDataCollection items = null)
 {
     this.Id = Guid.NewGuid().ToString("N");
     this.InvocationContext = Validation.ThrowIfNullOrReturn(invocationContext, nameof(invocationContext));
     this.Origin            = Validation.ThrowIfNullOrReturn(origin, nameof(origin));
     this.Items             = items ?? new MetaDataCollection();
     this.DataSource        = dataSource;
 }
示例#3
0
        /// <summary>
        /// Clones this request for the given lifecycle location.
        /// </summary>
        /// <param name="lifecycle">The lifecycle point at which the directive request should be pointed.</param>
        /// <param name="dataSource">The data source being passed to the field this directive is attached to, if any.</param>
        /// <returns>GraphDirectiveRequest.</returns>
        public IGraphDirectiveRequest ForLifeCycle(
            DirectiveLifeCycle lifecycle,
            GraphFieldDataSource dataSource)
        {
            var request = new GraphDirectiveRequest(
                this.Directive,
                this.DirectiveLocation,
                this.Origin,
                this.Items);

            request.Id         = this.Id;
            request.LifeCycle  = lifecycle;
            request.DataSource = dataSource;
            return(request);
        }
示例#4
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();
            }
        }
示例#5
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()})");
            }
        }