/// <summary> /// Formats the output of the metrics into a set of nested key/value pairs that can be written to a graph /// ql data response. The keys returned will be written directly to the "extensions" section of the graphql response. /// It is recommended to return a single toplevel key containing the entirety of your metrics data. /// </summary> /// <returns>KeyValuePair<System.String, System.Object>.</returns> public virtual IResponseFieldSet GenerateResult() { var results = new ResponseFieldSet(); results.AddSingleValue("version", VERSION); // the apollo tracing specification says the date MUST be in RFC3339 format // do not leave the formatting up to the serializer. results.AddSingleValue("startTime", this.StartDate); results.AddSingleValue("endTime", this.StartDate.AddTicks(this.TotalTicks)); results.AddSingleValue("duration", this.TotalTicks); if (_phaseEntries.TryGetValue(ApolloExecutionPhase.PARSING, out var entry)) { results.Add("parsing", this.GeneratePhaseResult(entry)); } if (_phaseEntries.TryGetValue(ApolloExecutionPhase.VALIDATION, out entry)) { results.Add("validation", this.GeneratePhaseResult(entry)); } results.Add("execution", this.GenerateExecutionResult()); var finalResult = new ResponseFieldSet(); finalResult.Add("tracing", results); return(finalResult); }
/// <summary> /// Generates the execution result, a list of individual results for each resolver tracked. /// </summary> /// <returns>IDictionary<System.String, System.Object>.</returns> private IResponseFieldSet GenerateExecutionResult() { // apollo tracing does not specifiy providing startOffset and duration keys for the execution // phase, just output the resolvers array var list = new ResponseList(); foreach (var timeEntry in _resolverEntries.OrderBy(x => x.Value.StartOffsetTicks)) { var resolverEntry = new ResponseFieldSet(); resolverEntry.Add("path", new ResponseList(timeEntry .Key .Request .Origin .Path .ToArray() .Select(x => new ResponseSingleValue(x)))); var parentType = _schema.KnownTypes.FindGraphType(timeEntry.Key.Request?.DataSource?.Value); if (parentType != null) { resolverEntry.AddSingleValue("parentType", parentType.Name); } resolverEntry.AddSingleValue("fieldName", timeEntry.Key.Request.Field.Name); resolverEntry.AddSingleValue("returnType", timeEntry.Key.Request.Field.TypeExpression.ToString()); resolverEntry.AddSingleValue("startOffset", timeEntry.Value.StartOffsetNanoseconds); resolverEntry.AddSingleValue("duration", timeEntry.Value.DurationNanoSeconds); list.Add(resolverEntry); } var dictionary = new ResponseFieldSet(); dictionary.Add("resolvers", list); return(dictionary); }
/// <summary> /// Convert the data items that were generated pushing their results into a final top level dictionary /// to be returned as the graph projection. Takes care of an final messaging in case one of the tasks failed. /// </summary> /// <param name="context">The context.</param> /// <returns>GraphQL.AspNet.Interfaces.Response.IResponseFieldSet.</returns> private IResponseFieldSet CreateFinalDictionary(GraphQueryExecutionContext context) { var topFieldResponses = new ResponseFieldSet(); foreach (var fieldResult in context.FieldResults) { var generated = fieldResult.GenerateResult(out var result); if (generated) { topFieldResponses.Add(fieldResult.Name, result); } } if (topFieldResponses.Fields.Count == 0 || topFieldResponses.Fields.All(x => x.Value == null)) { topFieldResponses = null; } return(topFieldResponses); }
/// <summary> /// Generates a result representing the individual child fields of this instance. This result /// is built in a manner that can be easily serialized. /// </summary> /// <param name="result">The result that was generated.</param> /// <returns><c>true</c> if the result that was generated is valid and should /// be included in a final result, <c>false</c> otherwise.</returns> private bool GenerateFieldListResult(out IResponseItem result) { result = null; var includeResult = false; // this instance represents a set of key/value pair fields // create a dictionary of those kvps as the result var fieldSet = new ResponseFieldSet(); foreach (var field in _childFields) { var includeChildResult = field.GenerateResult(out var childResult); includeResult = includeResult || includeChildResult; if (includeChildResult) { if (fieldSet.Fields.ContainsKey(field.FieldContext.Name)) { throw new GraphExecutionException( $"Duplicate field name. The field '{field.Name}' at '{this.Origin.Path.DotString()}' was resolved " + $"more than once for a source object, unable to generate a valid output. " + $"Field collections require unique names. An attempt was made to add the field '{field.Name}', " + $"for target type '{field.FieldContext.ExpectedSourceType?.FriendlyName() ?? "-all-"}' when the field " + "name was already present in the output dictionary.", this.Origin, new InvalidOperationException($"The source object '{this.SourceData}' successfully resolved a field name of '{field.Name}' more than once when it shouldn't. This may occur if a source " + "object type is referenced to to multiple target graph types in fragment references. Ensure that your source data uniquely maps to one fragment per field collection " + "or that the fragments do not share property names.")); } fieldSet.Add(field.FieldContext.Name, childResult); } } if (includeResult) { result = fieldSet; } return(includeResult); }
public async Task EnsureProperStringEscapement(string testValue, string expectedValue) { var fixedDate = DateTimeOffset.UtcNow.UtcDateTime; var builder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); builder.AddGraphQL(o => { o.ResponseOptions.TimeStampLocalizer = (_) => fixedDate; }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); var dic = new ResponseFieldSet(); dic.Add("testKey", new ResponseSingleValue(testValue)); var mockResult = new Mock <IGraphOperationResult>(); mockResult.Setup(x => x.Data).Returns(dic); mockResult.Setup(x => x.Messages).Returns(new GraphMessageCollection()); mockResult.Setup(x => x.Request).Returns(queryBuilder.OperationRequest); var writer = new DefaultResponseWriter <GraphSchema>(server.Schema); var result = await this.WriteResponse(writer, mockResult.Object); var expected = @" { ""data"" : { ""testKey"" : """ + expectedValue + @""", } }"; CommonAssertions.AreEqualJsonStrings(expected, result); }