Skip to content

Commit 7000436

Browse files
authored
Upgrade to Aspire 4 (#6986)
1 parent 2c90ea1 commit 7000436

25 files changed

+730
-729
lines changed

src/HotChocolate/Fusion/HotChocolate.Fusion.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Composi
2929
EndProject
3030
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Composition.Analyzers.Tests", "test\Composition.Analyzers.Tests\HotChocolate.Fusion.Composition.Analyzers.Tests.csproj", "{FD460672-8769-4EB8-87E0-A6A9D5C946C7}"
3131
EndProject
32+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Aspire", "src\Aspire\HotChocolate.Fusion.Aspire.csproj", "{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}"
33+
EndProject
3234
Global
3335
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3436
Debug|Any CPU = Debug|Any CPU
@@ -82,6 +84,10 @@ Global
8284
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
8385
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
8486
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.Build.0 = Release|Any CPU
87+
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
88+
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
89+
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
90+
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Release|Any CPU.Build.0 = Release|Any CPU
8591
EndGlobalSection
8692
GlobalSection(NestedProjects) = preSolution
8793
{0355AF0F-B91D-4852-8C9F-8E13CE5C88F3} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
@@ -95,5 +101,6 @@ Global
95101
{DBD317C2-8485-4A75-8BB7-D70C02B40944} = {0EF9C546-286E-407F-A02E-731804507FDE}
96102
{A939BC1B-93A0-40DC-B336-27FF2BC2F704} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
97103
{FD460672-8769-4EB8-87E0-A6A9D5C946C7} = {0EF9C546-286E-407F-A02E-731804507FDE}
104+
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
98105
EndGlobalSection
99106
EndGlobal
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace HotChocolate.Fusion.Aspire;
2+
3+
public sealed class FusionGatewayResource(ProjectResource projectResource)
4+
: Resource(projectResource.Name)
5+
, IResourceWithEnvironment
6+
, IResourceWithServiceDiscovery
7+
{
8+
public ProjectResource ProjectResource { get; } = projectResource;
9+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace HotChocolate.Fusion.Aspire;
2+
3+
public sealed class FusionGatewayResourceBuilder(
4+
IResourceBuilder<ProjectResource> projectResourceBuilder)
5+
: IResourceBuilder<FusionGatewayResource>
6+
{
7+
public IResourceBuilder<FusionGatewayResource> WithAnnotation<TAnnotation>(
8+
TAnnotation annotation,
9+
ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append)
10+
where TAnnotation : IResourceAnnotation
11+
{
12+
projectResourceBuilder.WithAnnotation(annotation, behavior);
13+
return this;
14+
}
15+
16+
public IDistributedApplicationBuilder ApplicationBuilder => projectResourceBuilder.ApplicationBuilder;
17+
18+
public FusionGatewayResource Resource { get; } = new(projectResourceBuilder.Resource);
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace HotChocolate.Fusion.Composition;
2+
3+
internal sealed class ConsoleLog : ICompositionLog
4+
{
5+
public bool HasErrors { get; private set; }
6+
7+
public void Write(LogEntry e)
8+
{
9+
if (e.Severity is LogSeverity.Error)
10+
{
11+
HasErrors = true;
12+
}
13+
14+
if (e.Code is null)
15+
{
16+
Console.WriteLine($"{e.Severity}: {e.Message}");
17+
}
18+
else if (e.Coordinate is null)
19+
{
20+
Console.WriteLine($"{e.Severity}: {e.Code} {e.Message}");
21+
}
22+
else
23+
{
24+
Console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}");
25+
}
26+
}
27+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
using System.Diagnostics;
2+
using System.Text.Json;
3+
using HotChocolate.Fusion.Composition.Settings;
4+
using HotChocolate.Language;
5+
using HotChocolate.Skimmed.Serialization;
6+
7+
namespace HotChocolate.Fusion.Composition;
8+
9+
public static class FusionGatewayConfigurationUtilities
10+
{
11+
public static async Task ConfigureAsync(
12+
IReadOnlyList<GatewayInfo> gateways,
13+
CancellationToken cancellationToken = default)
14+
{
15+
ExportSubgraphSchemaDocs(gateways);
16+
await EnsureSubgraphHasConfigAsync(gateways, cancellationToken);
17+
await ComposeAsync(gateways, cancellationToken);
18+
}
19+
20+
private static void ExportSubgraphSchemaDocs(IReadOnlyList<GatewayInfo> gateways)
21+
{
22+
var processed = new HashSet<string>();
23+
24+
foreach (var gateway in gateways)
25+
{
26+
foreach (var subgraph in gateway.Subgraphs)
27+
{
28+
if (!processed.Add(subgraph.Path))
29+
{
30+
continue;
31+
}
32+
33+
Console.WriteLine("Expoorting schema document for subgraph {0} ...", subgraph.Name);
34+
35+
var workingDirectory = System.IO.Path.GetDirectoryName(subgraph.Path)!;
36+
37+
var processStartInfo = new ProcessStartInfo
38+
{
39+
FileName = "dotnet",
40+
Arguments = "dotnet run --no-build --no-restore -- schema export --output schema.graphql",
41+
WorkingDirectory = workingDirectory,
42+
UseShellExecute = false,
43+
RedirectStandardOutput = true,
44+
RedirectStandardError = true,
45+
};
46+
47+
using (var process = Process.Start(processStartInfo)!)
48+
{
49+
var output = process.StandardOutput.ReadToEnd();
50+
var errors = process.StandardError.ReadToEnd();
51+
52+
process.WaitForExit();
53+
54+
if (!string.IsNullOrEmpty(output))
55+
{
56+
Console.WriteLine(output);
57+
}
58+
59+
if (!string.IsNullOrEmpty(errors))
60+
{
61+
Console.WriteLine(errors);
62+
}
63+
64+
if (process.ExitCode != 0)
65+
{
66+
Console.WriteLine(
67+
"{0}(1,1): error HF1002: ; Failed to export schema document for subgraph {1} ...",
68+
subgraph.Path,
69+
subgraph.Name);
70+
Environment.Exit(-255);
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
private static async Task EnsureSubgraphHasConfigAsync(
78+
IReadOnlyList<GatewayInfo> gateways,
79+
CancellationToken ct)
80+
{
81+
foreach (var gateway in gateways)
82+
{
83+
foreach (var project in gateway.Subgraphs)
84+
{
85+
var projectRoot = System.IO.Path.GetDirectoryName(project.Path)!;
86+
var configFile = System.IO.Path.Combine(projectRoot, WellKnownFileNames.ConfigFile);
87+
88+
if (File.Exists(configFile))
89+
{
90+
continue;
91+
}
92+
93+
var config = new SubgraphConfigurationDto(project.Name);
94+
var configJson = PackageHelper.FormatSubgraphConfig(config);
95+
await File.WriteAllTextAsync(configFile, configJson, ct);
96+
}
97+
}
98+
}
99+
100+
private static async Task ComposeAsync(
101+
IReadOnlyList<GatewayInfo> gateways,
102+
CancellationToken ct)
103+
{
104+
foreach (var gateway in gateways)
105+
{
106+
await ComposeGatewayAsync(gateway.Path, gateway.Subgraphs.Select(t => t.Path), ct);
107+
}
108+
}
109+
110+
private static async Task ComposeGatewayAsync(
111+
string gatewayProject,
112+
IEnumerable<string> subgraphProjects,
113+
CancellationToken ct)
114+
{
115+
var gatewayDirectory = System.IO.Path.GetDirectoryName(gatewayProject)!;
116+
var packageFileName = System.IO.Path.Combine(gatewayDirectory, $"gateway{WellKnownFileExtensions.FusionPackage}");
117+
var packageFile = new FileInfo(packageFileName);
118+
var settingsFileName = System.IO.Path.Combine(gatewayDirectory, "gateway-settings.json");
119+
var settingsFile = new FileInfo(settingsFileName);
120+
var subgraphDirectories = subgraphProjects.Select(t => System.IO.Path.GetDirectoryName(t)!).ToArray();
121+
122+
// Ensure Gateway Project Directory Exists.
123+
if (!Directory.Exists(gatewayDirectory))
124+
{
125+
Directory.CreateDirectory(gatewayDirectory);
126+
}
127+
128+
await using var package = FusionGraphPackage.Open(packageFile.FullName);
129+
var subgraphConfigs =
130+
(await package.GetSubgraphConfigurationsAsync(ct)).ToDictionary(t => t.Name);
131+
await ResolveSubgraphPackagesAsync(subgraphDirectories, subgraphConfigs, ct);
132+
133+
using var settingsJson = settingsFile.Exists
134+
? JsonDocument.Parse(await File.ReadAllTextAsync(settingsFile.FullName, ct))
135+
: await package.GetFusionGraphSettingsAsync(ct);
136+
var settings = settingsJson.Deserialize<PackageSettings>() ?? new PackageSettings();
137+
138+
var features = settings.CreateFeatures();
139+
140+
var composer = new FusionGraphComposer(
141+
settings.FusionTypePrefix,
142+
settings.FusionTypeSelf,
143+
() => new ConsoleLog());
144+
145+
var fusionGraph = await composer.TryComposeAsync(subgraphConfigs.Values, features, ct);
146+
147+
if (fusionGraph is null)
148+
{
149+
Console.WriteLine("Fusion graph composition failed.");
150+
return;
151+
}
152+
153+
var fusionGraphDoc = Utf8GraphQLParser.Parse(SchemaFormatter.FormatAsString(fusionGraph));
154+
var typeNames = FusionTypeNames.From(fusionGraphDoc);
155+
var rewriter = new FusionGraphConfigurationToSchemaRewriter();
156+
var schemaDoc = (DocumentNode)rewriter.Rewrite(fusionGraphDoc, typeNames)!;
157+
158+
using var updateSettingsJson = JsonSerializer.SerializeToDocument(
159+
settings,
160+
new JsonSerializerOptions(JsonSerializerDefaults.Web));
161+
162+
await package.SetFusionGraphAsync(fusionGraphDoc, ct);
163+
await package.SetFusionGraphSettingsAsync(updateSettingsJson, ct);
164+
await package.SetSchemaAsync(schemaDoc, ct);
165+
166+
foreach (var config in subgraphConfigs.Values)
167+
{
168+
await package.SetSubgraphConfigurationAsync(config, ct);
169+
}
170+
171+
Console.WriteLine("Fusion graph composed.");
172+
}
173+
174+
private static async Task ResolveSubgraphPackagesAsync(
175+
IReadOnlyList<string> subgraphDirectories,
176+
IDictionary<string, SubgraphConfiguration> subgraphConfigs,
177+
CancellationToken cancellationToken)
178+
{
179+
for (var i = 0; i < subgraphDirectories.Count; i++)
180+
{
181+
var path = subgraphDirectories[i];
182+
183+
if (!Directory.Exists(path))
184+
{
185+
continue;
186+
}
187+
188+
var configFile = System.IO.Path.Combine(path, WellKnownFileNames.ConfigFile);
189+
var schemaFile = System.IO.Path.Combine(path, WellKnownFileNames.SchemaFile);
190+
var extensionFile = System.IO.Path.Combine(path, WellKnownFileNames.ExtensionFile);
191+
192+
if (!File.Exists(configFile) || !File.Exists(schemaFile))
193+
{
194+
continue;
195+
}
196+
197+
var conf = await PackageHelper.LoadSubgraphConfigAsync(configFile, cancellationToken);
198+
var schema = await File.ReadAllTextAsync(schemaFile, cancellationToken);
199+
var extensions = Array.Empty<string>();
200+
201+
if (File.Exists(extensionFile))
202+
{
203+
extensions = [await File.ReadAllTextAsync(extensionFile, cancellationToken),];
204+
}
205+
206+
subgraphConfigs[conf.Name] =
207+
new SubgraphConfiguration(
208+
conf.Name,
209+
schema,
210+
extensions,
211+
conf.Clients,
212+
conf.Extensions);
213+
}
214+
}
215+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using HotChocolate.Language;
2+
using HotChocolate.Language.Visitors;
3+
4+
namespace HotChocolate.Fusion.Composition;
5+
6+
internal sealed class FusionGraphConfigurationToSchemaRewriter : SyntaxRewriter<FusionTypeNames>
7+
{
8+
public DocumentNode Rewrite(DocumentNode fusionGraph)
9+
{
10+
var typeNames = FusionTypeNames.From(fusionGraph);
11+
var schemaDoc = (DocumentNode?)Rewrite(fusionGraph, typeNames);
12+
13+
if (schemaDoc is null)
14+
{
15+
throw new InvalidOperationException();
16+
}
17+
18+
return schemaDoc;
19+
}
20+
21+
protected override DirectiveNode? RewriteDirective(DirectiveNode node, FusionTypeNames context)
22+
=> context.IsFusionDirective(node.Name.Value) ? null : base.RewriteDirective(node, context);
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace HotChocolate.Fusion.Composition;
2+
3+
public sealed class GatewayInfo(string name, string path, IReadOnlyList<SubgraphInfo> subgraphs)
4+
{
5+
public string Name { get; } = name;
6+
7+
public string Path { get; } = path;
8+
9+
public IReadOnlyList<SubgraphInfo> Subgraphs { get; } = subgraphs;
10+
11+
public static GatewayInfo Create<TProject>(string name, params SubgraphInfo[] projects)
12+
where TProject : IProjectMetadata, new()
13+
=> new(name, new TProject().ProjectPath, projects);
14+
}

0 commit comments

Comments
 (0)