Example #1
0
        public async Task <ExecutionResult> ExecuteAsync(ExecutionOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.Schema == null)
            {
                throw new InvalidOperationException("Cannot execute request if no schema is specified");
            }
            if (options.Query == null)
            {
                throw new InvalidOperationException("Cannot execute request if no query is specified");
            }
            if (options.FieldMiddleware == null)
            {
                throw new InvalidOperationException("Cannot execute request if no middleware builder specified");
            }

            var metrics = new Metrics(options.EnableMetrics).Start(options.OperationName);

            options.Schema.NameConverter = options.NameConverter;
            options.Schema.Filter        = options.SchemaFilter;

            ExecutionResult  result  = null;
            ExecutionContext context = null;

            try
            {
                if (!options.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        options.FieldMiddleware.ApplyTo(options.Schema);
                        options.Schema.Initialize();
                    }
                }

                var document = options.Document;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null)
                    {
                        document = _documentBuilder.Build(options.Query);
                    }
                }

                if (document.Operations.Count == 0)
                {
                    throw new NoOperationError();
                }

                var operation = GetOperation(options.OperationName, document);
                metrics.SetOperationName(operation?.Name);

                if (operation == null)
                {
                    throw new InvalidOperationException($"Query does not contain operation '{options.OperationName}'.");
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = await _documentValidator.ValidateAsync(
                        options.Query,
                        options.Schema,
                        document,
                        options.ValidationRules,
                        options.UserContext,
                        options.Inputs);
                }

                if (options.ComplexityConfiguration != null && validationResult.IsValid)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, options.ComplexityConfiguration);
                }

                context = BuildExecutionContext(
                    options.Schema,
                    options.Root,
                    document,
                    operation,
                    options.Inputs,
                    options.UserContext,
                    options.CancellationToken,
                    metrics,
                    options.Listeners,
                    options.ThrowOnUnhandledException,
                    options.UnhandledExceptionDelegate,
                    options.MaxParallelExecutionCount,
                    options.RequestServices);

                foreach (var listener in options.Listeners)
                {
                    await listener.AfterValidationAsync(context, validationResult)
                    .ConfigureAwait(false);
                }

                if (!validationResult.IsValid)
                {
                    return(new ExecutionResult
                    {
                        Errors = validationResult.Errors,
                        Perf = metrics.Finish()
                    });
                }

                if (context.Errors.Count > 0)
                {
                    return(new ExecutionResult
                    {
                        Errors = context.Errors,
                        Perf = metrics.Finish()
                    });
                }

                using (metrics.Subject("execution", "Executing operation"))
                {
                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.BeforeExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }

                    IExecutionStrategy executionStrategy = SelectExecutionStrategy(context);

                    if (executionStrategy == null)
                    {
                        throw new InvalidOperationException("Invalid ExecutionStrategy!");
                    }

                    var task = executionStrategy.ExecuteAsync(context)
                               .ConfigureAwait(false);

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.BeforeExecutionAwaitedAsync(context)
                            .ConfigureAwait(false);
                        }
                    }

                    result = await task;

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.AfterExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }
                }

                if (context.Errors.Count > 0)
                {
                    result.Errors = context.Errors;
                }
            }
            catch (OperationCanceledException) when(options.CancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (ExecutionError ex)
            {
                result = new ExecutionResult
                {
                    Errors = new ExecutionErrors
                    {
                        ex
                    }
                };
            }
            catch (Exception ex)
            {
                if (options.ThrowOnUnhandledException)
                {
                    throw;
                }

                UnhandledExceptionContext exceptionContext = null;
                if (options.UnhandledExceptionDelegate != null)
                {
                    exceptionContext = new UnhandledExceptionContext(context, null, ex);
                    options.UnhandledExceptionDelegate(exceptionContext);
                    ex = exceptionContext.Exception;
                }

                result = new ExecutionResult
                {
                    Errors = new ExecutionErrors
                    {
                        ex is ExecutionError executionError ? executionError : new UnhandledError(exceptionContext?.ErrorMessage ?? "Error executing document.", ex)
                    }
                };
            }
            finally
            {
                result ??= new ExecutionResult();
                result.Perf = metrics.Finish();
            }

            return(result);
        }
