/// <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); }
/// <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; }
/// <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); }
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(); } }
/// <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<GraphFieldExecutionContext>.</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()})"); } }