/// <summary> /// Execute a single node /// </summary> /// <remarks> /// Builds child nodes, but does not execute them /// </remarks> protected virtual async Task <ExecutionNode> ExecuteNodeAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); if (node.IsResultSet) { return(node); } try { var arguments = GetArgumentValues(context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); var subFields = SubFieldsFor(context, node.FieldDefinition.ResolvedType, node.Field); var resolveContext = new ResolveFieldContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = node.Source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path, SubFields = subFields }; var resolver = node.FieldDefinition.Resolver ?? new NameFieldResolver(); var result = resolver.Resolve(resolveContext); if (result is Task task) { await task.ConfigureAwait(false); result = task.GetResult(); } node.Result = result; ValidateNodeResult(context, node); // Build child nodes if (node.Result != null) { if (node is ObjectExecutionNode objectNode) { SetSubFieldNodes(context, objectNode); } else if (node is ArrayExecutionNode arrayNode) { SetArrayItemNodes(context, arrayNode); } } } catch (ExecutionError error) { error.AddLocation(node.Field, context.Document); error.Path = node.Path; context.Errors.Add(error); node.Result = null; } catch (Exception ex) { if (context.ThrowOnUnhandledException) { throw; } var error = new ExecutionError($"Error trying to resolve {node.Name}.", ex); error.AddLocation(node.Field, context.Document); error.Path = node.Path; context.Errors.Add(error); node.Result = null; } return(node); }
protected virtual async Task <IObservable <ExecutionResult> > ResolveEventStreamAsync(ExecutionContext context, ExecutionNode node) { context.CancellationToken.ThrowIfCancellationRequested(); var arguments = GetArgumentValues( context.Schema, node.FieldDefinition.Arguments, node.Field.Arguments, context.Variables); object source = (node.Parent != null) ? node.Parent.Result : context.RootValue; try { var resolveContext = new ResolveEventStreamContext { FieldName = node.Field.Name, FieldAst = node.Field, FieldDefinition = node.FieldDefinition, ReturnType = node.FieldDefinition.ResolvedType, ParentType = node.GetParentType(context.Schema), Arguments = arguments, Source = source, Schema = context.Schema, Document = context.Document, Fragments = context.Fragments, RootValue = context.RootValue, UserContext = context.UserContext, Operation = context.Operation, Variables = context.Variables, CancellationToken = context.CancellationToken, Metrics = context.Metrics, Errors = context.Errors, Path = node.Path }; 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 => { var executionNode = BuildExecutionNode(node.Parent, node.GraphType, node.Field, node.FieldDefinition, node.IndexInParentNode); executionNode.Source = value; return executionNode; }) .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); } } return new ExecutionResult { Data = new Dictionary <string, object> { { executionNode.Name, executionNode.ToValue() } } }.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.Path, exception) } }.With(context)))); } catch (Exception ex) { var message = $"Error trying to resolve {node.Field.Name}."; var error = GenerateError(context, message, node.Field, node.Path, ex); context.Errors.Add(error); return(null); } }
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 { Executed = true, 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); } }