Example #2
0
        public async Task <ExecutionResult> ExecuteAsync(ExecutionOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            var metrics = new Metrics(options.EnableMetrics);

            metrics.Start(options.OperationName);

            options.Schema.FieldNameConverter = options.FieldNameConverter;

            ExecutionResult result = null;

            try
            {
                ValidateOptions(options);

                if (!options.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        if (options.SetFieldMiddleware)
                        {
                            options.FieldMiddleware.ApplyTo(options.Schema);
                        }
                        options.Schema.Initialize();
                    }
                }

                var document = options.Document;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null)
                    {
                        document = _documentBuilder.Build(options.Query);
                    }
                }

                var operation = GetOperation(options.OperationName, document);
                metrics.SetOperationName(operation?.Name);

                if (operation == null)
                {
                    throw new ExecutionError("Unable to determine operation from query.");
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = _documentValidator.Validate(
                        options.Query,
                        options.Schema,
                        document,
                        options.ValidationRules,
                        options.UserContext,
                        options.Inputs);
                }

                if (options.ComplexityConfiguration != null && validationResult.IsValid)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, options.ComplexityConfiguration);
                }

                foreach (var listener in options.Listeners)
                {
                    await listener.AfterValidationAsync(
                        options.UserContext,
                        validationResult,
                        options.CancellationToken)
                    .ConfigureAwait(false);
                }

                if (!validationResult.IsValid)
                {
                    return(new ExecutionResult()
                    {
                        Errors = validationResult.Errors
                    });
                }

                var context = BuildExecutionContext(
                    options.Schema,
                    options.Root,
                    document,
                    operation,
                    options.Inputs,
                    options.UserContext,
                    options.CancellationToken,
                    metrics,
                    options.Listeners);

                if (context.Errors.Any())
                {
                    return(new ExecutionResult()
                    {
                        Errors = context.Errors
                    });
                }

                using (metrics.Subject("execution", "Executing operation"))
                {
                    foreach (var listener in context.Listeners)
                    {
                        await listener.BeforeExecutionAsync(context.UserContext, context.CancellationToken)
                        .ConfigureAwait(false);
                    }

                    IExecutionStrategy executionStrategy = SelectExecutionStrategy(context);

                    if (executionStrategy == null)
                    {
                        throw new InvalidOperationException("Invalid ExecutionStrategy!");
                    }

                    var task = executionStrategy.ExecuteAsync(context)
                               .ConfigureAwait(false);

                    foreach (var listener in context.Listeners)
                    {
                        await listener.BeforeExecutionAwaitedAsync(context.UserContext, context.CancellationToken)
                        .ConfigureAwait(false);
                    }

                    result = await task;

                    foreach (var listener in context.Listeners)
                    {
                        await listener.AfterExecutionAsync(context.UserContext, context.CancellationToken)
                        .ConfigureAwait(false);
                    }
                }

                if (context.Errors.Any())
                {
                    result.Errors = context.Errors;
                }
            }
            catch (Exception ex)
            {
                result = new ExecutionResult
                {
                    Errors = new ExecutionErrors()
                    {
                        new ExecutionError(ex.Message, ex)
                    }
                };
            }
            finally
            {
                result = result ?? new ExecutionResult();
                result.ExposeExceptions = options.ExposeExceptions;
                result.Perf             = metrics.Finish()?.ToArray();
            }

            return(result);
        }
        private async Task <ExecutionResult> CoreExecuteAsync(ExecutionOptions options)
        {
            if (options.Schema == null)
            {
                throw new InvalidOperationException("Cannot execute request if no schema is specified");
            }
            if (options.Query == null && options.Document == null)
            {
                return new ExecutionResult {
                           Errors = new ExecutionErrors {
                               new QueryMissingError()
                           }
                }
            }
            ;

            var metrics = (options.EnableMetrics ? new Metrics() : Metrics.None).Start(options.OperationName);

            ExecutionResult? result            = null;
            ExecutionContext?context           = null;
            bool             executionOccurred = false;

            try
            {
                if (!options.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        options.Schema.Initialize();
                    }
                }

                var document = options.Document;

                bool saveInCache       = false;
                bool analyzeComplexity = true;
                var  validationRules   = options.ValidationRules;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null && (document = await _documentCache.GetAsync(options.Query !).ConfigureAwait(false)) != null)
                    {
                        // none of the default validation rules yet are dependent on the inputs, and the
                        // operation name is not passed to the document validator, so any successfully cached
                        // document should not need any validation rules run on it
                        validationRules   = options.CachedDocumentValidationRules ?? Array.Empty <IValidationRule>();
                        analyzeComplexity = false;
                    }
                    if (document == null)
                    {
                        document    = _documentBuilder.Build(options.Query !);
                        saveInCache = true;
                    }
                }

                if (document.OperationsCount() == 0)
                {
                    throw new NoOperationError();
                }

                var operation = GetOperation(options.OperationName, document);
                if (operation == null)
                {
                    throw new InvalidOperationError($"Query does not contain operation '{options.OperationName}'.");
                }
                metrics.SetOperationName(operation.Name);

                IValidationResult validationResult;
                Variables         variables;
                using (metrics.Subject("document", "Validating document"))
                {
                    (validationResult, variables) = await _documentValidator.ValidateAsync(
                        new ValidationOptions
                    {
                        Document          = document,
                        Rules             = validationRules,
                        Operation         = operation,
                        UserContext       = options.UserContext,
                        RequestServices   = options.RequestServices,
                        CancellationToken = options.CancellationToken,
                        Schema            = options.Schema,
                        Variables         = options.Variables ?? Inputs.Empty,
                        Extensions        = options.Extensions ?? Inputs.Empty,
                    }).ConfigureAwait(false);
                }

                if (options.ComplexityConfiguration != null && validationResult.IsValid && analyzeComplexity)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, options.ComplexityConfiguration);
                }

                if (saveInCache && validationResult.IsValid)
                {
                    await _documentCache.SetAsync(options.Query !, document).ConfigureAwait(false);
                }

                context = BuildExecutionContext(options, document, operation, variables, metrics);

                foreach (var listener in options.Listeners)
                {
                    await listener.AfterValidationAsync(context, validationResult) // TODO: remove ExecutionContext or make different type ?
                    .ConfigureAwait(false);
                }

                if (!validationResult.IsValid)
                {
                    return(new ExecutionResult
                    {
                        Errors = validationResult.Errors,
                        Perf = metrics.Finish()
                    });
                }

                if (context.Errors.Count > 0)
                {
                    return(new ExecutionResult
                    {
                        Errors = context.Errors,
                        Perf = metrics.Finish()
                    });
                }

                executionOccurred = true;

                using (metrics.Subject("execution", "Executing operation"))
                {
                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.BeforeExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }

                    var task = (context.ExecutionStrategy ?? throw new InvalidOperationException("Execution strategy not specified")).ExecuteAsync(context)
                               .ConfigureAwait(false);

                    result = await task;

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.AfterExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }
                }

                result.AddErrors(context.Errors);
            }
            catch (OperationCanceledException) when(options.CancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (ExecutionError ex)
            {
                (result ??= new()).AddError(ex);
            }
            catch (Exception ex)
            {
                if (options.ThrowOnUnhandledException)
                {
                    throw;
                }

                UnhandledExceptionContext?exceptionContext = null;
                if (options.UnhandledExceptionDelegate != null)
                {
                    exceptionContext = new UnhandledExceptionContext(context, null, ex);
                    await options.UnhandledExceptionDelegate(exceptionContext).ConfigureAwait(false);

                    ex = exceptionContext.Exception;
                }

                (result ??= new()).AddError(ex is ExecutionError executionError ? executionError : new UnhandledError(exceptionContext?.ErrorMessage ?? "Error executing document.", ex));
            }
            finally
            {
                result ??= new();
                result.Perf = metrics.Finish();
                if (executionOccurred)
                {
                    result.Executed = true;
                }
                context?.Dispose();
            }

            return(result);
        }
