Skip to content

Commit

Permalink
Merge pull request #67 from graphql-dotnet/invalid-empty-and-profiling
Browse files Browse the repository at this point in the history
Invalidate empty query, and profiling extensions
  • Loading branch information
tlil authored Aug 22, 2017
2 parents 4d0d1bc + 6c685a4 commit 22af047
Show file tree
Hide file tree
Showing 16 changed files with 277 additions and 7 deletions.
6 changes: 5 additions & 1 deletion deps/graphql-dotnet/src/GraphQL/Execution/ExecutionResult.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GraphQL.Instrumentation;
using System.Collections.Generic;
using GraphQL.Instrumentation;
using GraphQL.Language.AST;
using Newtonsoft.Json;

Expand All @@ -11,6 +12,9 @@ public class ExecutionResult

public ExecutionErrors Errors { get; set; }

public Dictionary<string, object> Extra { get; private set; } =
new Dictionary<string, object>();

public string Query { get; set; }

public Document Document { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s

writeData(result, writer, serializer);
writeErrors(result.Errors, writer, serializer, result.ExposeExceptions);
writeExtra(result, writer, serializer);

writer.WriteEndObject();
}
Expand Down Expand Up @@ -113,6 +114,23 @@ private void writeErrors(ExecutionErrors errors, JsonWriter writer, JsonSerializ
writer.WriteEndArray();
}

