Example #1
0
 /// <summary>
 /// Builds the primary request context used to execute the query and generate a response. When overridden in a child
 /// controller, this method allows the controller to alter or change various attributes about the request before it is
 /// invoked.
 /// </summary>
 /// <param name="queryData">The data package recieved via a POST request from a connected client.</param>
 /// <returns>A fully qualified request context that can be executed.</returns>
 protected virtual IGraphOperationRequest CreateRequest(GraphQueryData queryData)
 {
     return(new GraphOperationRequest(
                queryData.Query,
                queryData.OperationName,
                queryData.Variables));
 }
        public async Task ClientSubscription_FromQueryData_GeneralPropertyCheck()
        {
            var testServer = new TestServerBuilder()
                             .AddGraphController <ClientSubscriptionTestController>()
                             .AddSubscriptionServer()
                             .Build();

            var schema    = testServer.Schema;
            var subServer = testServer.RetrieveSubscriptionServer();
            var queryPlan = await testServer.CreateQueryPlan("subscription { watchObjects { property1 property2  }} ");

            Assert.AreEqual(1, queryPlan.Operations.Count);
            Assert.AreEqual(0, queryPlan.Messages.Count);

            var field = queryPlan.Operations.Values.First().FieldContexts[0].Field;
            var name  = field.GetType().FullName;

            (var socketClient, var testClient) = await testServer.CreateSubscriptionClient();

            var queryData = new GraphQueryData();

            var sub = new ClientSubscription <GraphSchema>(
                testClient,
                queryData,
                queryPlan,
                queryPlan.Operations.First().Value,
                "abc123");

            Assert.IsTrue(sub.IsValid);
            Assert.AreEqual("[subscription]/WatchObjects", sub.Route.Path);
            Assert.AreEqual("abc123", sub.Id);
            Assert.AreEqual(field, sub.Field);
            Assert.AreEqual(testClient, sub.Client);
            Assert.AreEqual(queryData, sub.QueryData);
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="GraphOperationRequest"/> class.
 /// </summary>
 /// <param name="queryData">The query data.</param>
 public GraphOperationRequest(GraphQueryData queryData)
 {
     this.Id            = Guid.NewGuid().ToString("N");
     this.OperationName = queryData.OperationName?.Trim();
     this.QueryText     = queryData.Query;
     this.VariableData  = queryData.Variables ?? new InputVariableCollection();
 }
Example #4
0
        /// <summary>
        /// Creates a mocked http context that can be passed through a query processor for testing with the given
        /// query data on the request.
        /// </summary>
        /// <param name="requestData">The request data.</param>
        /// <returns>HttpContext.</returns>
        public HttpContext CreateHttpContext(GraphQueryData requestData = null)
        {
            var requestStream  = new MemoryStream();
            var responseStream = new MemoryStream();

            var httpContext = new DefaultHttpContext();

            httpContext.User            = _userAccount;
            httpContext.RequestServices = this.ServiceProvider;
            httpContext.Response.Body   = responseStream;
            httpContext.Request.Method  = HttpMethods.Post.ToUpper();
            httpContext.Request.Body    = requestStream;

            if (requestData != null)
            {
                var writerOptions = new JsonWriterOptions()
                {
                    Indented = this.Schema.Configuration.ResponseOptions.IndentDocument,
                };

                var serializerSettings = new JsonSerializerOptions();
                serializerSettings.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

                using (var writer = new Utf8JsonWriter(requestStream, writerOptions))
                {
                    JsonSerializer.Serialize(writer, requestData, serializerSettings);
                    requestStream.Seek(0, SeekOrigin.Begin);
                }
            }

            return(httpContext);
        }
Example #5
0
        /// <summary>
        /// Submits the GraphQL query for processing.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IActionResult&gt;.</returns>
        public override async Task SubmitGraphQLQuery(GraphQueryData queryData)
        {
            if (this.User?.Identity == null || !this.User.Identity.IsAuthenticated)
            {
                await this.WriteStatusCodeResponse(HttpStatusCode.Unauthorized, ERROR_UNAUTHORIZED).ConfigureAwait(false);

                return;
            }

            await base.SubmitGraphQLQuery(queryData);
        }
        /// <summary>
        /// Executes the GraphQL query. When overriden in a child class allows the class to override the default behavior of
        /// processing a query against the GraphQL runtime and writing the result to the <see cref="HttpResponse"/>.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IGraphOperationResult&gt;.</returns>
        protected virtual async Task ExecuteGraphQLQuery(GraphQueryData queryData)
        {
            using var cancelSource = new CancellationTokenSource();

            try
            {
                // *******************************
                // Setup
                // *******************************
                this.GraphQLRequest = _runtime.CreateRequest(queryData);
                if (this.GraphQLRequest == null)
                {
                    await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_NO_REQUEST_CREATED).ConfigureAwait(false);

                    return;
                }

                // *******************************
                // Primary query execution
                // *******************************
                var queryResponse = await _runtime
                                    .ExecuteRequest(
                    this.HttpContext.RequestServices,
                    this.HttpContext.User,
                    this.GraphQLRequest,
                    this.EnableMetrics)
                                    .ConfigureAwait(false);

                // if any metrics were populated in the execution, allow a child class to process them
                if (queryResponse.Metrics != null)
                {
                    this.HandleQueryMetrics(queryResponse.Metrics);
                }

                // all done, finalize and return
                queryResponse = this.FinalizeResult(queryResponse);
                await this.WriteResponse(queryResponse).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                var exceptionResult = this.HandleQueryException(ex);
                if (exceptionResult == null)
                {
                    // no one was able to handle hte exception. Log it if able and just fail out to the caller
                    _logger?.UnhandledExceptionEvent(ex);
                    await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_INTERNAL_SERVER_ISSUE).ConfigureAwait(false);
                }
                else
                {
                    await this.WriteResponse(exceptionResult).ConfigureAwait(false);
                }
            }
        }
        /// <summary>
        /// Submits the request data to the GraphQL runtime for processing. When overloading in a child class, allows the class
        /// to interject and alter the <paramref name="queryData"/> just prior to it being executed by the graphql runtime.
        /// </summary>
        /// <param name="queryData">The query data parsed from an <see cref="HttpRequest"/>; may be null.</param>
        /// <returns>Task&lt;IActionResult&gt;.</returns>
        public virtual async Task SubmitGraphQLQuery(GraphQueryData queryData)
        {
            // ensure data was received
            if (queryData == null || string.IsNullOrWhiteSpace(queryData.Query))
            {
                await this.WriteStatusCodeResponse(HttpStatusCode.BadRequest, ERROR_NO_QUERY_PROVIDED).ConfigureAwait(false);

                return;
            }

            await this.ExecuteGraphQLQuery(queryData).ConfigureAwait(false);
        }
        /// <summary>
        /// Submits the GraphQL query for processing.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IActionResult&gt;.</returns>
        public override Task SubmitGraphQLQuery(GraphQueryData queryData)
        {
            // Deny ALL graph ql requests from being sent to a client between 1am and 4am
            // Place a breakpoint here and/or alter the hours to see that its being invoked.
            if (DateTime.UtcNow.Hour >= 1 && DateTime.UtcNow.Hour <= 4)
            {
                var response = this.ErrorMessageAsGraphQLResponse(
                    "This service denys all queries between 1am and 4am (UTC-0). We're making the donuts!");

                return(this.WriteResponse(response));
            }
            else
            {
                return(base.SubmitGraphQLQuery(queryData));
            }
        }
        /// <summary>
        /// Executes the query.
        /// </summary>
        /// <param name="queryText">The query text.</param>
        /// <param name="jsonText">The json text.</param>
        /// <returns>Task.</returns>
        private async Task ExecuteQuery(string queryText, string jsonText = "{}")
        {
            // top level services to execute the query
            var queryPipeline = _serviceProvider.GetService <ISchemaPipeline <GraphSchema, GraphQueryExecutionContext> >();
            var writer        = _serviceProvider.GetService <IGraphResponseWriter <GraphSchema> >();

            // parse the json doc, simulating a request recieved to the QueryController
            var inputVars = InputVariableCollection.FromJsonDocument(jsonText);

            var query = new GraphQueryData()
            {
                Query     = queryText,
                Variables = inputVars ?? InputVariableCollection.Empty,
            };

            var request = new GraphOperationRequest(query);
            var context = new GraphQueryExecutionContext(request, _serviceProvider, null);

            // execute
            await queryPipeline.InvokeAsync(context, default);

            var response = context.Result;

            if (response.Messages.Count > 0)
            {
                throw new InvalidOperationException("Query failed: " + response.Messages[0].Message);
            }

            // simulate writing hte result to an output stream
            string result = null;

            using (var memStream = new MemoryStream())
            {
                await writer.WriteAsync(memStream, response);

                memStream.Seek(0, SeekOrigin.Begin);
                using (var streamReader = new StreamReader(memStream))
                    result = streamReader.ReadToEnd();
            }

            if (result == null)
            {
                throw new InvalidOperationException("Query failed: No Response was serialized");
            }
        }