Example #4
0
        public async Task <ExecutionResult> ExecuteAsync(ExecutionOptions config)
        {
            var metrics = new Metrics();

            metrics.Start(config.OperationName);

            config.Schema.FieldNameConverter = config.FieldNameConverter;

            var result = InitializeResult(config);

            try
            {
                ValidateOptions(config);

                if (!config.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        config.FieldMiddleware.ApplyTo(config.Schema);
                        config.Schema.Initialize();
                    }
                }

                var document = config.Document;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null)
                    {
                        document = _documentBuilder.Build(config.Query);
                    }
                }

                result.Document = document;

                var operation = GetOperation(config.OperationName, document);
                result.Operation = operation;
                metrics.SetOperationName(operation?.Name);

                if (operation == null)
                {
                    throw new ExecutionError("Unable to determine operation from query.");
                }

                if (config.ComplexityConfiguration != null)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, config.ComplexityConfiguration);
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = _documentValidator.Validate(
                        config.Query,
                        config.Schema,
                        document,
                        config.ValidationRules,
                        config.UserContext);
                }

                foreach (var listener in config.Listeners)
                {
                    await listener.AfterValidationAsync(
                        config.UserContext,
                        validationResult,
                        config.CancellationToken)
                    .ConfigureAwait(false);
                }

                if (validationResult.IsValid)
                {
                    var context = BuildExecutionContext(
                        config.Schema,
                        config.Root,
                        document,
                        operation,
                        config.Inputs,
                        config.UserContext,
                        config.CancellationToken,
                        metrics);

                    if (context.Errors.Any())
                    {
                        result.Errors = context.Errors;
                        return(result);
                    }

                    using (metrics.Subject("execution", "Executing operation"))
                    {
                        foreach (var listener in config.Listeners)
                        {
                            await listener.BeforeExecutionAsync(config.UserContext, config.CancellationToken).ConfigureAwait(false);
                        }

                        await OnExecution(config, context, result);

                        foreach (var listener in config.Listeners)
                        {
                            await listener.AfterExecutionAsync(config.UserContext, config.CancellationToken).ConfigureAwait(false);
                        }
                    }

                    if (context.Errors.Any())
                    {
                        result.Errors = context.Errors;
                    }
                }
                else
                {
                    OnValidationError(validationResult, result);
                }

                return(result);
            }
            catch (Exception exc)
            {
                OnError(exc, result);
                return(result);
            }
            finally
            {
                result.Perf = metrics.Finish().ToArray();
            }
        }
        public async Task<ExecutionResult> ExecuteAsync(ExecutionOptions config)
        {
            var metrics = new Metrics();
            metrics.Start(config.OperationName);

            config.FieldMiddleware.ApplyTo(config.Schema);

            var result = new ExecutionResult { Query = config.Query };
            try
            {
                if (!config.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        config.Schema.Initialize();
                    }
                }

                var document = config.Document;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null)
                    {
                        document = _documentBuilder.Build(config.Query);
                    }
                }

                result.Document = document;

                var operation = GetOperation(config.OperationName, document);
                result.Operation = operation;
                metrics.SetOperationName(operation?.Name);

                if (config.ComplexityConfiguration != null)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, config.ComplexityConfiguration);
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = _documentValidator.Validate(
                        config.Query,
                        config.Schema,
                        document,
                        config.ValidationRules,
                        config.UserContext);
                }

                foreach (var listener in config.Listeners)
                {
                    await listener.AfterValidationAsync(
                            config.UserContext,
                            validationResult,
                            config.CancellationToken)
                        .ConfigureAwait(false);
                }

                if (validationResult.IsValid)
                {
                    var context = BuildExecutionContext(
                        config.Schema,
                        config.Root,
                        document,
                        operation,
                        config.Inputs,
                        config.UserContext,
                        config.CancellationToken,
                        metrics);

                    if (context.Errors.Any())
                    {
                        result.Errors = context.Errors;
                        return result;
                    }

                    using (metrics.Subject("execution", "Executing operation"))
                    {
                        foreach (var listener in config.Listeners)
                        {
                            await listener.BeforeExecutionAsync(config.UserContext, config.CancellationToken).ConfigureAwait(false);
                        }

                        var task = ExecuteOperationAsync(context).ConfigureAwait(false);

                        foreach (var listener in config.Listeners)
                        {
                            await listener.BeforeExecutionAwaitedAsync(config.UserContext, config.CancellationToken).ConfigureAwait(false);
                        }

                        result.Data = await task;

                        foreach (var listener in config.Listeners)
                        {
                            await listener.AfterExecutionAsync(config.UserContext, config.CancellationToken).ConfigureAwait(false);
                        }
                    }

                    if (context.Errors.Any())
                    {
                        result.Errors = context.Errors;
                    }
                }
                else
                {
                    result.Data = null;
                    result.Errors = validationResult.Errors;
                }

                return result;
            }
            catch (Exception exc)
            {
                if (result.Errors == null)
                {
                    result.Errors = new ExecutionErrors();
                }

                result.Data = null;
                result.Errors.Add(new ExecutionError(exc.Message, exc));
                return result;
            }
            finally
            {
                result.Perf = metrics.Finish().ToArray();
            }
        }
 public Task<ExecutionResult> ExecuteAsync(Action<ExecutionOptions> configure)
 {
     var options = new ExecutionOptions();
     configure(options);
     return ExecuteAsync(options);
 }
