/// <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);
            }
        }
Exemple #3
0
        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);
            }
        }