Example #10
0
        /// <summary>
        /// Submits the GraphQL query for processing.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IActionResult&gt;.</returns>
        public virtual async Task SubmitGraphQLQuery(GraphQueryData queryData)
        {
            // ensure data was received
            if (queryData == null || string.IsNullOrWhiteSpace(queryData.Query))
            {
                await this.WriteStatusCodeResponse(HttpStatusCode.BadRequest, ERROR_NO_QUERY_PROVIDED).ConfigureAwait(false);

                return;
            }

            using (var cancelSource = new CancellationTokenSource())
            {
                try
                {
                    // *******************************
                    // Setup
                    // *******************************
                    this.GraphQLRequest = this.CreateRequest(queryData);
                    if (this.GraphQLRequest == null)
                    {
                        await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_NO_REQUEST_CREATED).ConfigureAwait(false);

                        return;
                    }

                    // *******************************
                    // Primary query execution
                    // *******************************
                    var metricPackage = this.EnableMetrics ? _metricsFactory.CreateMetricsPackage() : null;
                    var context       = new GraphQueryExecutionContext(
                        this.GraphQLRequest,
                        this.HttpContext.RequestServices,
                        this.HttpContext.User,
                        metricPackage,
                        _logger);

                    await _queryPipeline.InvokeAsync(context, cancelSource.Token).ConfigureAwait(false);

                    // *******************************
                    // Response Generation
                    // *******************************
                    var queryResponse = context.Result;
                    if (queryResponse == null)
                    {
                        queryResponse = this.ErrorMessageAsGraphQLResponse(ERROR_NO_RESPONSE);
                    }

                    // if any metrics were populated in the execution, allow a child class to process them
                    if (context.Metrics != null)
                    {
                        this.HandleQueryMetrics(context.Metrics);
                    }

                    // all done, finalize and return
                    queryResponse = this.FinalizeResult(queryResponse);
                    await this.WriteResponse(queryResponse).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    var exceptionResult = this.HandleQueryException(ex);
                    if (exceptionResult == null)
                    {
                        // no one was able to handle hte exception. Log it if able and just fail out to the caller
                        _logger?.UnhandledExceptionEvent(ex);
                        await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_INTERNAL_SERVER_ISSUE).ConfigureAwait(false);
                    }
                    else
                    {
                        await this.WriteResponse(exceptionResult).ConfigureAwait(false);
                    }
                }
            }
        }
        private (IGraphQLHttpProcessor <GraphSchema> Processor, HttpContext Context) CreateQueryArtifacts(GraphQueryData data = null)
        {
            var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames);

            builder.AddGraphType <CandyController>();
            var server = builder.Build();

            return(server.CreateHttpQueryProcessor(), server.CreateHttpContext(data));
        }
        /// <summary>
        /// Creates a new operation result from a collection of generated messages and optional raw data
        /// provided by a requestor.
        /// </summary>
        /// <param name="errorMessages">The collection of messages. Must be not null and contain at least one message.</param>
        /// <param name="queryData">The original query data.</param>
        /// <returns>GraphOperationResult.</returns>
        public static GraphOperationResult FromMessages(IGraphMessageCollection errorMessages, GraphQueryData queryData = null)
        {
            Validation.ThrowIfNull(errorMessages, nameof(errorMessages));
            if (errorMessages.Count < 1)
            {
                errorMessages.Critical("An unknown error occured.");
            }

            return(new GraphOperationResult(
                       new GraphOperationRequest(queryData),
                       errorMessages));
        }
 /// <inheritdoc />
 public IGraphOperationRequest CreateRequest(GraphQueryData queryData = null)
 {
     return(new GraphOperationRequest(queryData ?? GraphQueryData.Empty));
 }
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientSubscription{TSchema}" /> class.
        /// </summary>
        /// <param name="clientProxy">The client proxy that will own this subscription.</param>
        /// <param name="originalQuerydata">The original querydata that generated this subscription.</param>
        /// <param name="queryPlan">The query plan.</param>
        /// <param name="selectedOperation">The selected operation from the query plan
        /// from which to generate the subscription.</param>
        /// <param name="subscriptionid">A unique id to assign to this subscription. A guid id
        /// will be generated if this value is not supplied.</param>
        public ClientSubscription(
            ISubscriptionClientProxy clientProxy,
            GraphQueryData originalQuerydata,
            IGraphQueryPlan queryPlan,
            IGraphFieldExecutableOperation selectedOperation,
            string subscriptionid = null)
        {
            this.Client         = Validation.ThrowIfNullOrReturn(clientProxy, nameof(clientProxy));
            this.QueryData      = Validation.ThrowIfNullOrReturn(originalQuerydata, nameof(originalQuerydata));
            this.QueryOperation = Validation.ThrowIfNullOrReturn(selectedOperation, nameof(selectedOperation));
            this.QueryPlan      = Validation.ThrowIfNullOrReturn(queryPlan, nameof(queryPlan));
            this.Messages       = this.QueryPlan?.Messages ?? new GraphMessageCollection();

            this.Id = string.IsNullOrWhiteSpace(subscriptionid)
                ? Guid.NewGuid().ToString("N")
                : subscriptionid.Trim();

            this.IsValid = false;

            // parsing the query plan will garuntee that if the document contains
            // a subscription that it contains only one operation and
            // that a top level field will be a subscription-ready field.
            //
            // However, ensure that the operation that will be executed
            // does in fact represent a subscription being harnesssed
            if (this.QueryOperation.OperationType != GraphCollection.Subscription)
            {
                this.Messages.Critical(
                    $"The chosen operation is not a subscription operation.",
                    Constants.ErrorCodes.BAD_REQUEST);
                return;
            }

            var currentContext = this.QueryOperation.FieldContexts[0];

            // find the first non-virtual field referenced, it should be a controller
            // its garunteed to exist via the document generation rule engine
            // but it could be deep, walk down the subscirption tree to find it
            while (currentContext?.Field != null)
            {
                // when pointing at a subscription field we're done
                if (!currentContext.Field.IsVirtual)
                {
                    this.Field = currentContext.Field as ISubscriptionGraphField;
                    break;
                }

                currentContext = currentContext?.ChildContexts.Count == 1 ? currentContext.ChildContexts?[0] : null;
            }

            // just in case it wasn't found...
            // this is theoretically not possible but just in case
            // the user swaps out some DI components incorrectly or by mistake...
            if (this.Field == null)
            {
                this.Messages.Add(
                    GraphMessageSeverity.Critical,
                    "An eventable field could not found in the subscription operation. Ensure you include a field declared " +
                    "as a subscription field.",
                    Constants.ErrorCodes.BAD_REQUEST);
            }

            this.IsValid = this.Messages.IsSucessful && this.QueryOperation != null && this.Field != null;
        }