public void SourceOrigin_NoLocation_PropertyCheck()
        {
            var path = new SourcePath();

            path.AddFieldName("topField");
            path.AddFieldName("middleField");
            path.AddArrayIndex(0);

            var origin = new SourceOrigin(path);

            Assert.AreEqual(path, origin.Path);
            Assert.AreEqual(SourceLocation.None, origin.Location);
        }
        public void SourcePath_MakeParent(object[] items, string expectedString)
        {
            var path = new SourcePath();

            foreach (var item in items)
            {
                if (item is int i)
                {
                    path.AddArrayIndex(i);
                }
                else if (item is string s)
                {
                    path.AddFieldName(s);
                }
            }

            var parent = path.MakeParent();
            var result = parent.ArrayString();

            Assert.AreEqual(expectedString, result);

            var origin = parent.AsOrigin();

            Assert.AreEqual(parent, origin.Path);
            Assert.AreEqual(SourceLocation.None, origin.Location);
        }
        public void SourceOrigin_WithBothParts_PropertyCheck()
        {
            var path = new SourcePath();

            path.AddFieldName("topField");
            path.AddFieldName("middleField");
            path.AddArrayIndex(0);

            var location = new SourceLocation(5, 2, 1);

            var origin = new SourceOrigin(location, path);

            Assert.AreEqual(path, origin.Path);
            Assert.AreEqual(location, origin.Location);

            // prefer path over source text when available
            Assert.AreEqual(path.ToString(), origin.ToString());
        }
예제 #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();
            }
        }