/// <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(); }
/// <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); }
/// <summary> /// Submits the GraphQL query for processing. /// </summary> /// <param name="queryData">The query data.</param> /// <returns>Task<IActionResult>.</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<IGraphOperationResult>.</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<IActionResult>.</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<IActionResult>.</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"); } }
/// <summary> /// Submits the GraphQL query for processing. /// </summary> /// <param name="queryData">The query data.</param> /// <returns>Task<IActionResult>.</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; }