Skip to content

Commit 6824099

Browse files
tobias-tenglermichaelstaib
authored andcommitted
Fixed parsing issue in Utf8GraphQLRequestParser#ParseStringOrNull (#7703)
1 parent 8e94289 commit 6824099

File tree

13 files changed

+600
-2
lines changed

13 files changed

+600
-2
lines changed

src/HotChocolate/Caching/src/Caching/Extensions/QueryCacheRequestExecutorBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static IRequestExecutorBuilder UseQueryCachePipeline(
4242
.UseDocumentValidation()
4343
.UseOperationCache()
4444
.UseOperationResolver()
45+
.UseSkipWarmupExecution()
4546
.UseOperationVariableCoercion()
4647
.UseOperationExecution();
4748
}

src/HotChocolate/Core/src/Abstractions/Execution/ExecutionResultKind.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public enum ExecutionResultKind
2424
/// A subscription response stream.
2525
/// </summary>
2626
SubscriptionResult,
27+
28+
/// <summary>
29+
/// A no-op result for warmup requests.
30+
/// </summary>
31+
WarmupResult,
2732
}

src/HotChocolate/Core/src/Abstractions/Execution/OperationRequestBuilderExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,11 @@ public static OperationRequestBuilder SetUser(
7474
this OperationRequestBuilder builder,
7575
ClaimsPrincipal claimsPrincipal)
7676
=> builder.SetGlobalState(nameof(ClaimsPrincipal), claimsPrincipal);
77+
78+
/// <summary>
79+
/// Marks this request as a warmup request that will bypass security measures and skip execution.
80+
/// </summary>
81+
public static OperationRequestBuilder MarkAsWarmupRequest(
82+
this OperationRequestBuilder builder)
83+
=> builder.SetGlobalState(WellKnownContextData.IsWarmupRequest, true);
7784
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace HotChocolate.Execution;
2+
3+
public sealed class WarmupExecutionResult : ExecutionResult
4+
{
5+
public override ExecutionResultKind Kind => ExecutionResultKind.WarmupResult;
6+
7+
public override IReadOnlyDictionary<string, object?>? ContextData => null;
8+
}

src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,9 @@ public static class WellKnownContextData
328328
/// The key to access the compiled requirements.
329329
/// </summary>
330330
public const string FieldRequirements = "HotChocolate.Types.ObjectField.Requirements";
331+
332+
/// <summary>
333+
/// The key to determine whether the request is a warmup request.
334+
/// </summary>
335+
public const string IsWarmupRequest = "HotChocolate.AspNetCore.Warmup.IsWarmupRequest";
331336
}

src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ public static IRequestExecutorBuilder UseOperationVariableCoercion(
120120
this IRequestExecutorBuilder builder) =>
121121
builder.UseRequest(OperationVariableCoercionMiddleware.Create());
122122

