Skip to content

Commit b6f951f

Browse files
authored
Added Source Schema Settings to Hot Chocolate Core (#8510)
1 parent 68a2115 commit b6f951f

File tree

13 files changed

+311
-24
lines changed

13 files changed

+311
-24
lines changed

src/HotChocolate/AspNetCore/src/AspNetCore.CommandLine/Command/ExportCommand.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.CommandLine;
22
using System.Text;
3+
using System.Text.Json;
4+
using System.Text.Json.Nodes;
35
using HotChocolate.Execution;
4-
using HotChocolate.Execution.Configuration;
6+
using HotChocolate.Execution.Internal;
57
using Microsoft.Extensions.DependencyInjection;
68
using Microsoft.Extensions.Hosting;
79

@@ -46,31 +48,31 @@ private static async Task ExecuteAsync(
4648
{
4749
var schemaNames = provider.SchemaNames;
4850

49-
if(schemaNames.IsEmpty)
51+
if (schemaNames.IsEmpty)
5052
{
5153
console.WriteLine("No schemas registered.");
5254
return;
5355
}
5456

5557
schemaName = schemaNames.Contains(ISchemaDefinition.DefaultName)
5658
? ISchemaDefinition.DefaultName
57-
: schemaNames[1];
59+
: schemaNames[0];
5860
}
5961

6062
var executor = await provider.GetExecutorAsync(schemaName, cancellationToken);
6163

62-
var sdl = executor.Schema.ToString();
63-
6464
if (output is not null)
6565
{
66-
await File.WriteAllTextAsync(
67-
output.FullName,
68-
sdl,
69-
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),
70-
cancellationToken);
66+
var result = await SchemaFileExporter.Export(output.FullName, executor, cancellationToken);
67+
// ReSharper disable LocalizableElement
68+
console.WriteLine("Exported Files:");
69+
console.WriteLine($"- {result.SchemaFileName}");
70+
console.WriteLine($"- {result.SettingsFileName}");
71+
// ReSharper restore LocalizableElement
7172
}
7273
else
7374
{
75+
var sdl = executor.Schema.ToString();
7476
console.WriteLine(sdl);
7577
}
7678
}