Example #7
0
        /// <inheritdoc/>
        public async Task <ExecutionResult> ExecuteAsync(ExecutionOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.Schema == null)
            {
                throw new InvalidOperationException("Cannot execute request if no schema is specified");
            }
            if (options.Query == null)
            {
                throw new InvalidOperationException("Cannot execute request if no query is specified");
            }
            if (options.FieldMiddleware == null)
            {
                throw new InvalidOperationException("Cannot execute request if no middleware builder specified");
            }

            var metrics = new Metrics(options.EnableMetrics).Start(options.OperationName);

            options.Schema.NameConverter = options.NameConverter;
            options.Schema.Filter        = options.SchemaFilter;

            ExecutionResult  result  = null;
            ExecutionContext context = null;

            try
            {
                if (!options.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        lock (options.Schema)
                        {
                            if (!options.Schema.Initialized)
                            {
                                options.FieldMiddleware.ApplyTo(options.Schema);
                                options.Schema.Initialize();
                            }
                        }
                    }
                }

                var document = options.Document;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null)
                    {
                        document = _documentBuilder.Build(options.Query);
                    }
                }

                if (document.Operations.Count == 0)
                {
                    throw new NoOperationError();
                }

                var operation = GetOperation(options.OperationName, document);
                metrics.SetOperationName(operation?.Name);

                if (operation == null)
                {
                    throw new InvalidOperationException($"Query does not contain operation '{options.OperationName}'.");
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = await _documentValidator.ValidateAsync(
                        options.Query,
                        options.Schema,
                        document,
                        options.ValidationRules,
                        options.UserContext,
                        options.Inputs);
                }

                if (options.ComplexityConfiguration != null && validationResult.IsValid)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, options.ComplexityConfiguration);
                }

                try
                {
                    context = BuildExecutionContext(
                        options.Schema,
                        options.Root,
                        document,
                        operation,
                        options.Inputs ?? Inputs.Empty,
                        options.UserContext,
                        options.CancellationToken,
                        metrics,
                        options.Listeners,
                        options.ThrowOnUnhandledException,
                        options.UnhandledExceptionDelegate,
                        options.MaxParallelExecutionCount,
                        options.RequestServices);
                }
                catch (InvalidVariableError)
                {
                    // error parsing variables
                    // attempt to run AfterValidationAsync with null for the 'ExecutionContext.Variables' property

                    context = BuildExecutionContext(
                        options.Schema,
                        options.Root,
                        document,
                        operation,
                        null,
                        options.UserContext,
                        options.CancellationToken,
                        metrics,
                        options.Listeners,
                        options.ThrowOnUnhandledException,
                        options.UnhandledExceptionDelegate,
                        options.MaxParallelExecutionCount,
                        options.RequestServices);

                    try
                    {
                        foreach (var listener in options.Listeners)
                        {
                            await listener.AfterValidationAsync(context, validationResult)
                            .ConfigureAwait(false);
                        }

                        // if there was a validation error, return that, and ignore the variable parsing error
                        if (!validationResult.IsValid)
                        {
                            return(new ExecutionResult
                            {
                                Errors = validationResult.Errors,
                                Perf = metrics.Finish()
                            });
                        }
                    }
                    catch
                    {
                        // if there was an error within AfterValidationAsync (such as a NullReferenceException
                        // due to ExecutionContext.Variables being null), skip this step and throw the variable parsing error
                    }

                    // if there was no validation errors returned, throw the variable parsing error
                    throw;
                }

                foreach (var listener in options.Listeners)
                {
                    await listener.AfterValidationAsync(context, validationResult)
                    .ConfigureAwait(false);
                }

                if (!validationResult.IsValid)
                {
                    return(new ExecutionResult
                    {
                        Errors = validationResult.Errors,
                        Perf = metrics.Finish()
                    });
                }

                if (context.Errors.Count > 0)
                {
                    return(new ExecutionResult
                    {
                        Errors = context.Errors,
                        Perf = metrics.Finish()
                    });
                }

                using (metrics.Subject("execution", "Executing operation"))
                {
                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.BeforeExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }

                    IExecutionStrategy executionStrategy = SelectExecutionStrategy(context);

                    if (executionStrategy == null)
                    {
                        throw new InvalidOperationException("Invalid ExecutionStrategy!");
                    }

                    var task = executionStrategy.ExecuteAsync(context)
                               .ConfigureAwait(false);

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
#pragma warning disable CS0612 // Type or member is obsolete
                            await listener.BeforeExecutionAwaitedAsync(context)