private void writeExtra(ExecutionResult result, JsonWriter writer, JsonSerializer serializer)
{
if (result.Extra == null || result.Extra.Count == 0)
{
return;
}

writer.WritePropertyName("extra");
writer.WriteStartObject();
result.Extra.Apply(kvp =>
{
writer.WritePropertyName(kvp.Key);
serializer.Serialize(writer, kvp.Value);
});
writer.WriteEndObject();
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
Expand Down
4 changes: 2 additions & 2 deletions src/GraphQL.Conventions/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
[assembly: AssemblyCopyright("Copyright 2016-2017 Tommy Lillehagen. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.3.4")]
[assembly: AssemblyInformationalVersion("1.3.4")]
[assembly: AssemblyFileVersion("1.3.5")]
[assembly: AssemblyInformationalVersion("1.3.5")]
[assembly: CLSCompliant(false)]

[assembly: InternalsVisibleTo("Tests")]
59 changes: 59 additions & 0 deletions src/GraphQL.Conventions/Extensions/ChaosAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using GraphQL.Conventions;
using GraphQL.Conventions.Attributes;
using GraphQL.Conventions.Attributes.Collectors;
using GraphQL.Conventions.Execution;
using GraphQL.Conventions.Types.Descriptors;

namespace GraphQL.Conventions.Extensions
{
public class ChaosAttribute : ExecutionFilterAttributeBase
{
public static bool IsEnabled = false;

private const int DefaultSuccessRate = 50;

private static readonly Random _random = new Random();

private readonly int _successRate;

public ChaosAttribute(int successRate = DefaultSuccessRate)
{
_successRate = successRate;
}

public override Task<object> Execute(IResolutionContext context, FieldResolutionDelegate next)
{
if (_random.Next(0, 100) > _successRate)
{
var path = $"{context.FieldInfo.DeclaringType.Name}.{context.FieldInfo.Name}";
throw new ChaosException($"Only {_successRate} % of requests will succeed.", path);
}
return next(context);
}
}

public class ChaosMetaDataAttribute : MetaDataAttributeBase, IDefaultAttribute
{
public override void MapField(GraphFieldInfo entity, MemberInfo memberInfo)
{
if (ChaosAttribute.IsEnabled)
{
entity.ExecutionFilters.Add(new ChaosAttribute());
}
}
}

public class ChaosException : Exception
{
public ChaosException(string message, string path)
: base(message)
{
Path = path;
}

public string Path { get; private set; }
}
}
23 changes: 23 additions & 0 deletions src/GraphQL.Conventions/Extensions/NameNormalizerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Reflection;
using GraphQL.Conventions.Attributes;
using GraphQL.Conventions.Attributes.Collectors;
using GraphQL.Conventions.Types.Descriptors;

namespace GraphQL.Conventions.Extensions
{
public class NameNormalizerAttribute : MetaDataAttributeBase, IDefaultAttribute
{
public NameNormalizerAttribute()
: base(AttributeApplicationPhase.Override)
{
}

public override void MapType(GraphTypeInfo entity, TypeInfo typeInfo)
{
if (entity.Name?.EndsWith("Dto") ?? false)
{
entity.Name = entity.Name.Remove(entity.Name.Length - 3);
}
}
}
}
26 changes: 26 additions & 0 deletions src/GraphQL.Conventions/Extensions/Profiling/PerformanceRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace GraphQL.Conventions.Extensions
{
public class PerformanceRecord
{
[JsonProperty(PropertyName = "path")]
public string Path { get; set; }

[JsonProperty(PropertyName = "start")]
public long StartTimeInMs { get; set; }

[JsonProperty(PropertyName = "end")]
public long EndTimeInMs { get; set; }

[JsonProperty(PropertyName = "type")]
public string ParentType { get; set; }

[JsonProperty(PropertyName = "field")]
public string Field { get; set; }

[JsonProperty(PropertyName = "args")]
public Dictionary<string, object> Arguments { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using GraphQL.Conventions;
using GraphQL.Conventions.Web;

namespace GraphQL.Conventions.Extensions
{
public static class ProfilingResultEnricher
{
public static void EnrichWithProfilingInformation(this Response response)
{
var perf = response?.ExecutionResult?.Perf;
if (perf == null) { return; }

var records = new List<PerformanceRecord>();
foreach (var record in perf)
{
if (record.Category != "field") { continue; }

records.Add(new PerformanceRecord
{
Path = string.Join(".", record.Metadata["path"] as List<string>),
StartTimeInMs = record.Start,
EndTimeInMs = record.End,
ParentType = record.Metadata["typeName"] as string,
Field = record.Metadata["fieldName"] as string,
Arguments = record.Metadata["arguments"] as Dictionary<string, object>,
});
}

if (records.Any())
{
response.AddExtra("profile", records.OrderBy(record => record.Path));
}
}
}
}
26 changes: 26 additions & 0 deletions src/GraphQL.Conventions/Extensions/Utilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using GraphQL.Conventions;
using GraphQL.Conventions.Web;

namespace GraphQL.Conventions.Extensions
{
public static class Utilities
{
public static string IdentifierForTypeOrNull<T>(this string id) =>
id.IsIdentifierForType<T>() ? id.IdentifierForType<T>() : null;

public static string IdentifierForTypeOrNull<T>(this NonNull<string> id) =>
id.IsIdentifierForType<T>() ? id.IdentifierForType<T>() : null;

public static string IdentifierForType<T>(this string id) =>
new Id(id).IdentifierForType<T>();

public static string IdentifierForType<T>(this NonNull<string> id) =>
id.Value.IdentifierForType<T>();

public static bool IsIdentifierForType<T>(this string id) =>
new Id(id).IsIdentifierForType<T>();

public static bool IsIdentifierForType<T>(this NonNull<string> id) =>
id.Value.IsIdentifierForType<T>();
}
}
2 changes: 1 addition & 1 deletion src/GraphQL.Conventions/GraphQL.Conventions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>GraphQL Conventions for .NET</Description>
<VersionPrefix>1.3.4</VersionPrefix>
<VersionPrefix>1.3.5</VersionPrefix>
<Authors>Tommy Lillehagen</Authors>
<TargetFrameworks>netstandard1.5;net45</TargetFrameworks>
<DebugType>portable</DebugType>
Expand Down
5 changes: 5 additions & 0 deletions src/GraphQL.Conventions/Web/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public static Request InvalidInput(Exception exception)
: this()
{
_queryInput = queryInput;

if (string.IsNullOrWhiteSpace(_queryInput.QueryString))
{
_exception = new ArgumentException($"Empty query string");
}
}

Request(Exception exception)
Expand Down
28 changes: 27 additions & 1 deletion src/GraphQL.Conventions/Web/Response.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GraphQL.Http;

namespace GraphQL.Conventions.Web
{
public class Response
{
private static DocumentWriter _writer = new DocumentWriter();

private string _body;

public Response(
Request request,
ExecutionResult result)
Expand All @@ -29,7 +35,27 @@ public Response(

public Validation.IValidationResult ValidationResult { get; private set; }

public string Body { get; internal set; }
public string Body
{
get
{
if (string.IsNullOrWhiteSpace(_body) && ExecutionResult != null)
{
_body = _writer.Write(ExecutionResult);
}
return _body;
}
internal set
{
_body = value;
}
}

public void AddExtra(string key, object value)
{
ExecutionResult.Extra[key] = value;
_body = null;
}

public bool HasData => ExecutionResult?.Data != null;

Expand Down
2 changes: 1 addition & 1 deletion src/GraphQL.Conventions/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.3.4-*",
"version": "1.3.5-*",
"description": "GraphQL Conventions for .NET",
"authors": [
"Tommy Lillehagen"
Expand Down
1 change: 1 addition & 0 deletions test/Tests/Adapters/Engine/GraphQLExecutorTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GraphQL.Conventions.Tests.Templates;
using GraphQL.Conventions.Tests.Templates.Extensions;

Expand Down
24 changes: 24 additions & 0 deletions test/Tests/Web/RequestHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using GraphQL.Conventions.Extensions;
using GraphQL.Conventions.Tests.Templates;
using GraphQL.Conventions.Tests.Templates.Extensions;
using GraphQL.Conventions.Web;
Expand Down Expand Up @@ -73,9 +74,32 @@ public async void Cannot_Run_Too_Complex_Query_Using_ComplexityConfiguration()
response.Errors[0].Message.ShouldEqual("Query is too nested to execute. Depth is 2 levels, maximum allowed on this endpoint is 1.");
}

[Test]
public async void Can_Enrich_With_Profiling_Information()
{
var request = Request.New("{ \"query\": \"{ a: foo(ms: 10) b: foo(ms: 20) }\" }");
var response = await RequestHandler
.New()
.WithQuery<ProfiledQuery>()
.WithProfiling()
.Generate()
.ProcessRequest(request, null);
response.EnrichWithProfilingInformation();
response.Body.ShouldContain("\"extra\":{\"profile\":");
}

class TestQuery
{
public string Hello => "World";
}

class ProfiledQuery
{
public async Task<int> Foo(int ms)
{
await Task.Delay(ms);
return ms;
}
}
}
}
2 changes: 1 addition & 1 deletion test/Tests/Web/RequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void Cannot_Instantiate_Request_Object_From_Invalid_String()
public void Cannot_Derive_Query_From_Invalid_String()
{
var request = Request.New("{\"invalid_query\":\"{}\"}");
request.IsValid.ShouldEqual(true);
request.IsValid.ShouldEqual(false);
request.QueryString.ShouldEqual(string.Empty);
}
}
Expand Down
21 changes: 21 additions & 0 deletions test/Tests/Web/ResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using GraphQL.Conventions.Tests.Templates.Extensions;
using GraphQL.Conventions.Web;
using GraphQL.Validation;
using Newtonsoft.Json;

namespace GraphQL.Conventions.Tests.Web
{
Expand All @@ -28,5 +29,25 @@ public void Can_Instantiate_Response_Object_From_Validation_Result()
var response = new Response(request, result);
response.ValidationResult.Errors.Count.ShouldEqual(1);
}

[Test]
public void Can_Instantiate_Response_Object_With_Extra_Data()
{
var request = Request.New("{\"query\":\"{}\"}");
var result = new ExecutionResult();
result.Data = new Dictionary<string, object>();
result.Extra["trace"] = new
{
foo = 1,
bar = new
{
baz = "hello",
},
};
var response = new Response(request, result);
response.HasData.ShouldEqual(true);
response.HasErrors.ShouldEqual(false);
response.Body.ShouldEqual("{\"data\":{},\"extra\":{\"trace\":{\"foo\":1,\"bar\":{\"baz\":\"hello\"}}}}");
}
}
}

0 comments on commit 22af047

Please sign in to comment.