123+
public static IRequestExecutorBuilder UseSkipWarmupExecution(
124+
this IRequestExecutorBuilder builder) =>
125+
builder.UseRequest(SkipWarmupExecutionMiddleware.Create());
126+
123127
public static IRequestExecutorBuilder UseReadPersistedOperation(
124128
this IRequestExecutorBuilder builder) =>
125129
builder.UseRequest(ReadPersistedOperationMiddleware.Create());
@@ -191,6 +195,7 @@ public static IRequestExecutorBuilder UsePersistedOperationPipeline(
191195
.UseDocumentValidation()
192196
.UseOperationCache()
193197
.UseOperationResolver()
198+
.UseSkipWarmupExecution()
194199
.UseOperationVariableCoercion()
195200
.UseOperationExecution();
196201
}
@@ -215,6 +220,7 @@ public static IRequestExecutorBuilder UseAutomaticPersistedOperationPipeline(
215220
.UseDocumentValidation()
216221
.UseOperationCache()
217222
.UseOperationResolver()
223+
.UseSkipWarmupExecution()
218224
.UseOperationVariableCoercion()
219225
.UseOperationExecution();
220226
}
@@ -229,6 +235,7 @@ internal static void AddDefaultPipeline(this IList<RequestCoreMiddleware> pipeli
229235
pipeline.Add(DocumentValidationMiddleware.Create());
230236
pipeline.Add(OperationCacheMiddleware.Create());
231237
pipeline.Add(OperationResolverMiddleware.Create());
238+
pipeline.Add(SkipWarmupExecutionMiddleware.Create());
232239
pipeline.Add(OperationVariableCoercionMiddleware.Create());
233240
pipeline.Add(OperationExecutionMiddleware.Create());
234241
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace HotChocolate.Execution;
2+
3+
public static class WarmupRequestExecutorExtensions
4+
{
5+
public static bool IsWarmupRequest(this IRequestContext requestContext)
6+
=> requestContext.ContextData.ContainsKey(WellKnownContextData.IsWarmupRequest);
7+
}

src/HotChocolate/Core/src/Execution/Pipeline/OnlyPersistedOperationsAllowedMiddleware.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ private OnlyPersistedOperationsAllowedMiddleware(
3838

3939
public ValueTask InvokeAsync(IRequestContext context)
4040
{
41-
// if all operations are allowed we can skip this middleware.
42-
if(!_options.OnlyAllowPersistedDocuments)
41+
// if all operations are allowed or the request is a warmup request, we can skip this middleware.
42+
if(!_options.OnlyAllowPersistedDocuments || context.IsWarmupRequest())
4343
{
4444
return _next(context);
4545
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace HotChocolate.Execution.Pipeline;
2+
3+
internal sealed class SkipWarmupExecutionMiddleware(RequestDelegate next)
4+
{
5+
public async ValueTask InvokeAsync(IRequestContext context)
6+
{
7+
if (context.IsWarmupRequest())
8+
{
9+
context.Result = new WarmupExecutionResult();
10+
return;
11+
}
12+
13+
await next(context).ConfigureAwait(false);
14+
}
15+
16+
public static RequestCoreMiddleware Create()
17+
=> (_, next) =>
18+
{
19+
var middleware = new SkipWarmupExecutionMiddleware(next);
20+
return context => middleware.InvokeAsync(context);
21+
};
22+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using HotChocolate.Execution.Caching;
2+
using HotChocolate.Language;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Moq;
5+
6+
namespace HotChocolate.Execution;
7+
8+
public class WarmupRequestTests
9+
{
10+
[Fact]
11+
public async Task Warmup_Request_Warms_Up_Caches()
12+
{
13+
// arrange
14+
var executor = await new ServiceCollection()
15+
.AddGraphQL()
16+
.AddQueryType<Query>()
17+
.BuildRequestExecutorAsync();
18+
19+
var documentId = "f614e9a2ed367399e87751d41ca09105";
20+
var warmupRequest = OperationRequestBuilder.New()
21+
.SetDocument("query test($name: String!) { greeting(name: $name) }")
22+
.SetDocumentId(documentId)
23+
.MarkAsWarmupRequest()
24+
.Build();
25+
26+
var regularRequest = OperationRequestBuilder.New()
27+
.SetDocumentId(documentId)
28+
.SetVariableValues(new Dictionary<string, object?> { ["name"] = "Foo" })
29+
.Build();
30+
31+
// act 1
32+
var warmupResult = await executor.ExecuteAsync(warmupRequest);
33+
34+
// assert 1
35+
Assert.IsType<WarmupExecutionResult>(warmupResult);
36+
37+
var provider = executor.Services.GetCombinedServices();
38+
var documentCache = provider.GetRequiredService<IDocumentCache>();
39+
var operationCache = provider.GetRequiredService<IPreparedOperationCache>();
40+
41+
Assert.True(documentCache.TryGetDocument(documentId, out _));
42+
Assert.Equal(1, operationCache.Count);
43+
44+
// act 2
45+
var regularResult = await executor.ExecuteAsync(regularRequest);
46+
var regularOperationResult = regularResult.ExpectOperationResult();
47+
48+
// assert 2
49+
Assert.Null(regularOperationResult.Errors);
50+
Assert.NotNull(regularOperationResult.Data);
51+
Assert.NotEmpty(regularOperationResult.Data);
52+
53+
Assert.True(documentCache.TryGetDocument(documentId, out _));
54+
Assert.Equal(1, operationCache.Count);
55+
}
56+
57+
[Fact]
58+
public async Task Warmup_Request_Can_Skip_Persisted_Operation_Check()
59+
{
60+
// arrange
61+
var executor = await new ServiceCollection()
62+
.AddGraphQL()
63+
.ConfigureSchemaServices(services =>
64+
{
65+
services.AddSingleton<IOperationDocumentStorage>(_ => new Mock<IOperationDocumentStorage>().Object);
66+
})
67+
.AddQueryType<Query>()
68+
.ModifyRequestOptions(options =>
69+
{
70+
options.PersistedOperations.OnlyAllowPersistedDocuments = true;
71+
})
72+
.UsePersistedOperationPipeline()
73+
.BuildRequestExecutorAsync();
74+
75+
var documentId = "f614e9a2ed367399e87751d41ca09105";
76+
var warmupRequest = OperationRequestBuilder.New()
77+
.SetDocument("query test($name: String!) { greeting(name: $name) }")
78+
.SetDocumentId(documentId)
79+
.MarkAsWarmupRequest()
80+
.Build();
81+
82+
// act
83+
var warmupResult = await executor.ExecuteAsync(warmupRequest);
84+
85+
// assert
86+
Assert.IsType<WarmupExecutionResult>(warmupResult);
87+
88+
var provider = executor.Services.GetCombinedServices();
89+
var documentCache = provider.GetRequiredService<IDocumentCache>();
90+
var operationCache = provider.GetRequiredService<IPreparedOperationCache>();
91+
92+
Assert.True(documentCache.TryGetDocument(documentId, out _));
93+
Assert.Equal(1, operationCache.Count);
94+
}
95+
96+
public class Query
97+
{
98+
public string Greeting(string name) => $"Hello {name}";
99+
}
100+
}

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ private static IRequestExecutorBuilder UseFusionDefaultPipeline(
564564
.UseDocumentValidation()
565565
.UseOperationCache()
566566
.UseOperationResolver()
567+
.UseSkipWarmupExecution()
567568
.UseOperationVariableCoercion()
568569
.UseDistributedOperationExecution();
569570
}
@@ -588,6 +589,7 @@ private static IRequestExecutorBuilder UseFusionPersistedOperationPipeline(
588589
.UseDocumentValidation()
589590
.UseOperationCache()
590591
.UseOperationResolver()
592+
.UseSkipWarmupExecution()
591593
.UseOperationVariableCoercion()
592594
.UseDistributedOperationExecution();
593595
}
@@ -612,6 +614,7 @@ private static IRequestExecutorBuilder UseFusionAutomaticPersistedOperationPipel
612614
.UseDocumentValidation()
613615
.UseOperationCache()
614616
.UseOperationResolver()
617+
.UseSkipWarmupExecution()
615618
.UseOperationVariableCoercion()
616619
.UseDistributedOperationExecution();
617620
}

0 commit comments

Comments
 (0)