#pragma warning restore CS0612 // Type or member is obsolete
                            .ConfigureAwait(false);
                        }
                    }

                    result = await task;

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.AfterExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }
                }

                if (context.Errors.Count > 0)
                {
                    result.Errors = context.Errors;
                }
            }
            catch (OperationCanceledException) when(options.CancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (ExecutionError ex)
            {
                result = new ExecutionResult
                {
                    Errors = new ExecutionErrors
                    {
                        ex
                    }
                };
            }
            catch (Exception ex)
            {
                if (options.ThrowOnUnhandledException)
                {
                    throw;
                }

                UnhandledExceptionContext exceptionContext = null;
                if (options.UnhandledExceptionDelegate != null)
                {
                    exceptionContext = new UnhandledExceptionContext(context, null, ex);
                    options.UnhandledExceptionDelegate(exceptionContext);
                    ex = exceptionContext.Exception;
                }

                result = new ExecutionResult
                {
                    Errors = new ExecutionErrors
                    {
                        ex is ExecutionError executionError ? executionError : new UnhandledError(exceptionContext?.ErrorMessage ?? "Error executing document.", ex)
                    }
                };
            }
            finally
            {
                result ??= new ExecutionResult();
                result.Perf = metrics.Finish();
            }

            return(result);
        }
        /// <inheritdoc/>
        public virtual async Task <ExecutionResult> ExecuteAsync(ExecutionOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.Schema == null)
            {
                throw new InvalidOperationException("Cannot execute request if no schema is specified");
            }
            if (options.Query == null)
            {
                throw new InvalidOperationException("Cannot execute request if no query is specified");
            }

            var metrics = (options.EnableMetrics ? new Metrics() : Metrics.None).Start(options.OperationName);

            ExecutionResult  result            = null;
            ExecutionContext context           = null;
            bool             executionOccurred = false;

            try
            {
                if (!options.Schema.Initialized)
                {
                    using (metrics.Subject("schema", "Initializing schema"))
                    {
                        options.Schema.Initialize();
                    }
                }

                var  document          = options.Document;
                bool saveInCache       = false;
                bool analyzeComplexity = true;
                var  validationRules   = options.ValidationRules;
                using (metrics.Subject("document", "Building document"))
                {
                    if (document == null && (document = _documentCache[options.Query]) != null)
                    {
                        // none of the default validation rules yet are dependent on the inputs, and the
                        // operation name is not passed to the document validator, so any successfully cached
                        // document should not need any validation rules run on it
                        validationRules   = options.CachedDocumentValidationRules ?? Array.Empty <IValidationRule>();
                        analyzeComplexity = false;
                    }
                    if (document == null)
                    {
                        document    = _documentBuilder.Build(options.Query);
                        saveInCache = true;
                    }
                }

                if (document.Operations.Count == 0)
                {
                    throw new NoOperationError();
                }

                var operation = GetOperation(options.OperationName, document);
                metrics.SetOperationName(operation?.Name);

                if (operation == null)
                {
                    throw new InvalidOperationException($"Query does not contain operation '{options.OperationName}'.");
                }

                IValidationResult validationResult;
                using (metrics.Subject("document", "Validating document"))
                {
                    validationResult = await _documentValidator.ValidateAsync(
                        options.Query,
                        options.Schema,
                        document,
                        validationRules,
                        options.UserContext,
                        options.Inputs);
                }

                if (options.ComplexityConfiguration != null && validationResult.IsValid && analyzeComplexity)
                {
                    using (metrics.Subject("document", "Analyzing complexity"))
                        _complexityAnalyzer.Validate(document, options.ComplexityConfiguration);
                }

                if (saveInCache && validationResult.IsValid)
                {
                    _documentCache[options.Query] = document;
                }

                try
                {
                    context = BuildExecutionContext(
                        options.Schema,
                        options.Root,
                        document,
                        operation,
                        options.Inputs ?? Inputs.Empty,
                        options.UserContext,
                        options.CancellationToken,
                        metrics,
                        options.Listeners,
                        options.ThrowOnUnhandledException,
                        options.UnhandledExceptionDelegate,
                        options.MaxParallelExecutionCount,
                        options.RequestServices);
                }
                catch (InvalidVariableError)
                {
                    // error parsing variables
                    // attempt to run AfterValidationAsync with null for the 'ExecutionContext.Variables' property

                    context = BuildExecutionContext(
                        options.Schema,
                        options.Root,
                        document,
                        operation,
                        null,
                        options.UserContext,
                        options.CancellationToken,
                        metrics,
                        options.Listeners,
                        options.ThrowOnUnhandledException,
                        options.UnhandledExceptionDelegate,
                        options.MaxParallelExecutionCount,
                        options.RequestServices);

                    try
                    {
                        foreach (var listener in options.Listeners)
                        {
                            await listener.AfterValidationAsync(context, validationResult)
                            .ConfigureAwait(false);
                        }

                        // if there was a validation error, return that, and ignore the variable parsing error
                        if (!validationResult.IsValid)
                        {
                            return(new ExecutionResult
                            {
                                Errors = validationResult.Errors,
                                Perf = metrics.Finish()
                            });
                        }
                    }
                    catch
                    {
                        // if there was an error within AfterValidationAsync (such as a NullReferenceException
                        // due to ExecutionContext.Variables being null), skip this step and throw the variable parsing error
                    }

                    // if there was no validation errors returned, throw the variable parsing error
                    throw;
                }

                foreach (var listener in options.Listeners)
                {
                    await listener.AfterValidationAsync(context, validationResult)
                    .ConfigureAwait(false);
                }

                if (!validationResult.IsValid)
                {
                    return(new ExecutionResult
                    {
                        Errors = validationResult.Errors,
                        Perf = metrics.Finish()
                    });
                }

                if (context.Errors.Count > 0)
                {
                    return(new ExecutionResult
                    {
                        Errors = context.Errors,
                        Perf = metrics.Finish()
                    });
                }

                executionOccurred = true;

                using (metrics.Subject("execution", "Executing operation"))
                {
                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.BeforeExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }

                    IExecutionStrategy executionStrategy = SelectExecutionStrategy(context);

                    if (executionStrategy == null)
                    {
                        throw new InvalidOperationException("Invalid ExecutionStrategy!");
                    }

                    var task = executionStrategy.ExecuteAsync(context)
                               .ConfigureAwait(false);

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
#pragma warning disable CS0612 // Type or member is obsolete
                            await listener.BeforeExecutionAwaitedAsync(context)
#pragma warning restore CS0612 // Type or member is obsolete
                            .ConfigureAwait(false);
                        }
                    }

                    result = await task;

                    if (context.Listeners != null)
                    {
                        foreach (var listener in context.Listeners)
                        {
                            await listener.AfterExecutionAsync(context)
                            .ConfigureAwait(false);
                        }
                    }
                }

                result.AddErrors(context.Errors);
            }
            catch (OperationCanceledException) when(options.CancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (ExecutionError ex)
            {
                (result ??= new ExecutionResult()).AddError(ex);
            }
            catch (Exception ex)
            {
                if (options.ThrowOnUnhandledException)
                {
                    throw;
                }

                UnhandledExceptionContext exceptionContext = null;
                if (options.UnhandledExceptionDelegate != null)
                {
                    exceptionContext = new UnhandledExceptionContext(context, null, ex);
                    options.UnhandledExceptionDelegate(exceptionContext);
                    ex = exceptionContext.Exception;
                }

                (result ??= new ExecutionResult()).AddError(ex is ExecutionError executionError ? executionError : new UnhandledError(exceptionContext?.ErrorMessage ?? "Error executing document.", ex));
            }
            finally
            {
                result ??= new ExecutionResult();
                result.Perf = metrics.Finish();
                if (executionOccurred)
                {
                    result.Executed = true;
                }
                context?.Dispose();
            }

            return(result);
        }
Example #9
0
        public static async Task <ExecutionResult> ExecuteWithErrorCheck(this IDocumentExecuter documentExecuter, ExecutionOptions executionOptions)
        {
            Guard.AgainstNull(nameof(documentExecuter), documentExecuter);
            Guard.AgainstNull(nameof(executionOptions), executionOptions);
            var executionResult = await documentExecuter.ExecuteAsync(executionOptions);

            var errors = executionResult.Errors;

            if (errors != null && errors.Count > 0)
            {
                if (errors.Count == 1)
                {
                    throw errors.First();
                }

                throw new AggregateException(errors);
            }

            return(executionResult);
        }