src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public static GraphQLEndpointConventionBuilder MapGraphQL(
7474
{
7575
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
7676

77+
TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
78+
7779
path = path.ToString().TrimEnd('/');
7880
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
7981
var pattern = Parse(path + "/{**slug}");
@@ -189,6 +191,8 @@ public static GraphQLHttpEndpointConventionBuilder MapGraphQLHttp(
189191
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
190192
ArgumentNullException.ThrowIfNull(pattern);
191193

194+
TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
195+
192196
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
193197
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
194198

@@ -266,6 +270,8 @@ public static WebSocketEndpointConventionBuilder MapGraphQLWebSocket(
266270
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
267271
ArgumentNullException.ThrowIfNull(pattern);
268272

273+
TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
274+
269275
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
270276
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
271277

@@ -342,6 +348,8 @@ public static IEndpointConventionBuilder MapGraphQLSchema(
342348
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
343349
ArgumentNullException.ThrowIfNull(pattern);
344350

351+
TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
352+
345353
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
346354
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
347355

@@ -483,6 +491,8 @@ public static IEndpointConventionBuilder MapGraphQLPersistedOperations(
483491
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
484492
ArgumentNullException.ThrowIfNull(path);
485493

494+
TryResolveSchemaName(endpointRouteBuilder.ServiceProvider, ref schemaName);
495+
486496
schemaName ??= ISchemaDefinition.DefaultName;
487497
var group = endpointRouteBuilder.MapGroup(path);
488498
group.MapPersistedOperationMiddleware(endpointRouteBuilder.ServiceProvider, schemaName, requireOperationName);
@@ -572,6 +582,16 @@ public static WebSocketEndpointConventionBuilder WithOptions(
572582
GraphQLSocketOptions socketOptions) =>
573583
builder.WithMetadata(new GraphQLServerOptions { Sockets = socketOptions });
574584

585+
private static void TryResolveSchemaName(IServiceProvider services, ref string? schemaName)
586+
{
587+
if (schemaName is null
588+
&& services.GetService<IRequestExecutorProvider>() is { } provider
589+
&& provider.SchemaNames.Length == 1)
590+
{
591+
schemaName = provider.SchemaNames[0];
592+
}
593+
}
594+
575595
internal static NitroAppOptions ToNitroAppOptions(this GraphQLToolOptions options)
576596
=> new()
577597
{

src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using HotChocolate.AspNetCore.Warmup;
22
using HotChocolate.Execution.Configuration;
3+
using HotChocolate.Execution.Internal;
34

45
// ReSharper disable once CheckNamespace
56
namespace Microsoft.Extensions.DependencyInjection;
@@ -26,7 +27,6 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
2627
/// </exception>
2728
public static IRequestExecutorBuilder InitializeOnStartup(
2829
this IRequestExecutorBuilder builder,
29-
3030
Func<IRequestExecutor, CancellationToken, Task>? warmup = null,
3131
bool keepWarm = false)
3232
{
@@ -36,4 +36,40 @@ public static IRequestExecutorBuilder InitializeOnStartup(
3636
builder.Services.AddSingleton(new WarmupSchemaTask(builder.Name, keepWarm, warmup));
3737
return builder;
3838
}
39+
40+
public static IRequestExecutorBuilder InitializeOnStartup(
41+
this IRequestExecutorBuilder builder,
42+
RequestExecutorInitializationOptions options)
43+
{
44+
ArgumentNullException.ThrowIfNull(builder);
45+
46+
Func<IRequestExecutor, CancellationToken, Task>? warmup;
47+
48+
if (options.WriteSchemaFile.Enable)
49+
{
50+
var schemaFileName =
51+
options.WriteSchemaFile.FileName
52+
?? System.IO.Path.Combine(Environment.CurrentDirectory, "schema.graphqls");
53+
54+
if (options.Warmup is null)
55+
{
56+
warmup = async (executor, cancellationToken)
57+
=> await SchemaFileExporter.Export(schemaFileName, executor, cancellationToken);
58+
}
59+
else
60+
{
61+
warmup = async (executor, cancellationToken) =>
62+
{
63+
await SchemaFileExporter.Export(schemaFileName, executor, cancellationToken);
64+
await options.Warmup(executor, cancellationToken);
65+
};
66+
}
67+
}
68+
else
69+
{
70+
warmup = options.Warmup;
71+
}
72+
73+
return InitializeOnStartup(builder, warmup, options.KeepWarm);
74+
}
3975
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace Microsoft.Extensions.DependencyInjection;
2+
3+
/// <summary>
4+
/// Represents the initialization options for a request executor.
5+
/// </summary>
6+
public struct RequestExecutorInitializationOptions
7+
{
8+
/// <summary>
9+
/// Gets or sets the warmup task that shall be executed on a new executor.
10+
/// </summary>
11+
public Func<IRequestExecutor, CancellationToken, Task>? Warmup { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets a value indicating whether the warmup task shall be executed after eviction and
15+
/// keep executor in-memory.
16+
/// </summary>
17+
public bool KeepWarm { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the schema file initialization options.
21+
/// </summary>
22+
public SchemaFileInitializationOptions WriteSchemaFile { get; set; }
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Microsoft.Extensions.DependencyInjection;
2+
3+
/// <summary>
4+
/// Represents the schema file initialization options.
5+
/// </summary>
6+
public struct SchemaFileInitializationOptions
7+
{
8+
/// <summary>
9+
/// Gets or sets a value indicating whether a schema file shall be written
10+
/// to the file system every time the executor is initialized.
11+
/// </summary>
12+
public bool Enable { get; set; }
13+
14+
/// <summary>
15+
/// Gets or sets the name of the schema file.
16+
/// The default value is <c>"schema.graphqls"</c>.
17+
/// </summary>
18+
public string? FileName { get; set; }
19+
}

src/HotChocolate/AspNetCore/test/AspNetCore.CommandLine.Tests/SchemaExportCommandTests.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
namespace HotChocolate.AspNetCore.CommandLine;
99

10-
public class SchemaExportCommandTests
10+
public class SchemaExportCommandTests : IDisposable
1111
{
12+
private readonly List<string> _files = [];
13+
1214
[Fact]
1315
public async Task App_Should_OutputCorrectHelpTest_When_HelpIsRequested()
1416
{
@@ -52,6 +54,7 @@ public async Task App_Should_PrintSchema_When_OutputNotSpecified()
5254
public async Task App_Should_WriteSchemaToFile_When_OutputOptionIsSpecified()
5355
{
5456
// arrange
57+
var snapshot = new Snapshot();
5558
var services = new ServiceCollection();
5659
services.AddGraphQL()
5760
.AddQueryType(x => x.Name("Query").Field("foo").Resolve("bar"));
@@ -64,13 +67,15 @@ public async Task App_Should_WriteSchemaToFile_When_OutputOptionIsSpecified()
6467
var host = hostMock.Object;
6568
var console = new TestConsole();
6669
var app = new App(host).Build();
67-
var tempFile = System.IO.Path.GetTempFileName();
70+
var tempFile = CreateSchemaFileName();
6871

6972
// act
7073
await app.InvokeAsync($"schema export --output {tempFile}", console);
7174

7275
// assert
73-
(await File.ReadAllTextAsync(tempFile)).MatchSnapshot();
76+
snapshot.Add(await File.ReadAllTextAsync(tempFile + ".graphqls"), "Schema", markdownLanguage: "graphql");
77+
snapshot.Add(await File.ReadAllTextAsync(tempFile + "-settings.json"), "Settings", markdownLanguage: "json");
78+
await snapshot.MatchMarkdownAsync();
7479
}
7580

7681
[Fact]
@@ -96,4 +101,35 @@ public async Task App_Should_WriteNamedSchemaToOutput_When_SchemaNameIsSpecified
96101
// assert
97102
console.Out.ToString().MatchSnapshot();
98103
}
104+
105+
public string CreateSchemaFileName()
106+
{
107+
var tempFile = System.IO.Path.GetTempFileName();
108+
var schemaFile = tempFile + ".graphqls";
109+
var settingsFile = tempFile + "-settings.json";
110+
_files.Add(tempFile);
111+
_files.Add(schemaFile);
112+
_files.Add(settingsFile);
113+
return tempFile;
114+
}
115+
116+
public void Dispose()
117+
{
118+
foreach (var file in _files)
119+
{
120+
if (File.Exists(file))
121+
{
122+
try
123+
{
124+
File.Delete(file);
125+
}
126+
catch
127+
{
128+
// ignore
129+
}
130+
}
131+
}
132+
133+
_files.Clear();
134+
}
99135
}

src/HotChocolate/AspNetCore/test/AspNetCore.CommandLine.Tests/SchemaListCommandTests.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using HotChocolate.Types;
21
using System.CommandLine.IO;
32
using System.CommandLine.Parsing;
3+
using HotChocolate.Types;
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Hosting;
66
using Moq;
@@ -14,7 +14,36 @@ public async Task App_Should_List_All_SchemaNames()
1414
{
1515
// arrange
1616
var services = new ServiceCollection();
17-
services.AddGraphQL()
17+
services
18+
.AddGraphQL()
19+
.AddQueryType(x => x.Name("Query").Field("foo").Resolve("bar"));
20+
21+
var hostMock = new Mock<IHost>();
22+
hostMock
23+
.Setup(x => x.Services)
24+
.Returns(services.BuildServiceProvider());
25+
26+
var host = hostMock.Object;
27+
var console = new TestConsole();
28+
var app = new App(host).Build();
29+
30+
// act
31+
await app.InvokeAsync("schema list", console);
32+
33+
// assert
34+
console.Out.ToString().MatchSnapshot();
35+
}
36+
37+
[Fact]
38+
public async Task App_Should_List_All_SchemaNames_2()
39+
{
40+
// arrange
41+
var services = new ServiceCollection();
42+
services
43+
.AddGraphQL("schema1")
44+
.AddQueryType(x => x.Name("Query").Field("foo").Resolve("bar"))
45+
.Services
46+
.AddGraphQL("schema2")
1847
.AddQueryType(x => x.Name("Query").Field("foo").Resolve("bar"));
1948

2049
var hostMock = new Mock<IHost>();

src/HotChocolate/AspNetCore/test/AspNetCore.CommandLine.Tests/__snapshots__/SchemaExportCommandTests.App_Should_WriteSchemaToFile_When_OutputOptionIsSpecified.snap

Lines changed: 0 additions & 7 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema1
2+
schema2

src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<ItemGroup>
1414
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
1515
<InternalsVisibleTo Include="HotChocolate.AspNetCore" />
16+
<InternalsVisibleTo Include="HotChocolate.AspNetCore.CommandLine" />
1617
<InternalsVisibleTo Include="HotChocolate.AspNetCore.Tests" />
1718
<InternalsVisibleTo Include="HotChocolate.AspNetCore.Tests.Utilities" />
1819
<InternalsVisibleTo Include="HotChocolate.Caching" />

0 commit comments

Comments
 (0)