/// <summary> /// Executes a GraphQL request and returns the result. The default implementation builds the root node /// and passes execution to <see cref="ExecuteNodeTreeAsync(ExecutionContext, ObjectExecutionNode)"/>. /// Once complete, the values are collected into an object that is ready to be serialized and returned /// within an <see cref="ExecutionResult"/>. /// </summary> public virtual async Task <ExecutionResult> ExecuteAsync(ExecutionContext context) { var rootType = ExecutionHelper.GetOperationRootType(context.Document, context.Schema, context.Operation); var rootNode = BuildExecutionRootNode(context, rootType); await ExecuteNodeTreeAsync(context, rootNode) .ConfigureAwait(false); // After the entire node tree has been executed, get the values object data = rootNode.ToValue(); return(new ExecutionResult { Executed = true, Data = data, Query = context.Document.OriginalQuery, Document = context.Document, Operation = context.Operation, Extensions = context.Extensions }); }
protected virtual async Task <IObservable <ExecutionResult> > ResolveEventStreamAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = ExecutionHelper.GetArgumentValues( node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path, RequestServices = context.RequestServices, }; var eventStreamField = node.FieldDefinition as EventStreamFieldType; IObservable <object> subscription; if (eventStreamField?.Subscriber != null) { subscription = eventStreamField.Subscriber.Subscribe(resolveContext); } else if (eventStreamField?.AsyncSubscriber != null) { subscription = await eventStreamField.AsyncSubscriber.SubscribeAsync(resolveContext).ConfigureAwait(false); } else { throw new InvalidOperationException($"Subscriber not set for field '{node.Field.Name}'."); } return(subscription .Select(value => BuildSubscriptionExecutionNode(node.Parent, node.GraphType, node.Field, node.FieldDefinition, node.IndexInParentNode, value)) .SelectMany(async executionNode => { if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.BeforeExecutionAsync(context) .ConfigureAwait(false); } } // Execute the whole execution tree and return the result await ExecuteNodeTreeAsync(context, executionNode).ConfigureAwait(false); if (context.Listeners != null) { foreach (var listener in context.Listeners) { await listener.AfterExecutionAsync(context) .ConfigureAwait(false); } } // Set the execution node's value to null if necessary // Note: assumes that the subscription field is nullable, regardless of how it was defined // See https://github.com/graphql-dotnet/graphql-dotnet/pull/2240#discussion_r570631402 //TODO: check if a non-null subscription field is allowed per the spec //TODO: check if errors should be returned along with the data executionNode.PropagateNull(); // Return the result return new ExecutionResult { Data = new RootExecutionNode(null, null) { SubFields = new ExecutionNode[] { executionNode, } }, }.With(context); }) .Catch <ExecutionResult, Exception>(exception => Observable.Return( new ExecutionResult { Errors = new ExecutionErrors { GenerateError( context, $"Could not subscribe to field '{node.Field.Name}' in query '{context.Document.OriginalQuery}'.", node.Field, node.ResponsePath, exception) } }.With(context)))); } catch (Exception ex) { var message = $"Error trying to resolve field '{node.Field.Name}'."; var error = GenerateError(context, message, node.Field, node.ResponsePath, ex); context.Errors.Add(error); return(null); } }