Skip to content

Commit

Permalink
Upgrade to Aspire 4 (#6986)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Mar 13, 2024
1 parent 2c90ea1 commit 7000436
Show file tree
Hide file tree
Showing 25 changed files with 730 additions and 729 deletions.
7 changes: 7 additions & 0 deletions src/HotChocolate/Fusion/HotChocolate.Fusion.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Composi
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Aspire", "src\Aspire\HotChocolate.Fusion.Aspire.csproj", "{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -82,6 +84,10 @@ Global
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.Build.0 = Release|Any CPU
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0355AF0F-B91D-4852-8C9F-8E13CE5C88F3} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
Expand All @@ -95,5 +101,6 @@ Global
{DBD317C2-8485-4A75-8BB7-D70C02B40944} = {0EF9C546-286E-407F-A02E-731804507FDE}
{A939BC1B-93A0-40DC-B336-27FF2BC2F704} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
{FD460672-8769-4EB8-87E0-A6A9D5C946C7} = {0EF9C546-286E-407F-A02E-731804507FDE}
{4FF133CD-5C89-4E23-B8AB-639AB7912B6C} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace HotChocolate.Fusion.Aspire;

public sealed class FusionGatewayResource(ProjectResource projectResource)
: Resource(projectResource.Name)
, IResourceWithEnvironment
, IResourceWithServiceDiscovery
{
public ProjectResource ProjectResource { get; } = projectResource;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace HotChocolate.Fusion.Aspire;

public sealed class FusionGatewayResourceBuilder(
IResourceBuilder<ProjectResource> projectResourceBuilder)
: IResourceBuilder<FusionGatewayResource>
{
public IResourceBuilder<FusionGatewayResource> WithAnnotation<TAnnotation>(
TAnnotation annotation,
ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append)
where TAnnotation : IResourceAnnotation
{
projectResourceBuilder.WithAnnotation(annotation, behavior);
return this;
}

public IDistributedApplicationBuilder ApplicationBuilder => projectResourceBuilder.ApplicationBuilder;

public FusionGatewayResource Resource { get; } = new(projectResourceBuilder.Resource);
}
27 changes: 27 additions & 0 deletions src/HotChocolate/Fusion/src/Aspire/Composition/ConsoleLog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace HotChocolate.Fusion.Composition;

internal sealed class ConsoleLog : ICompositionLog
{
public bool HasErrors { get; private set; }

public void Write(LogEntry e)
{
if (e.Severity is LogSeverity.Error)
{
HasErrors = true;
}

if (e.Code is null)
{
Console.WriteLine($"{e.Severity}: {e.Message}");
}
else if (e.Coordinate is null)
{
Console.WriteLine($"{e.Severity}: {e.Code} {e.Message}");
}
else
{
Console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
using System.Diagnostics;
using System.Text.Json;
using HotChocolate.Fusion.Composition.Settings;
using HotChocolate.Language;
using HotChocolate.Skimmed.Serialization;

namespace HotChocolate.Fusion.Composition;

public static class FusionGatewayConfigurationUtilities
{
public static async Task ConfigureAsync(
IReadOnlyList<GatewayInfo> gateways,
CancellationToken cancellationToken = default)
{
ExportSubgraphSchemaDocs(gateways);
await EnsureSubgraphHasConfigAsync(gateways, cancellationToken);
await ComposeAsync(gateways, cancellationToken);
}

private static void ExportSubgraphSchemaDocs(IReadOnlyList<GatewayInfo> gateways)
{
var processed = new HashSet<string>();

foreach (var gateway in gateways)
{
foreach (var subgraph in gateway.Subgraphs)
{
if (!processed.Add(subgraph.Path))
{
continue;
}

Console.WriteLine("Expoorting schema document for subgraph {0} ...", subgraph.Name);

var workingDirectory = System.IO.Path.GetDirectoryName(subgraph.Path)!;

var processStartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "dotnet run --no-build --no-restore -- schema export --output schema.graphql",
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};

using (var process = Process.Start(processStartInfo)!)
{
var output = process.StandardOutput.ReadToEnd();
var errors = process.StandardError.ReadToEnd();

process.WaitForExit();

if (!string.IsNullOrEmpty(output))
{
Console.WriteLine(output);
}

if (!string.IsNullOrEmpty(errors))
{
Console.WriteLine(errors);
}

if (process.ExitCode != 0)
{
Console.WriteLine(
"{0}(1,1): error HF1002: ; Failed to export schema document for subgraph {1} ...",
subgraph.Path,
subgraph.Name);
Environment.Exit(-255);
}
}
}
}
}

private static async Task EnsureSubgraphHasConfigAsync(
IReadOnlyList<GatewayInfo> gateways,
CancellationToken ct)
{
foreach (var gateway in gateways)
{
foreach (var project in gateway.Subgraphs)
{
var projectRoot = System.IO.Path.GetDirectoryName(project.Path)!;
var configFile = System.IO.Path.Combine(projectRoot, WellKnownFileNames.ConfigFile);

if (File.Exists(configFile))
{
continue;
}

var config = new SubgraphConfigurationDto(project.Name);
var configJson = PackageHelper.FormatSubgraphConfig(config);
await File.WriteAllTextAsync(configFile, configJson, ct);
}
}
}

private static async Task ComposeAsync(
IReadOnlyList<GatewayInfo> gateways,
CancellationToken ct)
{
foreach (var gateway in gateways)
{
await ComposeGatewayAsync(gateway.Path, gateway.Subgraphs.Select(t => t.Path), ct);
}
}

private static async Task ComposeGatewayAsync(
string gatewayProject,
IEnumerable<string> subgraphProjects,
CancellationToken ct)
{
var gatewayDirectory = System.IO.Path.GetDirectoryName(gatewayProject)!;
var packageFileName = System.IO.Path.Combine(gatewayDirectory, $"gateway{WellKnownFileExtensions.FusionPackage}");
var packageFile = new FileInfo(packageFileName);
var settingsFileName = System.IO.Path.Combine(gatewayDirectory, "gateway-settings.json");
var settingsFile = new FileInfo(settingsFileName);
var subgraphDirectories = subgraphProjects.Select(t => System.IO.Path.GetDirectoryName(t)!).ToArray();

// Ensure Gateway Project Directory Exists.
if (!Directory.Exists(gatewayDirectory))
{
Directory.CreateDirectory(gatewayDirectory);
}

await using var package = FusionGraphPackage.Open(packageFile.FullName);
var subgraphConfigs =
(await package.GetSubgraphConfigurationsAsync(ct)).ToDictionary(t => t.Name);
await ResolveSubgraphPackagesAsync(subgraphDirectories, subgraphConfigs, ct);

using var settingsJson = settingsFile.Exists
? JsonDocument.Parse(await File.ReadAllTextAsync(settingsFile.FullName, ct))
: await package.GetFusionGraphSettingsAsync(ct);
var settings = settingsJson.Deserialize<PackageSettings>() ?? new PackageSettings();

var features = settings.CreateFeatures();

var composer = new FusionGraphComposer(
settings.FusionTypePrefix,
settings.FusionTypeSelf,
() => new ConsoleLog());

var fusionGraph = await composer.TryComposeAsync(subgraphConfigs.Values, features, ct);

if (fusionGraph is null)
{
Console.WriteLine("Fusion graph composition failed.");
return;
}

var fusionGraphDoc = Utf8GraphQLParser.Parse(SchemaFormatter.FormatAsString(fusionGraph));
var typeNames = FusionTypeNames.From(fusionGraphDoc);
var rewriter = new FusionGraphConfigurationToSchemaRewriter();
var schemaDoc = (DocumentNode)rewriter.Rewrite(fusionGraphDoc, typeNames)!;

using var updateSettingsJson = JsonSerializer.SerializeToDocument(
settings,
new JsonSerializerOptions(JsonSerializerDefaults.Web));

await package.SetFusionGraphAsync(fusionGraphDoc, ct);
await package.SetFusionGraphSettingsAsync(updateSettingsJson, ct);
await package.SetSchemaAsync(schemaDoc, ct);

foreach (var config in subgraphConfigs.Values)
{
await package.SetSubgraphConfigurationAsync(config, ct);
}

Console.WriteLine("Fusion graph composed.");
}

private static async Task ResolveSubgraphPackagesAsync(
IReadOnlyList<string> subgraphDirectories,
IDictionary<string, SubgraphConfiguration> subgraphConfigs,
CancellationToken cancellationToken)
{
for (var i = 0; i < subgraphDirectories.Count; i++)
{
var path = subgraphDirectories[i];

if (!Directory.Exists(path))
{
continue;
}

var configFile = System.IO.Path.Combine(path, WellKnownFileNames.ConfigFile);
var schemaFile = System.IO.Path.Combine(path, WellKnownFileNames.SchemaFile);
var extensionFile = System.IO.Path.Combine(path, WellKnownFileNames.ExtensionFile);

if (!File.Exists(configFile) || !File.Exists(schemaFile))
{
continue;
}

var conf = await PackageHelper.LoadSubgraphConfigAsync(configFile, cancellationToken);
var schema = await File.ReadAllTextAsync(schemaFile, cancellationToken);
var extensions = Array.Empty<string>();

if (File.Exists(extensionFile))
{
extensions = [await File.ReadAllTextAsync(extensionFile, cancellationToken),];
}

subgraphConfigs[conf.Name] =
new SubgraphConfiguration(
conf.Name,
schema,
extensions,
conf.Clients,
conf.Extensions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using HotChocolate.Language;
using HotChocolate.Language.Visitors;

namespace HotChocolate.Fusion.Composition;

internal sealed class FusionGraphConfigurationToSchemaRewriter : SyntaxRewriter<FusionTypeNames>
{
public DocumentNode Rewrite(DocumentNode fusionGraph)
{
var typeNames = FusionTypeNames.From(fusionGraph);
var schemaDoc = (DocumentNode?)Rewrite(fusionGraph, typeNames);

if (schemaDoc is null)
{
throw new InvalidOperationException();
}

return schemaDoc;
}

protected override DirectiveNode? RewriteDirective(DirectiveNode node, FusionTypeNames context)
=> context.IsFusionDirective(node.Name.Value) ? null : base.RewriteDirective(node, context);
}
14 changes: 14 additions & 0 deletions src/HotChocolate/Fusion/src/Aspire/Composition/GatewayInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace HotChocolate.Fusion.Composition;

public sealed class GatewayInfo(string name, string path, IReadOnlyList<SubgraphInfo> subgraphs)
{
public string Name { get; } = name;

public string Path { get; } = path;

public IReadOnlyList<SubgraphInfo> Subgraphs { get; } = subgraphs;

public static GatewayInfo Create<TProject>(string name, params SubgraphInfo[] projects)
where TProject : IProjectMetadata, new()
=> new(name, new TProject().ProjectPath, projects);
}
Loading

0 comments on commit 7000436

Please sign in to comment.