From 0c24dd9a2f20db7e184f185c2c30df6a602448b0 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:22:08 +0200 Subject: [PATCH 01/11] Add Nitro Fusion publishing to Aspire --- src/All.slnx | 4 + .../Fusion.Aspire/AspireCompositionHelper.cs | 60 + .../CommandLineSchemaExporter.cs | 458 + .../GraphQLResourceBuilderExtensions.cs | 47 + .../src/Fusion.Aspire/GraphQLResourceModel.cs | 115 + .../GraphQLSourceSchemaAnnotation.cs | 10 + .../GraphQLSourceSchemaValidator.cs | 45 + .../HotChocolate.Fusion.Aspire.csproj | 2 + .../src/Fusion.Aspire/SchemaComposition.cs | 262 +- .../Fusion.Aspire/SourceSchemaLocationType.cs | 3 +- .../CommandLineSchemaExporterTests.cs | 337 + .../GraphQLResourceBuilderExtensionsTests.cs | 56 + .../GraphQLResourceModelTests.cs | 121 + .../ChilliCream.Nitro.Aspire.csproj | 32 + .../FusionDeploymentResource.cs | 32 + .../FusionPipeline.cs | 283 + .../FusionPipelineExecutor.cs | 930 ++ .../FusionPipelineResource.cs | 5 + .../IFusionPipelineExecutor.cs | 18 + .../ChilliCream.Nitro.Aspire/NitroResource.cs | 15 + .../NitroResourceBuilderExtensions.cs | 238 + .../src/ChilliCream.Nitro.Aspire/README.md | 29 + .../FUSION_ASPIRE_PUBLISH_DEPLOY.md | 386 + .../ChilliCream.Nitro.Fusion/.graphqlrc.json | 25 + .../ChilliCream.Nitro.Fusion.csproj | 33 + .../FusionArchiveContent.cs | 140 + .../FusionDeploymentException.cs | 17 + .../FusionDeploymentWorkflow.cs | 593 + .../FusionIdentityCollisionException.cs | 12 + .../FusionIndeterminateStateException.cs | 27 + .../FusionPublicationRequest.cs | 14 + .../FusionSourceSchemaUpload.cs | 10 + .../FusionSourceSchemaVersion.cs | 6 + .../ChilliCream.Nitro.Fusion/FusionTarget.cs | 10 + .../Generated/FusionApiClient.Client.cs | 11092 ++++++++++++++++ .../IFusionDeploymentWorkflow.cs | 23 + .../NitroFusionServiceCollectionExtensions.cs | 27 + .../Operations/Fragments.graphql | 3 + .../Operations/FusionDeployment.graphql | 65 + .../Operations/FusionSourceSchema.graphql | 11 + .../src/ChilliCream.Nitro.Fusion/README.md | 14 + .../Transport/FusionApiTransportFactory.cs | 323 + .../Transport/IFusionDeploymentTransport.cs | 78 + .../ChilliCream.Nitro.Aspire.Tests.csproj | 15 + .../FusionPipelineTests.cs | 230 + .../ChilliCream.Nitro.Fusion.Tests.csproj | 15 + .../FusionDeploymentWorkflowTests.cs | 454 + 47 files changed, 16623 insertions(+), 102 deletions(-) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/CommandLineSchemaExporter.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceModel.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaValidator.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/CommandLineSchemaExporterTests.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceModelTests.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs diff --git a/src/All.slnx b/src/All.slnx index a4dad5e1730..408c384dc68 100644 --- a/src/All.slnx +++ b/src/All.slnx @@ -410,13 +410,17 @@ + + + + diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs index b23717b0cb9..b354272996e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs @@ -2,12 +2,68 @@ using System.Text; using HotChocolate.Fusion.Logging; using HotChocolate.Fusion.Packaging; +using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.Logging; namespace HotChocolate.Fusion.Aspire; internal static class AspireCompositionHelper { + public static async Task TryComposeArchivesAsync( + string fusionArchivePath, + IReadOnlyList archives, + GraphQLCompositionSettings settings, + ILogger logger, + CancellationToken cancellationToken) + { + var sourceSchemas = new List(archives.Count); + + try + { + foreach (var archiveInfo in archives) + { + using var archive = FusionSourceSchemaArchive.Open( + archiveInfo.ArchivePath); + var schema = await archive.TryGetSchemaAsync(cancellationToken) + ?? throw new InvalidOperationException( + $"Fusion source archive '{archiveInfo.Name}' has no schema."); + var sourceSettings = + await archive.TryGetSettingsAsync(cancellationToken) + ?? throw new InvalidOperationException( + $"Fusion source archive '{archiveInfo.Name}' has no settings."); + var extensions = + await archive.TryGetSchemaExtensionsAsync(cancellationToken); + + sourceSchemas.Add( + new SourceSchemaInfo + { + Name = archiveInfo.Name, + Schema = new SourceSchemaText( + archiveInfo.Name, + Encoding.UTF8.GetString(schema.Span), + extensions is null + ? null + : Encoding.UTF8.GetString(extensions.Value.Span)), + SchemaSettings = sourceSettings + }); + } + + return await TryComposeAsync( + fusionArchivePath, + [.. sourceSchemas], + settings, + logger, + cancellationToken); + } + finally + { + foreach (var sourceSchema in sourceSchemas) + { + sourceSchema.SchemaSettings.Dispose(); + } + } + } + public static async Task TryComposeAsync( string fusionArchivePath, ImmutableArray newSourceSchemas, @@ -100,3 +156,7 @@ private static string FormatMultilineMessage(string message) return string.Join(Environment.NewLine + " ", lines); } } + +internal readonly record struct SourceSchemaArchiveInfo( + string Name, + string ArchivePath); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/CommandLineSchemaExporter.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/CommandLineSchemaExporter.cs new file mode 100644 index 00000000000..f38db0480be --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/CommandLineSchemaExporter.cs @@ -0,0 +1,458 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; +using IOPath = System.IO.Path; + +namespace HotChocolate.Fusion.Aspire; + +internal static class CommandLineSchemaExporter +{ + private const int MaxCapturedOutputLength = 32 * 1024; + private static readonly TimeSpan s_processTerminationTimeout = TimeSpan.FromSeconds(10); + + public static async Task ExportAsync( + IResource resource, + GraphQLSourceSchemaAnnotation declaration, + string outputDirectory, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(resource); + ArgumentNullException.ThrowIfNull(declaration); + ArgumentException.ThrowIfNullOrWhiteSpace(outputDirectory); + + if (declaration is not + { + Location: SourceSchemaLocationType.CommandLineExport, + ExportSchemaName: { } schemaName, + ExportConfiguration: { } configuration, + ExportTargetFramework: { } targetFramework, + ExportTimeout: { } timeout + } + || string.IsNullOrWhiteSpace(schemaName) + || string.IsNullOrWhiteSpace(configuration) + || string.IsNullOrWhiteSpace(targetFramework) + || timeout <= TimeSpan.Zero + || declaration.ExportRuntimeIdentifier is { } runtimeIdentifier + && string.IsNullOrWhiteSpace(runtimeIdentifier)) + { + throw new InvalidOperationException( + $"Resource '{resource.Name}' does not have a complete command-line export declaration."); + } + + cancellationToken.ThrowIfCancellationRequested(); + + var projectPath = IOPath.GetFullPath(GraphQLResourceModel.GetProjectPath(resource)); + outputDirectory = IOPath.GetFullPath(outputDirectory); + Directory.CreateDirectory(outputDirectory); + + var schemaPath = IOPath.Combine(outputDirectory, "schema.graphqls"); + var settingsPath = IOPath.Combine(outputDirectory, "schema-settings.json"); + + DeleteExistingArtifact(schemaPath); + DeleteExistingArtifact(settingsPath); + + var startInfo = CreateStartInfo( + projectPath, + schemaPath, + schemaName, + configuration, + targetFramework, + declaration.ExportRuntimeIdentifier); + var processResult = await RunProcessAsync( + startInfo, + timeout, + resource.Name, + cancellationToken); + + if (processResult.ExitCode != 0) + { + throw new InvalidOperationException( + $"Schema export for resource '{resource.Name}' exited with code " + + $"{processResult.ExitCode}. Child-process output was suppressed because it may " + + "contain sensitive data."); + } + + await ValidateArtifactsAsync( + resource.Name, + schemaName, + schemaPath, + settingsPath, + cancellationToken); + + return new( + schemaPath, + settingsPath, + projectPath, + configuration, + targetFramework, + declaration.ExportRuntimeIdentifier); + } + + internal static ProcessStartInfo CreateStartInfo( + string projectPath, + string schemaPath, + string schemaName, + string configuration, + string targetFramework, + string? runtimeIdentifier) + { + ArgumentException.ThrowIfNullOrWhiteSpace(projectPath); + ArgumentException.ThrowIfNullOrWhiteSpace(schemaPath); + ArgumentException.ThrowIfNullOrWhiteSpace(schemaName); + ArgumentException.ThrowIfNullOrWhiteSpace(configuration); + ArgumentException.ThrowIfNullOrWhiteSpace(targetFramework); + + if (runtimeIdentifier is not null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(runtimeIdentifier); + } + + projectPath = IOPath.GetFullPath(projectPath); + schemaPath = IOPath.GetFullPath(schemaPath); + + var startInfo = new ProcessStartInfo + { + FileName = "dotnet", + WorkingDirectory = IOPath.GetDirectoryName(projectPath)!, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + startInfo.ArgumentList.Add("run"); + startInfo.ArgumentList.Add("--project"); + startInfo.ArgumentList.Add(projectPath); + startInfo.ArgumentList.Add("--configuration"); + startInfo.ArgumentList.Add(configuration); + startInfo.ArgumentList.Add("--framework"); + startInfo.ArgumentList.Add(targetFramework); + + if (runtimeIdentifier is not null) + { + startInfo.ArgumentList.Add("--runtime"); + startInfo.ArgumentList.Add(runtimeIdentifier); + } + + startInfo.ArgumentList.Add("--no-launch-profile"); + startInfo.ArgumentList.Add("--"); + startInfo.ArgumentList.Add("schema"); + startInfo.ArgumentList.Add("export"); + startInfo.ArgumentList.Add("--output"); + startInfo.ArgumentList.Add(schemaPath); + startInfo.ArgumentList.Add("--schema-name"); + startInfo.ArgumentList.Add(schemaName); + + ApplyEnvironmentAllowList(startInfo); + return startInfo; + } + + internal static async Task RunProcessAsync( + ProcessStartInfo startInfo, + TimeSpan timeout, + string resourceName, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(startInfo); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(timeout, TimeSpan.Zero); + ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); + cancellationToken.ThrowIfCancellationRequested(); + + using var process = new Process { StartInfo = startInfo }; + using var timeoutSource = new CancellationTokenSource(timeout); + using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, + timeoutSource.Token); + using var outputSource = new CancellationTokenSource(); + + if (!process.Start()) + { + throw new InvalidOperationException( + $"Could not start schema export for resource '{resourceName}'."); + } + + var stdout = CaptureAsync( + process.StandardOutput, + retainOutput: true, + outputSource.Token); + var stderr = CaptureAsync( + process.StandardError, + retainOutput: false, + outputSource.Token); + + try + { + try + { + await process.WaitForExitAsync(linkedSource.Token); + } + catch (OperationCanceledException) + when (cancellationToken.IsCancellationRequested + || timeoutSource.IsCancellationRequested) + { + await TerminateProcessAsync( + process, + stdout, + stderr, + outputSource); + + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(cancellationToken); + } + + throw new TimeoutException( + $"Schema export for resource '{resourceName}' exceeded {timeout}."); + } + catch + { + await TerminateProcessAsync( + process, + stdout, + stderr, + outputSource); + throw; + } + + var captured = await Task.WhenAll(stdout, stderr); + return new( + process.ExitCode, + captured[0].Output, + captured[0].WasTruncated, + captured[1].CharacterCount); + } + finally + { + if (!HasExited(process)) + { + await TerminateProcessAsync( + process, + stdout, + stderr, + outputSource); + } + } + } + + internal static void ApplyEnvironmentAllowList(ProcessStartInfo startInfo) + { + ArgumentNullException.ThrowIfNull(startInfo); + + var inheritedEnvironment = startInfo.Environment.ToArray(); + var comparison = OperatingSystem.IsWindows() + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + + startInfo.Environment.Clear(); + + CopyIfPresent("PATH"); + CopyIfPresent("DOTNET_ROOT"); + CopyIfPresent("DOTNET_ROOT_X64"); + CopyIfPresent("DOTNET_ROOT_X86"); + CopyIfPresent("DOTNET_ROOT_ARM64"); + CopyIfPresent("DOTNET_CLI_HOME"); + CopyIfPresent("NUGET_PACKAGES"); + + if (OperatingSystem.IsWindows()) + { + CopyIfPresent("SystemRoot"); + CopyIfPresent("SystemDrive"); + CopyIfPresent("USERPROFILE"); + CopyIfPresent("LOCALAPPDATA"); + } + else + { + CopyIfPresent("HOME"); + } + + CopyIfPresent("TMPDIR"); + CopyIfPresent("TMP"); + CopyIfPresent("TEMP"); + + startInfo.Environment["DOTNET_NOLOGO"] = "1"; + startInfo.Environment["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1"; + + void CopyIfPresent(string name) + { + foreach (var pair in inheritedEnvironment) + { + if (pair.Key.Equals(name, comparison) + && !string.IsNullOrEmpty(pair.Value)) + { + startInfo.Environment[name] = pair.Value; + return; + } + } + } + } + + internal static async Task ValidateArtifactsAsync( + string resourceName, + string schemaName, + string schemaPath, + string settingsPath, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); + ArgumentException.ThrowIfNullOrWhiteSpace(schemaName); + ArgumentException.ThrowIfNullOrWhiteSpace(schemaPath); + ArgumentException.ThrowIfNullOrWhiteSpace(settingsPath); + + if (!File.Exists(schemaPath) || !File.Exists(settingsPath)) + { + throw new InvalidOperationException( + $"Schema export for resource '{resourceName}' did not produce both " + + "schema.graphqls and schema-settings.json."); + } + + var schemaText = await File.ReadAllTextAsync(schemaPath, cancellationToken); + var settingsText = await File.ReadAllTextAsync(settingsPath, cancellationToken); + + if (string.IsNullOrWhiteSpace(settingsText)) + { + throw new InvalidOperationException( + $"Schema export for resource '{resourceName}' produced empty schema-settings.json."); + } + + using var settings = JsonDocument.Parse(settingsText); + var configuration = SchemaComposition.ReadEndpointConfiguration( + resourceName, + schemaName, + settings); + GraphQLSourceSchemaValidator.Validate( + resourceName, + configuration, + schemaText); + } + + private static async Task CaptureAsync( + StreamReader reader, + bool retainOutput, + CancellationToken cancellationToken) + { + StringBuilder? output = retainOutput + ? new StringBuilder(MaxCapturedOutputLength) + : null; + var buffer = new char[4096]; + var characterCount = 0L; + + while (true) + { + var count = await reader.ReadAsync(buffer.AsMemory(), cancellationToken); + if (count == 0) + { + break; + } + + characterCount += count; + + if (output?.Length is < MaxCapturedOutputLength) + { + output.Append( + buffer, + 0, + Math.Min(count, MaxCapturedOutputLength - output.Length)); + } + } + + return new( + output?.ToString() ?? string.Empty, + characterCount, + characterCount > MaxCapturedOutputLength); + } + + private static async Task TerminateProcessAsync( + Process process, + Task stdout, + Task stderr, + CancellationTokenSource outputSource) + { + TryKillProcess(process, entireProcessTree: true); + + if (!HasExited(process)) + { + TryKillProcess(process, entireProcessTree: false); + } + + using var cleanupSource = new CancellationTokenSource(s_processTerminationTimeout); + + try + { + await process.WaitForExitAsync(cleanupSource.Token); + await Task.WhenAll(stdout, stderr).WaitAsync(cleanupSource.Token); + } + catch (OperationCanceledException) when (cleanupSource.IsCancellationRequested) + { + outputSource.Cancel(); + } + catch (IOException) + { + outputSource.Cancel(); + } + catch (ObjectDisposedException) + { + outputSource.Cancel(); + } + } + + private static bool HasExited(Process process) + { + try + { + return process.HasExited; + } + catch (InvalidOperationException) + { + return true; + } + } + + private static void TryKillProcess(Process process, bool entireProcessTree) + { + try + { + if (!process.HasExited) + { + process.Kill(entireProcessTree); + } + } + catch (InvalidOperationException) + { + } + catch (NotSupportedException) + { + } + catch (Win32Exception) + { + } + } + + private static void DeleteExistingArtifact(string path) + { + if (File.Exists(path)) + { + File.Delete(path); + } + } + + private readonly record struct CapturedOutput( + string Output, + long CharacterCount, + bool WasTruncated); +} + +internal sealed record CommandLineSchemaExportResult( + string SchemaPath, + string SettingsPath, + string ProjectPath, + string Configuration, + string TargetFramework, + string? RuntimeIdentifier); + +internal sealed record CommandLineProcessResult( + int ExitCode, + string StandardOutput, + bool StandardOutputWasTruncated, + long StandardErrorCharacterCount); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs index cb38240d4c0..79e340c3bbb 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs @@ -72,6 +72,53 @@ public static IResourceBuilder WithGraphQLSchemaFile( return builder; } + /// + /// Marks a project resource as exporting a GraphQL schema through the Hot Chocolate + /// command-line schema exporter during publishing. + /// + /// The project resource builder. + /// The exact registered schema name to export. + /// The build configuration used by dotnet run. + /// The exact target framework used by dotnet run. + /// + /// The runtime identifier used by dotnet run, or for an + /// explicitly portable export. + /// + /// The maximum time allowed for the child process. + /// The resource builder for chaining. + public static IResourceBuilder WithGraphQLSchemaExport( + this IResourceBuilder builder, + string schemaName, + string configuration, + string targetFramework, + string? runtimeIdentifier, + TimeSpan timeout) + { + ArgumentException.ThrowIfNullOrWhiteSpace(schemaName); + ArgumentException.ThrowIfNullOrWhiteSpace(configuration); + ArgumentException.ThrowIfNullOrWhiteSpace(targetFramework); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(timeout, TimeSpan.Zero); + + if (runtimeIdentifier is not null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(runtimeIdentifier); + } + + builder.WithAnnotation( + new GraphQLSourceSchemaAnnotation + { + SourceSchemaName = schemaName, + ExportSchemaName = schemaName, + ExportConfiguration = configuration, + ExportTargetFramework = targetFramework, + ExportRuntimeIdentifier = runtimeIdentifier, + ExportTimeout = timeout, + Location = SourceSchemaLocationType.CommandLineExport + }); + + return builder; + } + /// /// Marks a resource as needing GraphQL schema composition from its referenced subgraphs. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceModel.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceModel.cs new file mode 100644 index 00000000000..652df83481e --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceModel.cs @@ -0,0 +1,115 @@ +using System.Diagnostics.CodeAnalysis; +using Aspire.Hosting.ApplicationModel; +using IOPath = System.IO.Path; + +namespace HotChocolate.Fusion.Aspire; + +internal static class GraphQLResourceModel +{ + [SuppressMessage( + "Trimming", + "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' " + + "in call to target method.")] + public static IReadOnlyList GetReferencedSourceSchemas( + IResourceWithEndpoints compositionResource, + DistributedApplicationModel model) + { + ArgumentNullException.ThrowIfNull(compositionResource); + ArgumentNullException.ThrowIfNull(model); + + var referencedResources = new HashSet( + ReferenceEqualityComparer.Instance); + + foreach (var annotation in compositionResource.Annotations) + { + switch (annotation) + { + case ResourceRelationshipAnnotation relationship: + referencedResources.Add(relationship.Resource); + break; + + case var endpointReference + when endpointReference.GetType().FullName + == "Aspire.Hosting.ApplicationModel.EndpointReferenceAnnotation": + var resourceProperty = endpointReference.GetType().GetProperty("Resource"); + if (resourceProperty?.GetValue(endpointReference) is IResource resource) + { + referencedResources.Add(resource); + } + break; + } + } + + return model.Resources + .OfType() + .Where(resource => referencedResources.Contains(resource)) + .Where(resource => resource.HasGraphQLSchema()) + .Select(resource => new GraphQLSourceSchemaResource( + resource, + GetSourceSchema(resource))) + .ToArray(); + } + + public static GraphQLSourceSchemaAnnotation GetSourceSchema( + IResource resource) + => resource.Annotations.OfType().Single(); + + public static GraphQLSchemaCompositionAnnotation GetComposition( + IResource resource) + => resource.Annotations.OfType().Single(); + + public static GraphQLProjectSchemaPaths GetProjectSchemaPaths( + IResource resource, + GraphQLSourceSchemaAnnotation declaration) + { + ArgumentNullException.ThrowIfNull(resource); + ArgumentNullException.ThrowIfNull(declaration); + + if (declaration.Location is not SourceSchemaLocationType.ProjectDirectory) + { + throw new InvalidOperationException( + $"GraphQL source resource '{resource.Name}' does not use a project schema file."); + } + + var projectPath = IOPath.GetFullPath(GetProjectPath(resource)); + var projectDirectory = IOPath.GetDirectoryName(projectPath)!; + var schemaPath = IOPath.GetFullPath( + IOPath.Combine( + projectDirectory, + declaration.SchemaPath ?? "schema.graphqls")); + var schemaDirectory = IOPath.GetDirectoryName(schemaPath)!; + var schemaFileName = IOPath.GetFileNameWithoutExtension(schemaPath); + + return new( + projectPath, + schemaPath, + IOPath.Combine(schemaDirectory, $"{schemaFileName}-settings.json"), + IOPath.Combine( + schemaDirectory, + schemaFileName + "-extensions" + IOPath.GetExtension(schemaPath))); + } + + [SuppressMessage( + "Trimming", + "IL2026:Members attributed with RequiresUnreferencedCodeAttribute require dynamic access otherwise.")] + public static string GetProjectPath(IResource resource) + { + if (resource is not ProjectResource projectResource) + { + throw new InvalidOperationException( + $"GraphQL source resource '{resource.Name}' must be a project resource."); + } + + return projectResource.GetProjectMetadata().ProjectPath; + } +} + +internal readonly record struct GraphQLSourceSchemaResource( + IResourceWithEndpoints Resource, + GraphQLSourceSchemaAnnotation Declaration); + +internal readonly record struct GraphQLProjectSchemaPaths( + string ProjectPath, + string SchemaPath, + string SettingsPath, + string ExtensionsPath); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaAnnotation.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaAnnotation.cs index 1a42dd711ed..46be67d7a82 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaAnnotation.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaAnnotation.cs @@ -13,5 +13,15 @@ internal sealed class GraphQLSourceSchemaAnnotation : IResourceAnnotation /// public string? SchemaPath { get; init; } + public string? ExportSchemaName { get; init; } + + public string? ExportConfiguration { get; init; } + + public string? ExportTargetFramework { get; init; } + + public string? ExportRuntimeIdentifier { get; init; } + + public TimeSpan? ExportTimeout { get; init; } + public required SourceSchemaLocationType Location { get; init; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaValidator.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaValidator.cs new file mode 100644 index 00000000000..acb8f77cdc2 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/GraphQLSourceSchemaValidator.cs @@ -0,0 +1,45 @@ +using HotChocolate.Fusion.Logging; + +namespace HotChocolate.Fusion.Aspire; + +internal static class GraphQLSourceSchemaValidator +{ + public static void Validate( + string resourceName, + SchemaEndpointConfiguration configuration, + string sourceText, + string? extensionsSourceText = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); + + if (string.IsNullOrWhiteSpace(sourceText)) + { + throw new InvalidOperationException( + $"Schema export for resource '{resourceName}' produced empty GraphQL SDL."); + } + + if (extensionsSourceText is not null + && string.IsNullOrWhiteSpace(extensionsSourceText)) + { + throw new InvalidOperationException( + $"Schema extensions for resource '{resourceName}' contain empty GraphQL SDL."); + } + + var log = new CompositionLog(); + var result = new SourceSchemaParser( + new SourceSchemaText( + configuration.SourceSchemaName, + sourceText, + extensionsSourceText), + log, + isApolloFederationV1: + configuration.ApolloFederationVersion is ApolloFederationVersion.Version1) + .Parse(); + + if (result.IsFailure) + { + throw new InvalidOperationException( + $"Schema for resource '{resourceName}' is not valid GraphQL SDL."); + } + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj b/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj index dad78802960..aa3681f56d8 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj @@ -9,6 +9,7 @@ + @@ -20,6 +21,7 @@ + diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs index 9f17fbfe569..8ba8a5f32a1 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; @@ -133,8 +132,8 @@ private async Task> DiscoverReferencedSourceSchemasAsync( { var sourceSchemas = new List(); - // Get all resources referenced by the composition resource - var referencedResources = GetReferencedResources(compositionResource, appModel); + var referencedResources = + GraphQLResourceModel.GetReferencedSourceSchemas(compositionResource, appModel); logger.LogInformation( "Found {Count} referenced resources for {ResourceName}", @@ -142,17 +141,12 @@ private async Task> DiscoverReferencedSourceSchemasAsync( try { - foreach (var referencedResource in referencedResources) + foreach (var referencedSource in referencedResources) { - if (!referencedResource.HasGraphQLSchema()) - { - logger.LogDebug( - "Resource {ResourceName} does not have a GraphQL schema, skipping", - referencedResource.Name); - continue; - } - - var schemaInfo = await GetSourceSchemaInfoAsync(referencedResource, cancellationToken); + var schemaInfo = await GetSourceSchemaInfoAsync( + referencedSource.Resource, + referencedSource.Declaration, + cancellationToken); if (schemaInfo is null) { continue; @@ -179,51 +173,11 @@ private async Task> DiscoverReferencedSourceSchemasAsync( return sourceSchemas; } - [SuppressMessage( - "Trimming", - "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' " - + "in call to target method. The return value of the source method does not have matching annotations.")] - [SuppressMessage("ReSharper", "UnusedVariable")] - private List GetReferencedResources( - IResourceWithEndpoints compositionResource, - DistributedApplicationModel appModel) - { - var referencedResourceNames = new HashSet(); - - foreach (var annotation in compositionResource.Annotations) - { - switch (annotation) - { - case ResourceRelationshipAnnotation rel: - referencedResourceNames.Add(rel.Resource.Name); - break; - - case var endpointRef when annotation.GetType().Name == "EndpointReferenceAnnotation": - var targetResourceProp = annotation.GetType().GetProperty("Resource"); - if (targetResourceProp?.GetValue(annotation) is IResource targetResource) - { - referencedResourceNames.Add(targetResource.Name); - } - break; - } - } - - return appModel.Resources - .OfType() - .Where(r => referencedResourceNames.Contains(r.Name)) - .ToList(); - } - private async Task GetSourceSchemaInfoAsync( IResourceWithEndpoints resource, + GraphQLSourceSchemaAnnotation sourceSchemaSettings, CancellationToken cancellationToken) { - var sourceSchemaSettings = resource.Annotations.OfType().FirstOrDefault(); - if (sourceSchemaSettings is null) - { - return null; - } - switch (sourceSchemaSettings.Location) { case SourceSchemaLocationType.SchemaEndpoint: @@ -235,6 +189,12 @@ private List GetReferencedResources( case SourceSchemaLocationType.ProjectDirectory: return await GetSourceSchemaFromFileAsync(resource, sourceSchemaSettings, cancellationToken); + case SourceSchemaLocationType.CommandLineExport: + return await GetSourceSchemaFromCommandLineAsync( + resource, + sourceSchemaSettings, + cancellationToken); + default: logger.LogWarning( "Unknown schema location type {LocationType} for {ResourceName}", @@ -244,6 +204,86 @@ private List GetReferencedResources( } } + private async Task GetSourceSchemaFromCommandLineAsync( + IResourceWithEndpoints resource, + GraphQLSourceSchemaAnnotation annotation, + CancellationToken cancellationToken) + { + var outputDirectory = IOPath.Combine( + IOPath.GetTempPath(), + "hotchocolate-fusion-aspire", + Guid.NewGuid().ToString("N")); + + try + { + var result = await CommandLineSchemaExporter.ExportAsync( + resource, + annotation, + outputDirectory, + cancellationToken); + var schemaText = await File.ReadAllTextAsync( + result.SchemaPath, + cancellationToken); + var settings = JsonDocument.Parse( + await File.ReadAllTextAsync(result.SettingsPath, cancellationToken)); + var ownershipTransferred = false; + + try + { + var configuration = ReadEndpointConfiguration( + resource.Name, + annotation.SourceSchemaName, + settings); + GraphQLSourceSchemaValidator.Validate( + resource.Name, + configuration, + schemaText); + + var sourceSchema = new SourceSchemaInfo + { + Name = configuration.SourceSchemaName, + ResourceName = resource.Name, + Schema = new SourceSchemaText(configuration.SourceSchemaName, schemaText), + SchemaSettings = settings + }; + + ownershipTransferred = true; + return sourceSchema; + } + finally + { + if (!ownershipTransferred) + { + settings.Dispose(); + } + } + } + finally + { + try + { + if (Directory.Exists(outputDirectory)) + { + Directory.Delete(outputDirectory, recursive: true); + } + } + catch (IOException exception) + { + logger.LogWarning( + exception, + "Could not remove schema export directory for {ResourceName}.", + resource.Name); + } + catch (UnauthorizedAccessException exception) + { + logger.LogWarning( + exception, + "Could not remove schema export directory for {ResourceName}.", + resource.Name); + } + } + } + private async Task GetSourceSchemaFromEndpointAsync( IResourceWithEndpoints resource, GraphQLSourceSchemaAnnotation annotation, @@ -286,6 +326,11 @@ private List GetReferencedResources( return null; } + GraphQLSourceSchemaValidator.Validate( + resource.Name, + endpointConfiguration, + schemaText); + var sourceSchema = new SourceSchemaInfo { Name = endpointConfiguration.SourceSchemaName, @@ -363,9 +408,12 @@ internal static SchemaEndpointConfiguration ReadEndpointConfiguration( GraphQLSourceSchemaAnnotation annotation, CancellationToken cancellationToken) { - var sourceSchemaName = resource.GetGraphQLSourceSchemaName() ?? resource.Name; + var sourceSchemaName = annotation.SourceSchemaName ?? resource.Name; - var schemaPath = annotation.SchemaPath ?? "schema.graphql"; + var schemaPaths = GraphQLResourceModel.GetProjectSchemaPaths( + resource, + annotation); + var schemaPath = schemaPaths.SchemaPath; if (IsExtensionsSchemaPath(schemaPath)) { @@ -375,30 +423,60 @@ internal static SchemaEndpointConfiguration ReadEndpointConfiguration( return null; } - var schemaFromFile = await ReadSchemaFromProjectDirectoryAsync(resource, schemaPath, cancellationToken); + var schemaFromFile = await ReadSchemaFromProjectDirectoryAsync( + resource, + schemaPath, + cancellationToken); if (schemaFromFile is not { } schemaFiles) { return null; } - // For file schemas, settings file is named after the schema file - // e.g., "foo.graphql" -> "foo-settings.json" - var settingsFileName = $"{IOPath.GetFileNameWithoutExtension(schemaPath)}-settings.json"; - - var schemaSettings = await GetSourceSchemaSettingsAsync(resource, settingsFileName, cancellationToken); + var schemaSettings = await GetSourceSchemaSettingsAsync( + resource, + schemaPaths.SettingsPath, + cancellationToken); if (schemaSettings == null) { return null; } - return new SourceSchemaInfo + var ownershipTransferred = false; + + try { - Name = sourceSchemaName, - ResourceName = resource.Name, - HttpEndpointUrl = null, // No HTTP endpoint for file-based schemas - Schema = new SourceSchemaText(sourceSchemaName, schemaFiles.Schema, schemaFiles.Extensions), - SchemaSettings = schemaSettings - }; + var configuration = ReadEndpointConfiguration( + resource.Name, + sourceSchemaName, + schemaSettings); + GraphQLSourceSchemaValidator.Validate( + resource.Name, + configuration, + schemaFiles.Schema, + schemaFiles.Extensions); + + var sourceSchema = new SourceSchemaInfo + { + Name = configuration.SourceSchemaName, + ResourceName = resource.Name, + HttpEndpointUrl = null, + Schema = new SourceSchemaText( + configuration.SourceSchemaName, + schemaFiles.Schema, + schemaFiles.Extensions), + SchemaSettings = schemaSettings + }; + + ownershipTransferred = true; + return sourceSchema; + } + finally + { + if (!ownershipTransferred) + { + schemaSettings.Dispose(); + } + } } private async Task GetSourceSchemaSettingsAsync( @@ -416,7 +494,9 @@ internal static SchemaEndpointConfiguration ReadEndpointConfiguration( } var projectDirectory = IOPath.GetDirectoryName(projectPath); - var settingsFile = IOPath.Combine(projectDirectory!, settingsFileName); + var settingsFile = IOPath.IsPathRooted(settingsFileName) + ? settingsFileName + : IOPath.Combine(projectDirectory!, settingsFileName); if (!File.Exists(settingsFile)) { @@ -556,7 +636,10 @@ internal static SchemaEndpointConfiguration ReadEndpointConfiguration( } var projectDirectory = IOPath.GetDirectoryName(projectPath); - var schemaFile = IOPath.Combine(projectDirectory!, fileName ?? "schema.graphql"); + var schemaPath = fileName ?? "schema.graphqls"; + var schemaFile = IOPath.IsPathRooted(schemaPath) + ? schemaPath + : IOPath.Combine(projectDirectory!, schemaPath); if (!File.Exists(schemaFile)) { @@ -591,42 +674,19 @@ internal static SchemaEndpointConfiguration ReadEndpointConfiguration( } } - [SuppressMessage( - "Trimming", - "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' " - + "in call to target method. The return value of the source method does not have matching annotations.")] private string? GetProjectPath(IResourceWithEndpoints resource) { - // Check if this is a ProjectResource - if (resource is not ProjectResource projectResource) + try { - return null; + return GraphQLResourceModel.GetProjectPath(resource); } - - // Get the project metadata from the ProjectResource - // The metadata is typically stored as an annotation or property - var metadataAnnotation = projectResource.Annotations - .FirstOrDefault(a => a.GetType().GetInterfaces().Contains(typeof(IProjectMetadata))); - - if (metadataAnnotation is IProjectMetadata projectMetadata) + catch (InvalidOperationException) { - return projectMetadata.ProjectPath; - } - - // Alternative approach: look for the metadata in the resource's type or properties - // Sometimes the metadata might be accessible through reflection on the resource itself - var metadataProperty = projectResource.GetType() - .GetProperties() - .FirstOrDefault(p => p.PropertyType.GetInterfaces().Contains(typeof(IProjectMetadata))); - - if (metadataProperty != null) - { - var metadata = metadataProperty.GetValue(projectResource) as IProjectMetadata; - return metadata?.ProjectPath; + logger.LogWarning( + "Could not find project metadata for resource {ResourceName}", + resource.Name); + return null; } - - logger.LogWarning("Could not find project metadata for resource {ResourceName}", resource.Name); - return null; } private async Task ComposeSchemaAsync( diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/SourceSchemaLocationType.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/SourceSchemaLocationType.cs index 2a2ba82cdf0..3251baff00e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/SourceSchemaLocationType.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/SourceSchemaLocationType.cs @@ -3,5 +3,6 @@ namespace HotChocolate.Fusion.Aspire; internal enum SourceSchemaLocationType { ProjectDirectory, - SchemaEndpoint + SchemaEndpoint, + CommandLineExport } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/CommandLineSchemaExporterTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/CommandLineSchemaExporterTests.cs new file mode 100644 index 00000000000..b3f90857b69 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/CommandLineSchemaExporterTests.cs @@ -0,0 +1,337 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace HotChocolate.Fusion.Aspire; + +public sealed class CommandLineSchemaExporterTests +{ + [Fact] + public void CreateStartInfo_Should_UseExplicitArgumentSafeBuildInputs() + { + var projectPath = GetTestProjectFile(); + var schemaPath = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "schema export", + "schema.graphqls"); + + var startInfo = CommandLineSchemaExporter.CreateStartInfo( + projectPath, + schemaPath, + "Products Schema", + "Release", + "net9.0", + "linux-x64"); + + string.Join(Environment.NewLine, startInfo.ArgumentList).MatchInlineSnapshot( + $$""" + run + --project + {{projectPath}} + --configuration + Release + --framework + net9.0 + --runtime + linux-x64 + --no-launch-profile + -- + schema + export + --output + {{schemaPath}} + --schema-name + Products Schema + """); + } + + [Fact] + public void CreateStartInfo_Should_OmitRuntimeArgument_When_ExportIsExplicitlyPortable() + { + var startInfo = CommandLineSchemaExporter.CreateStartInfo( + GetTestProjectFile(), + System.IO.Path.Combine(System.IO.Path.GetTempPath(), "schema.graphqls"), + "Products", + "Release", + "net9.0", + runtimeIdentifier: null); + + Assert.Equal( + [ + "run", + "--project", + GetTestProjectFile(), + "--configuration", + "Release", + "--framework", + "net9.0", + "--no-launch-profile", + "--", + "schema", + "export", + "--output", + System.IO.Path.Combine(System.IO.Path.GetTempPath(), "schema.graphqls"), + "--schema-name", + "Products" + ], + startInfo.ArgumentList); + } + + [Fact] + public void ApplyEnvironmentAllowList_Should_RemoveSecretsAndKeepOsBootstrapVariables() + { + var startInfo = new ProcessStartInfo(); + startInfo.Environment.Clear(); + startInfo.Environment["PATH"] = "dotnet-path"; + startInfo.Environment["DOTNET_ROOT"] = "dotnet-root"; + startInfo.Environment["NUGET_PACKAGES"] = "packages"; + startInfo.Environment["NITRO_API_KEY"] = "secret"; + + if (OperatingSystem.IsWindows()) + { + startInfo.Environment["SystemRoot"] = "system-root"; + startInfo.Environment["USERPROFILE"] = "user-profile"; + } + else + { + startInfo.Environment["HOME"] = "user-home"; + startInfo.Environment["path"] = "wrong-case-path"; + } + + CommandLineSchemaExporter.ApplyEnvironmentAllowList(startInfo); + + var expected = OperatingSystem.IsWindows() + ? new Dictionary + { + ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1", + ["DOTNET_NOLOGO"] = "1", + ["DOTNET_ROOT"] = "dotnet-root", + ["NUGET_PACKAGES"] = "packages", + ["PATH"] = "dotnet-path", + ["SystemRoot"] = "system-root", + ["USERPROFILE"] = "user-profile" + } + : new Dictionary + { + ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1", + ["DOTNET_NOLOGO"] = "1", + ["DOTNET_ROOT"] = "dotnet-root", + ["HOME"] = "user-home", + ["NUGET_PACKAGES"] = "packages", + ["PATH"] = "dotnet-path" + }; + + Assert.Equal( + expected.OrderBy(t => t.Key), + startInfo.Environment.OrderBy(t => t.Key)); + } + + [Fact] + public async Task RunProcessAsync_Should_DrainPipesAndBoundRetainedOutput_When_OutputIsLarge() + { + var startInfo = CreateLargeOutputProcess(); + + var result = await CommandLineSchemaExporter.RunProcessAsync( + startInfo, + TimeSpan.FromSeconds(30), + "products", + TestContext.Current.CancellationToken); + + $$""" + Exit code: {{result.ExitCode}} + Standard output length: {{result.StandardOutput.Length}} + Standard output truncated: {{result.StandardOutputWasTruncated}} + Standard error characters: {{result.StandardErrorCharacterCount > 32 * 1024}} + Secret retained: {{result.ToString().Contains("stderr-secret", StringComparison.Ordinal)}} + """.MatchInlineSnapshot( + """ + Exit code: 7 + Standard output length: 32768 + Standard output truncated: True + Standard error characters: True + Secret retained: False + """); + } + + [Fact] + public async Task RunProcessAsync_Should_KillProcessTreeAndPreserveToken_When_Canceled() + { + var startInfo = CreateLongRunningProcess(); + using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(500)); + var timer = Stopwatch.StartNew(); + + var exception = await Assert.ThrowsAnyAsync( + () => CommandLineSchemaExporter.RunProcessAsync( + startInfo, + TimeSpan.FromMinutes(1), + "products", + cancellation.Token)); + + Assert.Equal(cancellation.Token, exception.CancellationToken); + Assert.True( + timer.Elapsed < TimeSpan.FromSeconds(10), + $"Cancellation cleanup took {timer.Elapsed}."); + } + + [Fact] + public async Task ValidateArtifactsAsync_Should_AcceptExactNameAndValidSchema() + { + using var directory = new TemporaryDirectory(); + var schemaPath = directory.WriteFile( + "schema.graphqls", + "type Query { product: String }"); + var settingsPath = directory.WriteFile( + "schema-settings.json", + """{ "name": "Products" }"""); + + await CommandLineSchemaExporter.ValidateArtifactsAsync( + "products", + "Products", + schemaPath, + settingsPath, + TestContext.Current.CancellationToken); + } + + [Fact] + public async Task ValidateArtifactsAsync_Should_RejectNameMismatch() + { + using var directory = new TemporaryDirectory(); + var schemaPath = directory.WriteFile( + "schema.graphqls", + "type Query { product: String }"); + var settingsPath = directory.WriteFile( + "schema-settings.json", + """{ "name": "Inventory" }"""); + + var exception = await Assert.ThrowsAsync( + () => CommandLineSchemaExporter.ValidateArtifactsAsync( + "products", + "Products", + schemaPath, + settingsPath, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The configured source schema name 'Products' for resource 'products' " + + "does not match schema-settings.json name 'Inventory'.", + exception.Message); + } + + [Theory] + [InlineData(" ", "Schema export for resource 'products' produced empty GraphQL SDL.")] + [InlineData( + "type Query {", + "Schema for resource 'products' is not valid GraphQL SDL.")] + public async Task ValidateArtifactsAsync_Should_RejectInvalidSchema( + string schema, + string expectedMessage) + { + using var directory = new TemporaryDirectory(); + var schemaPath = directory.WriteFile("schema.graphqls", schema); + var settingsPath = directory.WriteFile( + "schema-settings.json", + """{ "name": "Products" }"""); + + var exception = await Assert.ThrowsAsync( + () => CommandLineSchemaExporter.ValidateArtifactsAsync( + "products", + "Products", + schemaPath, + settingsPath, + TestContext.Current.CancellationToken)); + + Assert.Equal(expectedMessage, exception.Message); + } + + private static ProcessStartInfo CreateLargeOutputProcess() + { + if (OperatingSystem.IsWindows()) + { + return CreateProcess( + "cmd.exe", + "/D", + "/S", + "/C", + "(for /L %i in (1,1,4000) do @echo 01234567890123456789)" + + "&(for /L %i in (1,1,4000) do @echo stderr-secret-0123456789 1>&2)" + + "&exit /b 7"); + } + + return CreateProcess( + "/bin/sh", + "-c", + "i=0; while [ $i -lt 4000 ]; do " + + "printf '01234567890123456789\\n'; " + + "printf 'stderr-secret-0123456789\\n' >&2; " + + "i=$((i + 1)); done; exit 7"); + } + + private static ProcessStartInfo CreateLongRunningProcess() + { + if (OperatingSystem.IsWindows()) + { + return CreateProcess( + "cmd.exe", + "/D", + "/S", + "/C", + "ping -t 127.0.0.1 >nul"); + } + + return CreateProcess( + "/bin/sh", + "-c", + "while true; do sleep 1; done"); + } + + private static ProcessStartInfo CreateProcess( + string fileName, + params string[] arguments) + { + var startInfo = new ProcessStartInfo + { + FileName = fileName, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + foreach (var argument in arguments) + { + startInfo.ArgumentList.Add(argument); + } + + return startInfo; + } + + private static string GetTestProjectFile([CallerFilePath] string sourceFile = "") + => System.IO.Path.GetFullPath( + System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(sourceFile)!, + "HotChocolate.Fusion.Aspire.Tests.csproj")); + + private sealed class TemporaryDirectory : IDisposable + { + private readonly string _path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "fusion-aspire-tests", + Guid.NewGuid().ToString("N")); + + public TemporaryDirectory() + { + Directory.CreateDirectory(_path); + } + + public string WriteFile(string fileName, string content) + { + var path = System.IO.Path.Combine(_path, fileName); + File.WriteAllText(path, content); + return path; + } + + public void Dispose() + { + Directory.Delete(_path, recursive: true); + } + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceBuilderExtensionsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceBuilderExtensionsTests.cs index 34896663e9a..de243372373 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceBuilderExtensionsTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceBuilderExtensionsTests.cs @@ -50,6 +50,62 @@ public void WithGraphQLSchemaEndpoint_Should_RejectPath_When_PathIsNotRooted() exception.Message); } + [Fact] + public void WithGraphQLSchemaExport_Should_PreserveExplicitBuildInputs() + { + var builder = DistributedApplication.CreateBuilder(); + var resource = builder + .AddProject("products", GetTestProjectFile()) + .WithGraphQLSchemaExport( + "Products", + "Release", + "net9.0", + "linux-x64", + TimeSpan.FromMinutes(2)); + + var annotation = Assert.Single( + resource.Resource.Annotations.OfType()); + + $$""" + Source schema name: {{annotation.SourceSchemaName}} + Export schema name: {{annotation.ExportSchemaName}} + Configuration: {{annotation.ExportConfiguration}} + Target framework: {{annotation.ExportTargetFramework}} + Runtime identifier: {{annotation.ExportRuntimeIdentifier}} + Timeout: {{annotation.ExportTimeout}} + Location: {{annotation.Location}} + """.MatchInlineSnapshot( + """ + Source schema name: Products + Export schema name: Products + Configuration: Release + Target framework: net9.0 + Runtime identifier: linux-x64 + Timeout: 00:02:00 + Location: CommandLineExport + """); + } + + [Fact] + public void WithGraphQLSchemaExport_Should_RejectWhitespaceRuntimeIdentifier() + { + var builder = DistributedApplication.CreateBuilder(); + var resource = builder.AddProject("products", GetTestProjectFile()); + + var exception = Assert.Throws( + () => resource.WithGraphQLSchemaExport( + "Products", + "Release", + "net9.0", + " ", + TimeSpan.FromMinutes(2))); + + Assert.Equal( + "The value cannot be an empty string or composed entirely of whitespace. " + + "(Parameter 'runtimeIdentifier')", + exception.Message); + } + [Theory] [InlineData( null, diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceModelTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceModelTests.cs new file mode 100644 index 00000000000..baf66123cad --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/GraphQLResourceModelTests.cs @@ -0,0 +1,121 @@ +using System.Runtime.CompilerServices; +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; + +namespace HotChocolate.Fusion.Aspire; + +public sealed class GraphQLResourceModelTests +{ + [Fact] + public void GetReferencedSourceSchemas_Should_ReturnSharedResourceAndDeclaration() + { + var builder = DistributedApplication.CreateBuilder(); + var products = builder + .AddProject("products", GetTestProjectFile()) + .WithGraphQLSchemaExport( + "Products", + "Release", + "net9.0", + runtimeIdentifier: null, + TimeSpan.FromMinutes(1)); + var unrelated = builder + .AddProject("unrelated", GetTestProjectFile()) + .WithGraphQLSchemaFile(); + var unreferenced = builder + .AddProject("unreferenced", GetTestProjectFile()) + .WithGraphQLSchemaFile(); + var gateway = builder + .AddProject("gateway", GetTestProjectFile()) + .WithReference(products) + .WithReference(unrelated) + .WithGraphQLSchemaComposition(); + var model = new DistributedApplicationModel( + [ + products.Resource, + unrelated.Resource, + unreferenced.Resource, + gateway.Resource + ]); + + var sources = GraphQLResourceModel.GetReferencedSourceSchemas( + gateway.Resource, + model); + + Assert.Collection( + sources, + source => + { + Assert.Same(products.Resource, source.Resource); + Assert.Same( + GraphQLResourceModel.GetSourceSchema(products.Resource), + source.Declaration); + }, + source => + { + Assert.Same(unrelated.Resource, source.Resource); + Assert.Same( + GraphQLResourceModel.GetSourceSchema(unrelated.Resource), + source.Declaration); + }); + } + + [Fact] + public void GetReferencedSourceSchemas_Should_FailClosed_When_DeclarationIsAmbiguous() + { + var builder = DistributedApplication.CreateBuilder(); + var products = builder + .AddProject("products", GetTestProjectFile()) + .WithGraphQLSchemaFile(); + products.Resource.Annotations.Add( + new GraphQLSourceSchemaAnnotation + { + Location = SourceSchemaLocationType.SchemaEndpoint + }); + var gateway = builder + .AddProject("gateway", GetTestProjectFile()) + .WithReference(products) + .WithGraphQLSchemaComposition(); + var model = new DistributedApplicationModel( + [products.Resource, gateway.Resource]); + + Assert.Throws( + () => GraphQLResourceModel.GetReferencedSourceSchemas( + gateway.Resource, + model)); + } + + [Fact] + public void GetProjectSchemaPaths_Should_ResolveSettingsBesideNestedSchema() + { + var builder = DistributedApplication.CreateBuilder(); + var products = builder + .AddProject("products", GetTestProjectFile()) + .WithGraphQLSchemaFile( + System.IO.Path.Combine("schemas", "products.graphqls"), + "Products"); + var declaration = GraphQLResourceModel.GetSourceSchema(products.Resource); + + var paths = GraphQLResourceModel.GetProjectSchemaPaths( + products.Resource, + declaration); + + var projectDirectory = System.IO.Path.GetDirectoryName(GetTestProjectFile())!; + $$""" + Project: {{System.IO.Path.GetRelativePath(projectDirectory, paths.ProjectPath)}} + Schema: {{System.IO.Path.GetRelativePath(projectDirectory, paths.SchemaPath)}} + Settings: {{System.IO.Path.GetRelativePath(projectDirectory, paths.SettingsPath)}} + Extensions: {{System.IO.Path.GetRelativePath(projectDirectory, paths.ExtensionsPath)}} + """.MatchInlineSnapshot( + """ + Project: HotChocolate.Fusion.Aspire.Tests.csproj + Schema: schemas/products.graphqls + Settings: schemas/products-settings.json + Extensions: schemas/products-extensions.graphqls + """); + } + + private static string GetTestProjectFile([CallerFilePath] string sourceFile = "") + => System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(sourceFile)!, + "HotChocolate.Fusion.Aspire.Tests.csproj"); +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj new file mode 100644 index 00000000000..c0b287ddcc2 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj @@ -0,0 +1,32 @@ + + + + ChilliCream.Nitro.Aspire + ChilliCream.Nitro.Aspire + net11.0;net10.0;net9.0 + false + Integrates deterministic Hot Chocolate Fusion publication workflows with .NET Aspire deployment pipelines. + README.md + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs new file mode 100644 index 00000000000..8527f9d0b37 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs @@ -0,0 +1,32 @@ +using Aspire.Hosting.ApplicationModel; + +namespace ChilliCream.Nitro.Aspire; + +/// +/// Represents an environment-specific Fusion deployment to Nitro. +/// +public sealed class FusionDeploymentResource( + string name, + NitroResource nitro) + : Resource(name) +{ + internal NitroResource Nitro { get; } = nitro; + + internal string? EnvironmentName { get; set; } + + internal string? StageName { get; set; } + + internal string? ConfigurationTag { get; set; } + + internal ParameterResource? ConfigurationTagParameter { get; set; } + + internal bool UseGitCommitAsSourceVersion { get; set; } + + internal bool WaitForApproval { get; set; } + + internal bool Force { get; set; } + + internal TimeSpan OperationTimeout { get; set; } = TimeSpan.FromMinutes(15); + + internal TimeSpan ApprovalTimeout { get; set; } = TimeSpan.FromHours(2); +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs new file mode 100644 index 00000000000..028d46d681b --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs @@ -0,0 +1,283 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Pipelines; +using HotChocolate.Fusion.Aspire; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace ChilliCream.Nitro.Aspire; + +#pragma warning disable ASPIREPIPELINES001 + +internal static class FusionPipeline +{ + internal const string ArtifactsStepName = "fusion-artifacts"; + internal const string ReadinessStepName = "fusion-readiness"; + internal const string UploadStepName = "fusion-upload"; + internal const string PublishStepName = "fusion-publish"; + + public static void Configure( + IResourceBuilder pipeline) + { + var topology = new FusionPipelineTopology(); + + pipeline.WithPipelineStepFactory( + context => CreateSteps(context, topology)); + pipeline.WithPipelineConfiguration( + context => ConfigureSteps(context, topology)); + } + + internal static IReadOnlyList SelectDeployments( + DistributedApplicationModel model, + string environmentName) + { + var deployments = model.Resources + .OfType() + .Where(deployment => + string.Equals( + deployment.EnvironmentName, + environmentName, + StringComparison.Ordinal)) + .ToArray(); + + foreach (var deployment in deployments) + { + ValidateDeclaration(deployment); + } + + var duplicate = deployments + .GroupBy( + deployment => ( + deployment.Nitro.CloudUrl, + deployment.Nitro.ApiId, + deployment.StageName), + FusionDeploymentKeyComparer.Instance) + .FirstOrDefault(group => group.Count() > 1); + + if (duplicate is not null) + { + throw new InvalidOperationException( + $"Multiple Fusion deployments map environment '{environmentName}' to Nitro " + + $"API '{duplicate.Key.ApiId}' stage '{duplicate.Key.StageName}'."); + } + + return deployments; + } + + internal static IResourceWithEndpoints GetCompositionResource( + DistributedApplicationModel model) + { + var compositions = model.GetGraphQLCompositionResources().ToArray(); + return compositions.Length switch + { + 1 => compositions[0], + 0 => throw new InvalidOperationException( + "A Fusion deployment requires one resource with GraphQL schema composition."), + _ => throw new InvalidOperationException( + "A Fusion deployment requires exactly one resource with GraphQL schema composition.") + }; + } + + private static IEnumerable CreateSteps( + PipelineStepFactoryContext context, + FusionPipelineTopology topology) + { + var environment = context.PipelineContext.Services + .GetRequiredService() + .EnvironmentName; + var deployments = SelectDeployments( + context.PipelineContext.Model, + environment); + + topology.EnvironmentName = environment; + topology.HasDeployments = deployments.Count > 0; + + return CreateStepDefinitions(context.Resource, topology); + } + + internal static PipelineStep[] CreateStepDefinitionsForTest( + IResource resource) + => CreateStepDefinitions(resource, new FusionPipelineTopology()); + + private static PipelineStep[] CreateStepDefinitions( + IResource resource, + FusionPipelineTopology topology) + => + [ + new PipelineStep + { + Name = ArtifactsStepName, + Description = "Produce portable Fusion deployment artifacts.", + Resource = resource, + RequiredBySteps = [WellKnownPipelineSteps.Publish], + Action = ExecuteArtifactsAsync + }, + new PipelineStep + { + Name = ReadinessStepName, + Description = "Verify deployed Fusion source services are ready.", + Resource = resource, + DependsOnSteps = [ArtifactsStepName], + Action = stepContext => ExecuteReadinessAsync(stepContext, topology) + }, + new PipelineStep + { + Name = UploadStepName, + Description = "Reconcile immutable Fusion source schema versions.", + Resource = resource, + DependsOnSteps = [ArtifactsStepName], + Action = ExecuteUploadAsync + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Compose and publish the Fusion configuration to Nitro.", + Resource = resource, + DependsOnSteps = [UploadStepName, ReadinessStepName], + RequiredBySteps = [WellKnownPipelineSteps.Deploy], + Action = ExecutePublishAsync + } + ]; + + private static void ConfigureSteps( + PipelineConfigurationContext context, + FusionPipelineTopology topology) + { + if (!topology.HasDeployments) + { + return; + } + + var composition = GetCompositionResource(context.Model); + var sources = GraphQLResourceModel.GetReferencedSourceSchemas( + composition, + context.Model); + var readiness = context.Steps.Single( + step => step.Name == ReadinessStepName); + + topology.SourcesWithoutCompute.Clear(); + + foreach (var source in sources) + { + var computeSteps = context + .GetSteps(source.Resource, WellKnownPipelineTags.DeployCompute) + .ToArray(); + + if (computeSteps.Length == 0) + { + topology.SourcesWithoutCompute.Add(source.Resource.Name); + continue; + } + + foreach (var computeStep in computeSteps) + { + readiness.DependsOn(computeStep); + } + } + } + + private static Task ExecuteArtifactsAsync(PipelineStepContext context) + => GetExecutor(context).CreateArtifactsAsync(context); + + private static Task ExecuteReadinessAsync( + PipelineStepContext context, + FusionPipelineTopology topology) + { + if (topology.SourcesWithoutCompute.Count > 0) + { + throw new InvalidOperationException( + "Fusion publication cannot prove compute deployment ordering for resources: " + + string.Join(", ", topology.SourcesWithoutCompute.Order())); + } + + return GetExecutor(context).VerifyReadinessAsync(context); + } + + private static Task ExecuteUploadAsync(PipelineStepContext context) + => GetExecutor(context).UploadAsync(context); + + private static Task ExecutePublishAsync(PipelineStepContext context) + => GetExecutor(context).PublishAsync(context); + + private static IFusionPipelineExecutor GetExecutor( + PipelineStepContext context) + => context.Services.GetService() + ?? FusionPipelineExecutor.Instance; + + private static void ValidateDeclaration( + FusionDeploymentResource deployment) + { + if (string.IsNullOrWhiteSpace(deployment.EnvironmentName)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' must select an Aspire environment."); + } + + if (string.IsNullOrWhiteSpace(deployment.StageName)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' must select a Nitro stage."); + } + + if (string.IsNullOrWhiteSpace(deployment.Nitro.CloudUrl)) + { + throw new InvalidOperationException( + $"Nitro target '{deployment.Nitro.Name}' must specify a cloud URL."); + } + + if (!Uri.TryCreate( + deployment.Nitro.CloudUrl, + UriKind.Absolute, + out var cloudUri) + || cloudUri.Scheme is not "https") + { + throw new InvalidOperationException( + $"Nitro target '{deployment.Nitro.Name}' cloud URL must use HTTPS."); + } + + if (string.IsNullOrWhiteSpace(deployment.Nitro.ApiId)) + { + throw new InvalidOperationException( + $"Nitro target '{deployment.Nitro.Name}' must specify an API ID."); + } + + if (deployment.ConfigurationTagParameter is null + && string.IsNullOrWhiteSpace(deployment.ConfigurationTag)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' must specify a configuration tag."); + } + } + + private sealed class FusionPipelineTopology + { + public string? EnvironmentName { get; set; } + + public bool HasDeployments { get; set; } + + public HashSet SourcesWithoutCompute { get; } = + new(StringComparer.Ordinal); + } + + private sealed class FusionDeploymentKeyComparer + : IEqualityComparer<(string? CloudUrl, string? ApiId, string? StageName)> + { + public static FusionDeploymentKeyComparer Instance { get; } = new(); + + public bool Equals( + (string? CloudUrl, string? ApiId, string? StageName) x, + (string? CloudUrl, string? ApiId, string? StageName) y) + => string.Equals(x.CloudUrl, y.CloudUrl, StringComparison.OrdinalIgnoreCase) + && string.Equals(x.ApiId, y.ApiId, StringComparison.Ordinal) + && string.Equals(x.StageName, y.StageName, StringComparison.Ordinal); + + public int GetHashCode( + (string? CloudUrl, string? ApiId, string? StageName) obj) + => HashCode.Combine( + StringComparer.OrdinalIgnoreCase.GetHashCode(obj.CloudUrl ?? ""), + StringComparer.Ordinal.GetHashCode(obj.ApiId ?? ""), + StringComparer.Ordinal.GetHashCode(obj.StageName ?? "")); + } +} + +#pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs new file mode 100644 index 00000000000..759015a5160 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs @@ -0,0 +1,930 @@ +using System.Diagnostics; +using System.Net; +using System.Security.Cryptography; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Pipelines; +using ChilliCream.Nitro.Fusion; +using HotChocolate.Fusion.Aspire; +using HotChocolate.Fusion.SourceSchema.Packaging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ChilliCream.Nitro.Aspire; + +#pragma warning disable ASPIREPIPELINES001 +#pragma warning disable ASPIREPIPELINES004 + +internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor +{ + public static FusionPipelineExecutor Instance { get; } = new(); + + public async Task CreateArtifactsAsync(PipelineStepContext context) + { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + + if (deployments.Count == 0) + { + return; + } + + var composition = FusionPipeline.GetCompositionResource(context.Model); + var sources = GraphQLResourceModel.GetReferencedSourceSchemas( + composition, + context.Model); + + if (sources.Count == 0) + { + throw new InvalidOperationException( + $"Fusion composition resource '{composition.Name}' has no declared source schemas."); + } + + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + + foreach (var deployment in deployments) + { + await CreateDeploymentArtifactsAsync( + deployment, + sources, + output, + context.CancellationToken); + } + } + + public async Task VerifyReadinessAsync(PipelineStepContext context) + { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + + if (deployments.Count == 0) + { + return; + } + + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + using var httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(10) + }; + + foreach (var deployment in deployments) + { + var deploymentDirectory = GetDeploymentDirectory(output, deployment); + + foreach (var sourceDirectory in Directory.EnumerateDirectories( + Path.Combine(deploymentDirectory, "sources"))) + { + var settingsPath = Path.Combine( + sourceDirectory, + "schema-settings.template.json"); + using var settings = JsonDocument.Parse( + await File.ReadAllTextAsync( + settingsPath, + context.CancellationToken)); + var endpoint = GetTransportEndpoint(settings); + RejectLoopbackEndpoint(endpoint); + + using var response = await httpClient.GetAsync( + endpoint, + context.CancellationToken); + if ((int)response.StatusCode >= (int)HttpStatusCode.InternalServerError) + { + throw new InvalidOperationException( + $"Fusion source '{Path.GetFileName(sourceDirectory)}' did not pass " + + "its production readiness check."); + } + } + } + } + + public async Task UploadAsync(PipelineStepContext context) + { + var artifacts = await MaterializeArchivesAsync(context); + if (artifacts.Count == 0) + { + return; + } + + var workflow = context.Services.GetRequiredService(); + + foreach (var group in artifacts.GroupBy(artifact => artifact.Deployment)) + { + var target = await ResolveTargetAsync( + group.Key, + context, + context.CancellationToken); + + foreach (var artifact in group) + { + await workflow.ReconcileSourceSchemaAsync( + target, + new FusionSourceSchemaUpload( + artifact.Name, + artifact.Version, + artifact.ArchivePath, + artifact.Sha256), + context.CancellationToken); + } + } + } + + public async Task PublishAsync(PipelineStepContext context) + { + var artifacts = await MaterializeArchivesAsync(context); + if (artifacts.Count == 0) + { + return; + } + + var workflow = context.Services.GetRequiredService(); + var compositionResource = FusionPipeline.GetCompositionResource(context.Model); + var composition = GraphQLResourceModel.GetComposition(compositionResource); + + foreach (var group in artifacts.GroupBy(artifact => artifact.Deployment)) + { + var deployment = group.Key; + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var releaseId = await ResolveConfigurationTagAsync( + deployment, + context.CancellationToken); + var farPath = Path.Combine( + Path.GetDirectoryName(group.First().ArchivePath)!, + $"{releaseId}.far"); + + await ComposeAsync( + farPath, + group.ToArray(), + composition.Settings, + context, + context.CancellationToken); + + await workflow.PublishAsync( + new FusionPublicationRequest( + target, + deployment.StageName!, + releaseId, + group + .Select(artifact => + new FusionSourceSchemaVersion( + artifact.Name, + artifact.Version)) + .ToArray(), + deployment.WaitForApproval, + deployment.Force, + deployment.OperationTimeout, + deployment.ApprovalTimeout), + farPath, + context.CancellationToken); + + await WriteDeploymentManifestAsync( + deployment, + releaseId, + group.ToArray(), + context, + context.CancellationToken); + } + } + + internal async Task> MaterializeArchivesAsync( + PipelineStepContext context) + { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + + if (deployments.Count == 0) + { + return []; + } + + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var artifacts = new List(); + + foreach (var deployment in deployments) + { + var releaseId = await ResolveConfigurationTagAsync( + deployment, + context.CancellationToken); + var deploymentDirectory = GetDeploymentDirectory(output, deployment); + var materializedDirectory = Path.Combine( + deploymentDirectory, + "materialized"); + Directory.CreateDirectory(materializedDirectory); + + foreach (var sourceDirectory in Directory.EnumerateDirectories( + Path.Combine(deploymentDirectory, "sources"))) + { + var name = Path.GetFileName(sourceDirectory); + var sourceVersion = deployment.UseGitCommitAsSourceVersion + ? await ReadGitCommitAsync( + sourceDirectory, + context.CancellationToken) + : releaseId; + ValidatePathSegment(sourceVersion, "source version"); + var schema = await File.ReadAllBytesAsync( + Path.Combine(sourceDirectory, "schema.graphqls"), + context.CancellationToken); + var settingsPath = Path.Combine( + sourceDirectory, + "schema-settings.template.json"); + using var settings = JsonDocument.Parse( + await File.ReadAllTextAsync( + settingsPath, + context.CancellationToken)); + + ValidateSettingsName(name, settings); + + var endpoint = GetTransportEndpoint(settings); + RejectLoopbackEndpoint(endpoint); + + var archivePath = Path.Combine( + materializedDirectory, + $"{name}-{sourceVersion}.zip"); + await CreateArchiveAsync( + archivePath, + schema, + settings, + GetExtensionsPath(sourceDirectory), + context.CancellationToken); + var digest = await ComputeFileDigestAsync( + archivePath, + context.CancellationToken); + + artifacts.Add( + new( + deployment, + name, + sourceVersion, + archivePath, + digest)); + } + } + + return artifacts; + } + + private static async Task CreateDeploymentArtifactsAsync( + FusionDeploymentResource deployment, + IReadOnlyList sources, + string output, + CancellationToken cancellationToken) + { + var deploymentDirectory = GetDeploymentDirectory(output, deployment); + var fusionDirectory = Path.GetDirectoryName(deploymentDirectory)!; + Directory.CreateDirectory(fusionDirectory); + var temporaryDirectory = Path.Combine( + fusionDirectory, + $".{deployment.Name}.{Guid.NewGuid():N}.tmp"); + + try + { + var sourcesDirectory = Path.Combine(temporaryDirectory, "sources"); + Directory.CreateDirectory(sourcesDirectory); + var sourceNames = new List(sources.Count); + + foreach (var source in sources) + { + sourceNames.Add( + await CreateSourceArtifactsAsync( + source, + sourcesDirectory, + cancellationToken)); + } + + var template = new FusionDeploymentTemplate( + FormatVersion: 1, + CloudUrl: deployment.Nitro.CloudUrl!, + ApiId: deployment.Nitro.ApiId!, + Environment: deployment.EnvironmentName!, + Stage: deployment.StageName!, + ConfigurationTag: deployment.ConfigurationTag + ?? $"{{{{{deployment.ConfigurationTagParameter!.Name}}}}}", + StageOwnership: "authoritative", + Sources: sourceNames.Order().ToArray()); + + await WriteJsonAtomicallyAsync( + Path.Combine(temporaryDirectory, "nitro-deployment-template.json"), + template, + cancellationToken); + + ReplaceDirectoryAtomically( + temporaryDirectory, + deploymentDirectory); + } + finally + { + DeleteDirectoryBestEffort(temporaryDirectory); + } + } + + internal static void ReplaceDirectoryAtomically( + string sourceDirectory, + string destinationDirectory) + { + if (!Directory.Exists(sourceDirectory)) + { + throw new DirectoryNotFoundException( + $"Replacement directory '{sourceDirectory}' does not exist."); + } + + var destinationParent = Path.GetDirectoryName(destinationDirectory) + ?? throw new InvalidOperationException( + $"Replacement destination '{destinationDirectory}' has no parent directory."); + Directory.CreateDirectory(destinationParent); + var backupDirectory = Path.Combine( + destinationParent, + $".{Path.GetFileName(destinationDirectory)}.{Guid.NewGuid():N}.bak"); + var movedDestination = false; + + try + { + if (Directory.Exists(destinationDirectory)) + { + Directory.Move(destinationDirectory, backupDirectory); + movedDestination = true; + } + + try + { + Directory.Move(sourceDirectory, destinationDirectory); + } + catch + { + if (movedDestination + && !Directory.Exists(destinationDirectory) + && Directory.Exists(backupDirectory)) + { + Directory.Move(backupDirectory, destinationDirectory); + movedDestination = false; + } + + throw; + } + } + finally + { + if (movedDestination) + { + DeleteDirectoryBestEffort(backupDirectory); + } + } + } + + private static async Task CreateSourceArtifactsAsync( + GraphQLSourceSchemaResource sourceSchema, + string sourcesDirectory, + CancellationToken cancellationToken) + { + var source = sourceSchema.Resource; + var declaration = sourceSchema.Declaration; + + string schemaPath; + string settingsPath; + string? extensionsPath = null; + string projectPath; + string configuration; + string? targetFramework; + string? runtimeIdentifier; + using var temporaryExportDirectory = new TemporaryDirectoryScope(); + + switch (declaration.Location) + { + case SourceSchemaLocationType.ProjectDirectory: + var schemaPaths = GraphQLResourceModel.GetProjectSchemaPaths( + source, + declaration); + projectPath = schemaPaths.ProjectPath; + schemaPath = schemaPaths.SchemaPath; + settingsPath = schemaPaths.SettingsPath; + extensionsPath = schemaPaths.ExtensionsPath; + configuration = "prebuilt"; + targetFramework = null; + runtimeIdentifier = null; + break; + + case SourceSchemaLocationType.CommandLineExport: + var exportDirectory = + temporaryExportDirectory.Create(source.Name); + var export = await CommandLineSchemaExporter.ExportAsync( + source, + declaration, + exportDirectory, + cancellationToken); + schemaPath = export.SchemaPath; + settingsPath = export.SettingsPath; + projectPath = export.ProjectPath; + configuration = export.Configuration; + targetFramework = export.TargetFramework; + runtimeIdentifier = export.RuntimeIdentifier; + break; + + case SourceSchemaLocationType.SchemaEndpoint: + throw new InvalidOperationException( + $"GraphQL source '{source.Name}' uses runtime endpoint acquisition, which " + + "is unavailable during Aspire publish. Declare a schema file or an " + + "explicit command-line export."); + + default: + throw new InvalidOperationException( + $"GraphQL source '{source.Name}' has an unsupported acquisition mode."); + } + + if (!File.Exists(schemaPath) || !File.Exists(settingsPath)) + { + throw new InvalidOperationException( + $"GraphQL source '{source.Name}' did not provide both schema and settings files."); + } + + var schema = await File.ReadAllTextAsync(schemaPath, cancellationToken); + if (string.IsNullOrWhiteSpace(schema)) + { + throw new InvalidOperationException( + $"GraphQL source '{source.Name}' has an empty schema."); + } + + string? extensions = null; + if (extensionsPath is not null && File.Exists(extensionsPath)) + { + extensions = await File.ReadAllTextAsync( + extensionsPath, + cancellationToken); + } + + using var settings = JsonDocument.Parse( + await File.ReadAllTextAsync(settingsPath, cancellationToken)); + var endpointConfiguration = SchemaComposition.ReadEndpointConfiguration( + source.Name, + declaration.SourceSchemaName ?? source.Name, + settings); + var name = endpointConfiguration.SourceSchemaName; + ValidatePathSegment(name, "source schema name"); + GraphQLSourceSchemaValidator.Validate( + source.Name, + endpointConfiguration, + schema, + extensions); + + var sourceDirectory = Path.Combine(sourcesDirectory, name); + Directory.CreateDirectory(sourceDirectory); + var destinationSchema = Path.Combine(sourceDirectory, "schema.graphqls"); + var destinationSettings = Path.Combine( + sourceDirectory, + "schema-settings.template.json"); + await File.WriteAllTextAsync( + destinationSchema, + schema, + cancellationToken); + await File.WriteAllTextAsync( + destinationSettings, + settings.RootElement.GetRawText(), + cancellationToken); + + if (extensions is not null) + { + await File.WriteAllTextAsync( + Path.Combine(sourceDirectory, "schema-extensions.graphqls"), + extensions, + cancellationToken); + } + + var projectDigest = await ComputeFileDigestAsync( + projectPath, + cancellationToken); + var schemaDigest = await ComputeFileDigestAsync( + destinationSchema, + cancellationToken); + var provenance = new FusionSourceProvenance( + ProjectPath: projectPath, + ProjectSha256: projectDigest, + SchemaSha256: schemaDigest, + Configuration: configuration, + TargetFramework: targetFramework, + RuntimeIdentifier: runtimeIdentifier, + LaunchProfile: false, + WorkingDirectory: Path.GetDirectoryName(projectPath)!); + + await WriteJsonAtomicallyAsync( + Path.Combine(sourceDirectory, "provenance.json"), + provenance, + cancellationToken); + + return name; + } + + private static async Task CreateArchiveAsync( + string archivePath, + byte[] schema, + JsonDocument settings, + string? extensionsPath, + CancellationToken cancellationToken) + { + var temporaryPath = archivePath + "." + Guid.NewGuid().ToString("N") + ".tmp"; + + try + { + using (var archive = FusionSourceSchemaArchive.Create(temporaryPath)) + { + await archive.SetArchiveMetadataAsync( + new HotChocolate.Fusion.SourceSchema.Packaging.ArchiveMetadata(), + cancellationToken); + await archive.SetSchemaAsync(schema, cancellationToken); + await archive.SetSettingsAsync(settings, cancellationToken); + + if (extensionsPath is not null) + { + await archive.SetSchemaExtensionsAsync( + await File.ReadAllBytesAsync( + extensionsPath, + cancellationToken), + cancellationToken); + } + + await archive.CommitAsync(cancellationToken); + } + + File.Move(temporaryPath, archivePath, overwrite: true); + } + finally + { + if (File.Exists(temporaryPath)) + { + File.Delete(temporaryPath); + } + } + } + + private static async Task ComposeAsync( + string farPath, + IReadOnlyList artifacts, + GraphQLCompositionSettings settings, + PipelineStepContext context, + CancellationToken cancellationToken) + { + if (File.Exists(farPath)) + { + File.Delete(farPath); + } + + var logger = context.Services + .GetRequiredService>(); + if (!await AspireCompositionHelper.TryComposeArchivesAsync( + farPath, + artifacts.Select( + artifact => new SourceSchemaArchiveInfo( + artifact.Name, + artifact.ArchivePath)) + .ToArray(), + settings, + logger, + cancellationToken)) + { + throw new InvalidOperationException( + "Fusion configuration composition failed."); + } + } + + private static async Task ResolveTargetAsync( + FusionDeploymentResource deployment, + PipelineStepContext context, + CancellationToken cancellationToken) + { + var apiKey = deployment.Nitro.ApiKey is null + ? context.Services.GetRequiredService()["Nitro:ApiKey"] + ?? context.Services.GetRequiredService()["NITRO_API_KEY"] + : await deployment.Nitro.ApiKey.GetValueAsync(cancellationToken); + + if (string.IsNullOrWhiteSpace(apiKey)) + { + throw new InvalidOperationException( + $"Nitro target '{deployment.Nitro.Name}' requires an API key."); + } + + return new( + new Uri(deployment.Nitro.CloudUrl!, UriKind.Absolute), + deployment.Nitro.ApiId!, + apiKey); + } + + private static async Task WriteDeploymentManifestAsync( + FusionDeploymentResource deployment, + string releaseId, + IReadOnlyList artifacts, + PipelineStepContext context, + CancellationToken cancellationToken) + { + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var manifest = new FusionDeploymentManifest( + FormatVersion: 1, + CloudUrl: deployment.Nitro.CloudUrl!, + ApiId: deployment.Nitro.ApiId!, + Environment: deployment.EnvironmentName!, + Stage: deployment.StageName!, + ConfigurationTag: releaseId, + StageOwnership: "authoritative", + Sources: artifacts.Select( + artifact => new FusionDeploymentManifestSource( + artifact.Name, + artifact.Version, + Path.GetRelativePath( + GetDeploymentDirectory(output, deployment), + artifact.ArchivePath), + artifact.Sha256)) + .ToArray()); + + await WriteJsonAtomicallyAsync( + Path.Combine( + GetDeploymentDirectory(output, deployment), + "nitro-deployment.json"), + manifest, + cancellationToken); + } + + internal static Uri GetTransportEndpoint(JsonDocument settings) + { + var root = settings.RootElement; + if (!root.TryGetProperty("transports", out var transports) + || !transports.TryGetProperty("http", out var http) + || !http.TryGetProperty("url", out var url) + || url.ValueKind is not JsonValueKind.String + || !Uri.TryCreate(url.GetString(), UriKind.Absolute, out var endpoint)) + { + throw new InvalidOperationException( + "Fusion deployment settings must specify an absolute production " + + "transports.http.url."); + } + + return endpoint; + } + + private static void RejectLoopbackEndpoint(Uri endpoint) + { + if (endpoint.IsLoopback + || endpoint.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + "Fusion deployment settings must not contain a loopback production endpoint."); + } + } + + private static void ValidateSettingsName( + string expectedName, + JsonDocument settings) + { + if (!settings.RootElement.TryGetProperty("name", out var name) + || name.ValueKind is not JsonValueKind.String + || !string.Equals( + name.GetString(), + expectedName, + StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"Fusion source settings name must exactly match '{expectedName}'."); + } + } + + private static string? GetExtensionsPath(string sourceDirectory) + { + var path = Path.Combine( + sourceDirectory, + "schema-extensions.graphqls"); + return File.Exists(path) ? path : null; + } + + private static async Task ResolveConfigurationTagAsync( + FusionDeploymentResource deployment, + CancellationToken cancellationToken) + { + var value = deployment.ConfigurationTag; + if (deployment.ConfigurationTagParameter is not null) + { + value = await deployment.ConfigurationTagParameter.GetValueAsync( + cancellationToken); + } + + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' configuration tag resolved to an empty value."); + } + + ValidatePathSegment(value, "configuration tag"); + return value; + } + + private static async Task ReadGitCommitAsync( + string sourceDirectory, + CancellationToken cancellationToken) + { + using var provenance = JsonDocument.Parse( + await File.ReadAllTextAsync( + Path.Combine(sourceDirectory, "provenance.json"), + cancellationToken)); + var workingDirectory = provenance.RootElement + .GetProperty("workingDirectory") + .GetString()!; + var startInfo = new ProcessStartInfo + { + FileName = "git", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + startInfo.ArgumentList.Add("-C"); + startInfo.ArgumentList.Add(workingDirectory); + startInfo.ArgumentList.Add("rev-parse"); + startInfo.ArgumentList.Add("HEAD"); + + using var process = Process.Start(startInfo) + ?? throw new InvalidOperationException("Could not start Git."); + var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); + + if (process.ExitCode != 0 || string.IsNullOrWhiteSpace(output)) + { + throw new InvalidOperationException( + "Could not resolve the Git commit for a Fusion source version."); + } + + return output.Trim(); + } + + internal static void ValidatePathSegment( + string value, + string description) + { + if (value is "." or ".." + || value.Any(character => + !char.IsAsciiLetterOrDigit(character) + && character is not '.' and not '_' and not '-')) + { + throw new InvalidOperationException( + $"Fusion {description} '{value}' cannot be used as a portable path segment."); + } + } + + private static async Task ComputeFileDigestAsync( + string path, + CancellationToken cancellationToken) + { + await using var stream = File.OpenRead(path); + var digest = await SHA256.HashDataAsync(stream, cancellationToken); + return Convert.ToHexStringLower(digest); + } + + private static async Task WriteJsonAtomicallyAsync( + string path, + T value, + CancellationToken cancellationToken) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + var temporaryPath = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; + + try + { + await using (var stream = File.Create(temporaryPath)) + { + await JsonSerializer.SerializeAsync( + stream, + value, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }, + cancellationToken); + } + + File.Move(temporaryPath, path, overwrite: true); + } + finally + { + if (File.Exists(temporaryPath)) + { + File.Delete(temporaryPath); + } + } + } + + private static string GetDeploymentDirectory( + string output, + FusionDeploymentResource deployment) + => Path.Combine(output, "fusion", deployment.Name); + + private static void DeleteDirectoryBestEffort(string path) + { + try + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + } + + private sealed class TemporaryDirectoryScope : IDisposable + { + private string? _path; + + public string Create(string resourceName) + { + _path = Path.Combine( + Path.GetTempPath(), + "chilicream-nitro-aspire", + Guid.NewGuid().ToString("N"), + resourceName); + return _path; + } + + public void Dispose() + { + try + { + if (_path is not null && Directory.Exists(_path)) + { + Directory.Delete(_path, recursive: true); + } + } + catch (IOException) + { + } + } + } +} + +internal sealed record FusionSourceArtifact( + FusionDeploymentResource Deployment, + string Name, + string Version, + string ArchivePath, + string Sha256); + +internal sealed record FusionDeploymentTemplate( + int FormatVersion, + string CloudUrl, + string ApiId, + string Environment, + string Stage, + string ConfigurationTag, + string StageOwnership, + IReadOnlyList Sources); + +internal sealed record FusionSourceProvenance( + string ProjectPath, + string ProjectSha256, + string SchemaSha256, + string Configuration, + string? TargetFramework, + string? RuntimeIdentifier, + bool LaunchProfile, + string WorkingDirectory); + +internal sealed record FusionDeploymentManifest( + int FormatVersion, + string CloudUrl, + string ApiId, + string Environment, + string Stage, + string ConfigurationTag, + string StageOwnership, + IReadOnlyList Sources); + +internal sealed record FusionDeploymentManifestSource( + string Name, + string SourceVersion, + string Archive, + string Sha256); + +#pragma warning restore ASPIREPIPELINES004 +#pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs new file mode 100644 index 00000000000..fac5c024f31 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs @@ -0,0 +1,5 @@ +using Aspire.Hosting.ApplicationModel; + +namespace ChilliCream.Nitro.Aspire; + +internal sealed class FusionPipelineResource(string name) : Resource(name); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs new file mode 100644 index 00000000000..3b3cf938912 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs @@ -0,0 +1,18 @@ +using Aspire.Hosting.Pipelines; + +namespace ChilliCream.Nitro.Aspire; + +#pragma warning disable ASPIREPIPELINES001 + +internal interface IFusionPipelineExecutor +{ + Task CreateArtifactsAsync(PipelineStepContext context); + + Task VerifyReadinessAsync(PipelineStepContext context); + + Task UploadAsync(PipelineStepContext context); + + Task PublishAsync(PipelineStepContext context); +} + +#pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs new file mode 100644 index 00000000000..b3b8a59722a --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs @@ -0,0 +1,15 @@ +using Aspire.Hosting.ApplicationModel; + +namespace ChilliCream.Nitro.Aspire; + +/// +/// Represents a Nitro target in an Aspire application model. +/// +public sealed class NitroResource(string name) : Resource(name) +{ + internal string? CloudUrl { get; set; } + + internal string? ApiId { get; set; } + + internal ParameterResource? ApiKey { get; set; } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs new file mode 100644 index 00000000000..5a59ce81f6e --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs @@ -0,0 +1,238 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; +using ChilliCream.Nitro.Fusion; + +namespace ChilliCream.Nitro.Aspire; + +/// +/// Provides extensions for declaring Nitro Fusion deployments. +/// +public static class NitroResourceBuilderExtensions +{ + /// + /// Adds a Nitro target. + /// + public static IResourceBuilder AddNitro( + this IDistributedApplicationBuilder builder, + string name) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + EnsureFusionPipeline(builder); + builder.Services.AddNitroFusionDeploymentWorkflow(); + + var configuredCloudUrl = + builder.Configuration["Nitro:CloudUrl"] + ?? builder.Configuration["NITRO_CLOUD_URL"]; + var resource = new NitroResource(name) + { + CloudUrl = string.IsNullOrWhiteSpace(configuredCloudUrl) + ? null + : NormalizeCloudUrl(configuredCloudUrl), + ApiId = + builder.Configuration["Nitro:ApiId"] + ?? builder.Configuration["NITRO_API_ID"] + }; + + return builder.AddResource(resource); + } + + /// + /// Sets the Nitro GraphQL API URL. + /// + public static IResourceBuilder WithCloudUrl( + this IResourceBuilder builder, + string cloudUrl) + { + ArgumentNullException.ThrowIfNull(builder); + + builder.Resource.CloudUrl = NormalizeCloudUrl(cloudUrl); + return builder; + } + + /// + /// Sets the Nitro API identifier. + /// + public static IResourceBuilder WithApiId( + this IResourceBuilder builder, + string apiId) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(apiId); + + builder.Resource.ApiId = apiId; + return builder; + } + + /// + /// Sets the secret parameter used as the Nitro API key. + /// + public static IResourceBuilder WithApiKey( + this IResourceBuilder builder, + IResourceBuilder apiKey) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(apiKey); + + if (!apiKey.Resource.Secret) + { + throw new ArgumentException( + "The Nitro API key parameter must be declared as a secret.", + nameof(apiKey)); + } + + builder.Resource.ApiKey = apiKey.Resource; + return builder; + } + + /// + /// Adds an environment-specific Fusion deployment. + /// + public static IResourceBuilder AddFusionDeployment( + this IResourceBuilder builder, + string name) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + var resource = new FusionDeploymentResource(name, builder.Resource); + return builder.ApplicationBuilder.AddResource(resource); + } + + /// + /// Maps the deployment to an exact Aspire environment. + /// + public static IResourceBuilder ForEnvironment( + this IResourceBuilder builder, + string environmentName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + builder.Resource.EnvironmentName = environmentName; + return builder; + } + + /// + /// Maps the deployment to an exact Nitro stage. + /// + public static IResourceBuilder ToStage( + this IResourceBuilder builder, + string stageName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(stageName); + + builder.Resource.StageName = stageName; + return builder; + } + + /// + /// Sets the immutable release tag. + /// + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, + IResourceBuilder configurationTag) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(configurationTag); + + builder.Resource.ConfigurationTagParameter = configurationTag.Resource; + builder.Resource.ConfigurationTag = null; + return builder; + } + + /// + /// Sets the immutable release tag. + /// + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, + string configurationTag) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(configurationTag); + + builder.Resource.ConfigurationTag = configurationTag; + builder.Resource.ConfigurationTagParameter = null; + return builder; + } + + /// + /// Uses the current Git commit as the default source schema version. + /// + public static IResourceBuilder + WithDefaultSourceVersionFromGitCommit( + this IResourceBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + builder.Resource.UseGitCommitAsSourceVersion = true; + return builder; + } + + /// + /// Configures whether Nitro waits for approval. + /// + public static IResourceBuilder WithApproval( + this IResourceBuilder builder, + bool waitForApproval) + { + ArgumentNullException.ThrowIfNull(builder); + builder.Resource.WaitForApproval = waitForApproval; + return builder; + } + + /// + /// Configures whether validation failures may be forced. + /// + public static IResourceBuilder WithForce( + this IResourceBuilder builder, + bool force) + { + ArgumentNullException.ThrowIfNull(builder); + builder.Resource.Force = force; + return builder; + } + + /// + /// Configures operation and approval timeouts. + /// + public static IResourceBuilder WithTimeouts( + this IResourceBuilder builder, + TimeSpan operation, + TimeSpan approval) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(operation, TimeSpan.Zero); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(approval, TimeSpan.Zero); + + builder.Resource.OperationTimeout = operation; + builder.Resource.ApprovalTimeout = approval; + return builder; + } + + private static void EnsureFusionPipeline(IDistributedApplicationBuilder builder) + { + if (builder.Resources.OfType().Any()) + { + return; + } + + var pipeline = builder.AddResource( + new FusionPipelineResource("fusion-nitro-pipeline")); + FusionPipeline.Configure(pipeline); + } + + private static string NormalizeCloudUrl(string cloudUrl) + { + if (!Uri.TryCreate(cloudUrl, UriKind.Absolute, out var uri) + || uri.Scheme is not "https") + { + throw new ArgumentException( + "The Nitro cloud URL must be an absolute HTTPS URL.", + nameof(cloudUrl)); + } + + return uri.AbsoluteUri.TrimEnd('/'); + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md new file mode 100644 index 00000000000..a357409c4a8 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md @@ -0,0 +1,29 @@ +# ChilliCream.Nitro.Aspire + +Publishes Hot Chocolate Fusion configurations through the .NET Aspire deployment pipeline. + +```csharp +var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); +var releaseId = builder.AddParameter("releaseId"); + +var nitro = builder.AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products-fusion") + .WithApiKey(nitroApiKey); + +nitro.AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag(releaseId) + .WithDefaultSourceVersionFromGitCommit() + .WithApproval(waitForApproval: true) + .WithForce(false) + .WithTimeouts( + operation: TimeSpan.FromMinutes(15), + approval: TimeSpan.FromHours(2)); +``` + +`aspire publish` creates portable Fusion artifacts and has no Nitro side effects. Use +`aspire do fusion-upload` to reconcile source versions and `aspire do fusion-publish` to run the +full readiness, composition, and publication graph. A matching `aspire deploy` also requires +`fusion-publish` to finish successfully. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md new file mode 100644 index 00000000000..769454ae558 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md @@ -0,0 +1,386 @@ +# Fusion and .NET Aspire publishing and deployment + +## Decision + +`aspire deploy` is the canonical Fusion release command. When an AppHost explicitly declares a Fusion deployment for the selected Aspire environment, every matching `aspire deploy` must export, reconcile source-schema uploads, compose, and publish the Fusion configuration before the well-known Deploy step can complete. + +`aspire publish` remains artifact-only for Fusion. It produces portable schema inputs, templates, bindings, and provenance, but it does not call Nitro, resolve a Nitro credential, or mutate a Nitro stage. Named `aspire do` steps remain useful for inspection, repair, and controlled reconciliation, but they are supplementary and must not provide a weaker route around compute/readiness dependencies. + +The resulting invariant is: + +```text +Fusion deployment declared for environment E + + +aspire deploy --environment E + = +Nitro stage reaches terminal success, or the Aspire deployment does not succeed +``` + +This is an explicit opt-in at AppHost design time, not an implicit inference. An AppHost without a matching Fusion deployment has no Nitro side effect. + +## Aspire version and API maturity + +At the time of research, the latest stable Aspire release is **13.4.6**, released June 20, 2026. See [Aspire 13.4.6](https://github.com/microsoft/aspire/releases/tag/v13.4.6). Aspire 13.4 made `aspire publish` and `aspire deploy` generally available. See [what's new in Aspire 13.4](https://aspire.dev/whats-new/aspire-13-4/). + +The programmatic pipeline APIs used to register and order custom steps remain experimental and emit `ASPIREPIPELINES001`. `aspire do` is the command for running a selected step and its dependencies, but that does not make every API behind custom step construction GA. Keep the experimental integration behind a narrow adapter and explicitly accept the diagnostic in that package. Sources: [deployment pipelines](https://aspire.dev/deployment/pipelines/), [ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/), [`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), [`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and [`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/). + +This repository currently pins `Aspire.Hosting.AppHost`, `Aspire.Hosting`, and `Aspire.Hosting.PostgreSQL` to **13.1.2** in [`src/Directory.Packages.props`](../../../../Directory.Packages.props). The required step-discovery and pipeline configuration concepts exist in that pin. Implement and test against 13.1.2 first, then separately test and upgrade to the current stable line. + +## Command contract + +| Entry point | Fusion behavior | Remote effect | +| --- | --- | --- | +| `aspire publish` | Evaluate/build as Aspire requires, then emit Fusion SDL, settings templates, endpoint bindings, archive inputs, and provenance beneath the output path. | No Nitro API call, Nitro credential resolution, upload, slot request, or stage mutation. Aspire itself may still create/update local deployment configuration, verify certificates, build, or perform other non-Nitro work. | +| `aspire deploy --environment Production` | Run the provider deployment and every Fusion deployment explicitly mapped to `Production`. It succeeds only after each mapped Nitro publication reaches terminal success. | Mandatory Nitro reconciliation for each matching declaration. | +| `aspire do fusion-upload` | Produce portable Fusion inputs and reconcile immutable source versions. | Upload only. It is not equivalent to deployment completion. | +| `aspire do fusion-publish` | Run the same compute discovery, readiness, upload, composition, and publication dependency graph as the matching deploy. | Safe supplementary reconciliation. It can redeploy or recheck prerequisites as dictated by its graph. | +| `aspire destroy` | Destroy provider resources according to the deployment target. | Never infer deletion of shared Nitro schema history or stage configurations. Nitro retention needs a separate explicit design. | + +`aspire deploy` does not consume an arbitrary previously produced `aspire publish` directory as a promoted release. It evaluates the AppHost and executes pipeline dependencies for that invocation. Artifact promotion requires an explicit artifact-path/apply step or an external release tool. + +## Environment and stage selection + +The Fusion deployment declaration must map an Aspire environment to exactly one intended Nitro stage. Do not infer stage from branch, tag, provider, resource name, or `ASPNETCORE_ENVIRONMENT`. Do not deploy staging and production from the same invocation merely because both are declared. + +```csharp +var nitro = builder.AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products-fusion") + .WithApiKey(builder.AddParameter("nitroApiKey", secret: true)); + +nitro.AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag(builder.AddParameter("releaseId")) + .WithDefaultSourceVersionFromGitCommit() + .WithApproval(waitForApproval: true) + .WithForce(false) + .WithTimeouts( + operation: TimeSpan.FromMinutes(15), + approval: TimeSpan.FromHours(2)); +``` + +Adding this resource opts Fusion publication into every `aspire deploy --environment Production`. A separate `.ForEnvironment("Staging").ToStage("staging")` declaration runs only when Staging is selected. Graph construction must fail when multiple Fusion deployments ambiguously claim the same environment/API/stage or when the selected environment has inconsistent mappings. + +The sketch is illustrative. Match the public API to Aspire 13.1.2 resource idioms and keep required values required. + +## Existing `HotChocolate.Fusion.Aspire` state + +The repository already has a source-schema and composition graph. Adapt it rather than creating a second Nitro-only graph. + +* [`GraphQLResourceBuilderExtensions.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs) exposes `WithGraphQLSchemaFile`, `WithGraphQLSchemaEndpoint`, and `WithGraphQLSchemaComposition`. +* [`SchemaComposition.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs) discovers referenced resources, creates internal `SourceSchemaInfo` records, and subscribes to `AfterResourcesCreatedEvent` through internal annotations and private discovery helpers. +* Native endpoint acquisition uses `/graphql/schema.graphql`. `/graphql` is only for an explicitly identified Apollo Federation `_service.sdl` flow. +* File mode can default its asserted name from the Aspire resource, while upload derives the authoritative name from `schema-settings.json`. The manifest must validate that the declared/manifest name exactly matches settings `name`. +* Current runtime discovery is suitable for `aspire run`, but normal DCP application orchestration is disabled in publish mode in the inspected Aspire source. Publish cannot assume resource endpoints are running. + +Refactor these declarations into a reusable internal model while preserving composition relationships between the gateway/composition resource and its referenced sources. The deploy pipeline then augments those declarations with provider step discovery, deployment endpoint bindings, and readiness evidence. + +## Can `aspire publish` use Hot Chocolate command-line schema export? + +Yes, as an explicit short-lived child process. Aspire publish mode does not start normal AppHost/DCP resources, but the custom publish pipeline action can start the referenced GraphQL project itself to run `HotChocolate.AspNetCore.CommandLine`. + +This export is not a pure or side-effect-free metadata operation. The child process executes the application's `Program`, configuration loading, service registration, `Build`, and endpoint mapping. Resolving the request executor performs full Hot Chocolate schema initialization, including type modules, schema hooks, warmup tasks, and any user code reached by those paths. Kestrel and ordinary `IHostedService` instances do not start because command mode invokes the CLI and skips `host.Run`/`host.RunAsync`. See [`WebApplicationExtensions.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/WebApplicationExtensions.cs) and [`ExportCommand.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/Command/ExportCommand.cs). + +The correct registration symbol is `ExportSchemaOnStartup`, not `ExportSchemaFileOnStartup`. It registers a schema executor warmup task, so it can export again while the CLI export command initializes the executor. See [`HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs). Projects used by the integration should either avoid the startup exporter in this mode or tolerate and isolate both writes. + +The application entry point must return the command exit code: + +```csharp +return await app.RunWithGraphQLCommandsAsync(args); +``` + +Use an argument-safe process invocation equivalent to: + +```text +dotnet run --project /absolute/path/Products.Subgraph.csproj --configuration Release --no-launch-profile -- schema export --output /isolated/products/schema.graphqls --schema-name Products +``` + +`schema` must be the first application argument after `--`, otherwise the application follows its normal server path. Always select the schema name explicitly. `ExportCommand` can print `No schemas registered.` and return successfully without creating output, so exit code zero is insufficient. The integration must validate the expected schema and settings artifacts. + +V1 should support checked-in/generated schema files and an explicit per-resource export command. Automatic `dotnet run` inference is opt-in because application startup and schema initialization can have arbitrary user side effects. Longer term, export from the exact published assembly or container that will be deployed, and bind schema provenance to its build/image digest. + +### Child-process requirements + +* Resolve a project with `ProjectResource.GetProjectMetadata().ProjectPath`. Reject resource types without a supported file/export declaration rather than guessing. +* Precreate an isolated output directory per source resource and invocation. Never share the application working directory or accept a stale file as fallback. +* Use `System.Diagnostics.Process` with `ProcessStartInfo.ArgumentList`, not shell concatenation. `WithProcessCommand` is a dashboard/resource command API, not the custom deployment pipeline runner. +* Use an environment allowlist. Do not pass Nitro credentials or unrelated AppHost secrets to the child. +* Record configuration, target framework, RID, working directory, launch-profile policy, project/assembly identity, and content/image digest in provenance. `--no-launch-profile` is the deterministic default. +* Enforce timeout and cancellation, and kill the entire process tree on termination. Capture bounded stdout/stderr and redact sensitive values. +* Validate freshness, non-empty SDL, parseable settings, exact `schema-settings.json` name, extensions, and the explicitly selected schema name. +* Treat a generated settings URL that resolves to localhost/loopback as a template risk. Reject it for final deployment and materialize the provider-resolved production URL only after readiness. +* Verify deterministic repeated exports for the same declared build inputs. Differences are a hard failure unless the differing provenance explains a new release input. + +Register the action with `WithPipelineStepFactory`. Use `WithPipelineConfiguration`, `PipelineConfigurationContext.GetSteps`, and `RequiredBy` to connect it to the release graph. There is no universal provider-neutral after-compute/readiness tag. Every supported provider needs an explicit step selector/readiness adapter. + +## Identity and configuration + +The recommended common path uses one immutable release ID as the configuration tag and as the default upload version for bare source names. The native model still keeps each source version explicit so an advanced rollout can select `products@version-a` and `inventory@version-b`. + +| Setting | Scope | Example | Policy | +| --- | --- | --- | --- | +| Cloud URL | Nitro target | `https://api.chillicream.com` | Explicit target/config value, then documented `NITRO_CLOUD_URL` compatibility fallback. HTTPS unless an explicit development exception exists. | +| API ID | Nitro target | `products-fusion` | Explicit identifier, not a display name. | +| API key | Credential | secret `ParameterResource` | Inject through CI/secret provider. Never store in artifacts or logs. | +| Aspire environment | Deployment selector | `Production` | Required explicit `.ForEnvironment(...)` mapping. | +| Stage | Nitro deployment | `production` | Required explicit mapping. Never inferred. | +| Release/configuration tag | Deployment invocation | `build-842-a1b2c3d4` | Unique for the rollout and stable across its retries. Inject once per CI release invocation. | +| Source version | Per source | `a1b2c3d4` or content hash | Immutable and derived from the exact deployed schema/build. Defaults to release tag only for bare-name compatibility. | +| Approval | Deployment | `true` | Explicit policy. Deployment succeeds only at terminal Nitro success. | +| Force | Deployment | `false` | Never infer and never default to true. | +| Timeouts | Deployment | operation and approval limits | Explicit and conservative. Timeout returns failed/indeterminate plus the recoverable request ID. | +| Stage ownership | Deployment | authoritative or additive | Explicit single-writer policy. | + +A Git commit alone is not always a rollout identity. Redeploying the same commit with different infrastructure, a dirty worktree, rebuilt image, changed endpoint binding, or altered settings can produce different desired state. CI should inject a unique release ID per release invocation and reuse it for retries of that same rollout. Do not derive the current release from stale Aspire deployment cache state. + +### Existing Nitro CLI input mapping + +| Existing input | Aspire-native input | Compatibility meaning | +| --- | --- | --- | +| `NITRO_API_ID` | `Nitro:ApiId` / `Parameters__nitroApiId` | API identity. | +| `NITRO_STAGE` | deployment `.ToStage(...)` / `Parameters__nitroStage` | Explicit environment-to-stage mapping. | +| `NITRO_TAG` | `configurationTag`, plus default source version for bare names | Publish uses it as configuration tag/default selected version; upload uses it as source version. Aspire splits the concepts internally. | +| `NITRO_API_KEY` | secret `nitroApiKey` / `Parameters__nitroApiKey` | Never write to artifacts/logs. Aspire deployment cache can persist resolved secrets in plaintext as described below. | +| `NITRO_CLOUD_URL` | `Nitro:CloudUrl` / `Parameters__nitroCloudUrl` | Compatibility override for the API base URL. | + +Prefer `ParameterResource` and Aspire configuration, including `Parameters__nitroApiKey` and `Parameters__releaseId`. Support `NITRO_*` only as a documented fallback with deterministic precedence. See [external parameters](https://aspire.dev/fundamentals/external-parameters/). + +Current source metadata must remain limited to the existing GitHub and Azure DevOps model. Richer provider/actor metadata is future work. Bind schema provenance to the exact build or container image digest deployed by the provider steps, not only to a mutable working directory. + +## Two-phase artifacts + +Some source-schema settings include deployment-resolved production URLs. A local publish cannot safely guess them. Artifact generation therefore has two phases. + +### Publish phase + +`aspire publish` emits immutable build inputs under the selected output path: + +```text +/fusion/production/ + nitro-deployment-template.json + sources/ + products/ + schema.graphqls + schema-settings.template.json + extensions/ + provenance.json + inventory/ + schema.graphqls + schema-settings.template.json + extensions/ + provenance.json +``` + +Its Fusion graph ends at artifacts: + +```text +source file or explicit child-process export + | + validate and record provenance + | + SDL/settings template/bindings + | + publish output complete +``` + +There is no Nitro credential resolution, upload, composition slot, commit, or stage mutation on this graph. + +Acquisition supports an existing SDL file and an explicit native export command such as: + +```text +dotnet run --project /absolute/path/Products.Subgraph.csproj --configuration Release --no-launch-profile -- schema export --output /isolated/products/schema.graphqls --schema-name Products +``` + +Endpoint acquisition is unavailable by default in publish mode. An externally managed endpoint can be used only when explicitly declared and authenticated. Apollo `/graphql` requires an explicit Apollo source kind. + +### Deploy materialization phase + +After provider deployment and actual readiness, resolve production endpoint bindings, materialize final `schema-settings.json`, and build the archive with the public `HotChocolate.Fusion.SourceSchema.Packaging.FusionSourceSchemaArchive`. Stage composition and commit must occur after service deployment and actual readiness. The final manifest records the release and provider provenance: + +```json +{ + "formatVersion": 1, + "cloudUrl": "https://api.chillicream.com", + "apiId": "products-fusion", + "environment": "Production", + "stage": "production", + "configurationTag": "build-842-a1b2c3d4", + "stageOwnership": "authoritative", + "sources": [ + { + "name": "products", + "sourceVersion": "a1b2c3d4", + "archive": "materialized/products-a1b2c3d4.zip", + "sha256": "...", + "imageDigest": "sha256:..." + } + ] +} +``` + +The deployment manifest and its validation are new work. There is no existing Nitro CLI manifest/SDL validator to reuse. Validate all paths, hashes, settings names, bindings, and provenance. Write the final manifest atomically after all archives materialize. + +## Mandatory deploy graph + +The required ordering is: + +```text +publish-time export/validation -----+ + | +provider source compute deploy | (may run in parallel) + | | +actual production readiness --------+ + | +final settings/archive materialization + | +reconcile source uploads + | +request and claim Nitro slot + | +re-download current stage FAR and selected archives + | +authoritative local composition + | +policy-aware remote validation and FAR commit + | +terminal publication or approval completion + | +WellKnownPipelineSteps.Deploy completes +``` + +Export can run in parallel with provider deployment. Readiness plus completed export feeds final settings/archive materialization, which precedes upload and stage composition. Upload may move earlier only when the final archive has no deployment-resolved input and its endpoint/provenance is already deployment-complete. If schema export itself requires deployment-only configuration, move that export after deployment/readiness or fail the resource as unsupported. Never claim a Nitro slot while waiting for compute deployment or readiness. + +The Nitro reconcile step must depend on the lowest provider deployment and readiness steps and be required by the well-known Deploy step. It must never depend on Deploy itself, because that creates a cycle. + +`RequiredBySteps = [WellKnownPipelineSteps.Deploy]` guarantees that reconciliation is included before Deploy completes. It does not order sibling prerequisites relative to one another. Register export with `WithPipelineStepFactory`. In `WithPipelineConfiguration`, discover concrete provider steps through `PipelineConfigurationContext.GetSteps(resource, WellKnownPipelineTags.DeployCompute)` where the provider supports it, then create explicit dependencies with `RequiredBy` from compute to readiness, export/readiness to materialization, upload to composition, and Nitro terminal status to root Deploy. These APIs are present in the repository-pinned Aspire 13.1.2 surface. There is no universal after-compute/readiness tag, so a provider adapter or explicit selector is required. + +Provider-neutral readiness is not proven. A `DeployCompute` step completing may mean a resource was submitted, not that its production endpoint accepts traffic. Provider adapters must add a supported readiness/check step. Graph construction must fail if an Aspire-managed source lacks supported deploy-step discovery or production readiness. A source explicitly declared as external may participate only with an explicit readiness proof. + +If Nitro publication is intended as an application-wide finalizer, it must also depend on all unrelated release prerequisites whose failure should prevent cutover. Merely being a sibling prerequisite of Deploy does not provide that ordering. + +## Current Nitro reconciliation flow + +For each matching deploy, the reconciliation step executes even if the desired state is unchanged: + +1. Resolve the selected environment declaration, release ID, stage ownership, force/approval policy, and timeouts. +2. Run publish-time export/local validation in parallel with provider deployment where safe, then complete actual provider readiness. If export needs deployment-only configuration, run it after readiness or fail unsupported. +3. After both export and readiness, resolve final production bindings, materialize source archives, and verify settings name/provenance/image digest. +4. Reconcile each `name@sourceVersion`. Download an existing exact version and compare normalized schema, settings, and extensions. Identical is a verified no-op. Different content under the same identity is a hard collision. Upload only missing versions. +5. Acquire stage serialization for `(cloudUrl, apiId, stage)`. Reconcile any persisted/server-backed request state before requesting a new slot. +6. Request and claim the slot only after compute readiness. Re-download the latest stage FAR and every selected source archive after the slot is acquired so composition uses authoritative current inputs. +7. Compose locally. Apply the selected stage ownership policy, preserving or removing undeclared schemas as defined below, and produce the desired FAR. +8. Compare the desired FAR/intent with an existing publication for the same configuration tag. Identical is terminal success/no-op. Different intent for the same tag is a collision. +9. When approval is disabled, validate the FAR before commit and apply the explicit force policy. When approval is enabled, commit starts processing and validation/approval states are observed through the subscription. +10. Commit and subscribe until terminal success or failure. Approval-gated deployment succeeds only after terminal Nitro success. +11. Complete the Nitro reconciliation prerequisite, allowing `WellKnownPipelineSteps.Deploy` to complete. + +On timeout, cancellation, or lost response, return failed/indeterminate and report the recoverable request ID. Release a claimed slot only when lifecycle-safe. An approval request can outlive a CI process, so a new invocation must resume/reconcile it rather than blindly create another publication. + +## Idempotency, serialization, and approvals + +Every-deploy reconciliation makes idempotency mandatory: + +* Same source name/version plus normalized-identical content is success/no-op. Same identity plus different content is a collision. +* Same release/configuration tag plus identical desired FAR and selected source intent is success/no-op. Same tag plus different intent is a collision. +* Persist `requestId`, phase, desired FAR digest, release ID, and source selection. Local cache alone is insufficient because CI runners are ephemeral and responses can be lost. Require server-backed lookup/idempotency or an equivalent durable release record before claiming robust retry behavior. +* Serialize publishers by `(cloudUrl, apiId, stage)`. A stale approval must never supersede a newer rollout. Enforce stage queue/order on the server or fail when it cannot be proven. +* Retry only bounded, known-transient transport/server failures with exponential backoff and jitter. Never retry authorization, invalid schema/settings, stage/API not found, force-policy rejection, or identity collisions. + +Aspire stores deployment state under `~/.aspire/deployments`, including resolved parameter values and secrets in plaintext. Never share, publish, or commit this cache. Inject credentials and the release ID from CI, and use clear-cache behavior when state is stale or compromised. The cache cannot be the authority for Nitro idempotency. See [deployment state caching](https://aspire.dev/deployment/deployment-state-caching/). + +## Stage ownership and partial state + +Choose and document one stage ownership policy: + +| Policy | Behavior | Constraint | +| --- | --- | --- | +| Authoritative declared set | The resulting FAR contains exactly the AppHost-declared source set for this deployment. Undeclared schemas are removed. | Recommended for a single release owner. Requires complete declarations. | +| Additive preserve-undeclared | Replace declared sources and preserve undeclared sources from the latest stage FAR. | Required for shared ownership, but prone to drift. Still requires one serialized writer/coordinator per stage. | + +There is no transaction across the service deployment provider, artifact uploads, and Nitro stage publication. Uploads can remain orphaned. Compute can be updated while the old FAR remains active if Nitro validation, approval, or commit fails. Automatic rollback across the compute provider and Nitro is unsafe without a provider-specific, tested rollback protocol. + +Source-service changes must remain backward-compatible with the old FAR during this window. Operators need a documented recovery path using the persisted request ID, release manifest, image digests, and previous FAR identity. The deployment result must report partial state precisely instead of claiming rollback. + +## Named `aspire do` operations + +Use target-specific names and actual CLI options: + +```bash +aspire publish --output-path ./artifacts/aspire --environment Production --non-interactive +aspire do fusion-upload --output-path ./artifacts/aspire --environment Production --non-interactive +aspire do fusion-publish --output-path ./artifacts/aspire --environment Production --non-interactive +aspire do fusion-publish --list-steps +``` + +`aspire do` evaluates and builds the AppHost unless `--no-build` is used, and it reruns the selected step's dependencies. Deployment commands default to Production. Inspect scope with `--list-steps`. The safe reconcile command carries the same provider compute/readiness dependencies as Deploy, so it may redeploy or recheck resources. + +`aspire publish` remains the artifact inspection path, and upload alone does not publish a release. `fusion-publish` requires the supported compute and readiness graph before it claims a slot. + +## Packaging and implementation plan + +`ChilliCream.Nitro.Client` is explicitly non-packable in [`ChilliCream.Nitro.Client.csproj`](ChilliCream.Nitro.Client.csproj). Extract/move the supported client and workflow into a packable assembly or deliberately co-ship supported assemblies. A public `ChilliCream.Nitro.Aspire` package must not depend on an internal non-packable project. + +| Area | Work | +| --- | --- | +| Existing Fusion Aspire | Adapt its source annotations and composition relationships into a reusable declaration model. | +| Artifact layer | Reuse `FusionSourceSchemaArchive`; add template, binding, provenance, manifest, normalization, and validation code. | +| Nitro workflow | Extract current local FAR composition and `IFusionConfigurationClient` orchestration behind a packable supported API. | +| Aspire pipeline | Add environment-filtered resources, provider adapters, readiness proof, exact step dependencies, Deploy completion prerequisite, and `do` commands. | +| State/recovery | Add server-backed request/tag lookup or durable release-state service, stage serialization, resume, collision checks, and terminal status reporting. | + +### Release-critical phases + +1. Prove pipeline wiring on Aspire 13.1.2: environment filtering, `GetSteps(...DeployCompute)`, exact dependencies, `RequiredBySteps`, `do --list-steps`, and cycle detection. +2. Refactor existing Fusion Aspire declarations and implement two-phase artifacts with provenance bound to deployed build/image digest. +3. Implement provider adapter one, including production endpoint readiness. Fail unsupported graphs closed. +4. Implement mandatory upload reconciliation and normalized duplicate policy. +5. Implement stage serialization, request resume/idempotency, authoritative re-download/local FAR composition, validation/commit, approvals, and terminal reporting. +6. Integrate reconciliation as a prerequisite of WellKnown Deploy for every environment-matched declaration. +7. Exercise partial-state and recovery behavior end to end, then upgrade/test against Aspire 13.4.6. + +## Tests and acceptance criteria + +| Layer | Required evidence | +| --- | --- | +| Environment selection | Production deploy runs only Production Fusion deployment; Staging runs only Staging; ambiguous mappings fail graph construction. | +| Publish | Produces templates/provenance and has no Nitro calls, credential resolution, upload, slot, or stage mutation. | +| Export lifecycle | Sentinels prove `Program`, configuration, service registration, endpoint mapping, schema hooks/modules, and executor warmups run. Sentinels also prove Kestrel and normal `IHostedService` instances do not start in command mode. | +| Export command | Uses argv-safe `ArgumentList`, explicit schema name, isolated/precreated output, bounded redacted output, timeout/cancellation/tree kill, and no Nitro secrets in the environment. | +| Export validation | Multi-schema selection is exact; zero exit with no registered schema/no output fails; localhost settings fail final materialization; stale output never falls back; repeated identical input exports deterministically. | +| Export provenance | Configuration, TFM, RID, working directory, launch-profile policy, project/assembly and exact build/image digest match the deployed resource. Unsupported resource types fail closed. | +| Pipeline topology | Export may parallel compute; export plus actual readiness precede final materialization/upload; upload/readiness precede slot; Nitro terminal success precedes Deploy completion. Sibling prerequisites cannot race and no step depends on Deploy. | +| Provider readiness | Supported provider proves a real production endpoint, and unsupported Aspire-managed sources fail graph construction. External source requires explicit proof. | +| Materialization | Production bindings and exact image/build digest produce the final archive; settings name equals declared name. | +| Upload reconciliation | Exact normalized source identity is no-op; mismatch is collision; missing source uploads once under bounded retries. | +| Composition | Acquires slot after readiness, re-downloads latest FAR and sources, applies declared stage ownership, composes locally, and validates/commits the FAR. | +| Publication identity | Same release tag/identical intent is no-op; same tag/different intent fails; new release is serialized after existing stage work. | +| Resume/approval | Lost response or ephemeral runner resumes by request ID/server record. Approval timeout is failed/indeterminate, and Deploy cannot succeed before terminal Nitro success. Stale approval cannot supersede a later rollout. | +| Partial state | Nitro failure after compute update reports new compute plus old FAR, leaves recoverable state, and does not claim automatic rollback. | +| `aspire do` | Safe reconcile includes compute/readiness dependencies. Export/upload inspection cannot masquerade as completed deployment. | +| Compatibility | Focused suite passes on repository pin 13.1.2 and on 13.4.6 before support is advertised. | + +Done means every matching `aspire deploy` executes Fusion reconciliation, can verified-no-op on identical desired state, and cannot complete until Nitro reaches terminal success. It also means the pipeline refuses to construct when exact compute/readiness ordering cannot be proven. + +## Remaining questions + +1. Which provider is the first supported adapter, and what concrete step proves its production endpoint ready rather than merely submitted? +2. Which Nitro server lookup/idempotency API can recover request ID and phase from `(api, stage, release tag, desired digest)` on a new CI runner? +3. Can the server reject or serialize stale approvals so an older request can never become current after a newer rollout? +4. Which fields form normalized source archive equality, and can packaging APIs expose them without duplicate parsing? +5. Is authoritative stage ownership acceptable for the first integration, or is additive multi-owner composition required? +6. Which unrelated application prerequisites must Nitro cutover wait for when it acts as the global finalizer? +7. How should a same-commit rebuild derive source versions and release IDs when schema content, endpoint bindings, or image digests differ? +8. Which resources can safely opt into automatic child-process export, and which initialization hooks need explicit isolation guidance? + +## Sources + +* [What's new in Aspire 13.4](https://aspire.dev/whats-new/aspire-13-4/) +* [Aspire 13.4.6 release](https://github.com/microsoft/aspire/releases/tag/v13.4.6) +* [Deploy with Aspire](https://aspire.dev/deployment/deploy-with-aspire/) +* [Aspire deployment pipelines](https://aspire.dev/deployment/pipelines/) +* [`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), [`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and [`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/) +* [Aspire external parameters](https://aspire.dev/fundamentals/external-parameters/) +* [ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/) +* [Aspire deployment state caching](https://aspire.dev/deployment/deployment-state-caching/) +* Local code: [`GraphQLResourceBuilderExtensions.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs), [`SchemaComposition.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs), [`WebApplicationExtensions.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/WebApplicationExtensions.cs), [`ExportCommand.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/Command/ExportCommand.cs), [`HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs), [`FusionConfiguration/IFusionConfigurationClient.cs`](FusionConfiguration/IFusionConfigurationClient.cs), [`FusionConfiguration/FusionConfigurationClient.cs`](FusionConfiguration/FusionConfigurationClient.cs), [`ChilliCream.Nitro.Client.csproj`](ChilliCream.Nitro.Client.csproj), and [`src/Directory.Packages.props`](../../../../Directory.Packages.props). diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json new file mode 100644 index 00000000000..87f7b94b3a8 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json @@ -0,0 +1,25 @@ +{ + "name": "ChilliCream.Nitro.Fusion", + "schema": "../ChilliCream.Nitro.Client/schema.graphql", + "documents": "Operations/*.graphql", + "extensions": { + "strawberryShake": { + "name": "FusionApiClient", + "namespace": "ChilliCream.Nitro.Fusion.Transport", + "accessModifier": "internal", + "emitGeneratedCode": true, + "noStore": true, + "records": { + "inputs": true, + "entities": true + }, + "transportProfiles": [ + { + "name": "Default", + "default": "HTTP", + "subscription": "HTTP" + } + ] + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj new file mode 100644 index 00000000000..1a6d03773e7 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj @@ -0,0 +1,33 @@ + + + + ChilliCream.Nitro.Fusion + ChilliCream.Nitro.Fusion + net11.0;net10.0;net9.0 + false + Provides deterministic Nitro upload and publication workflows for Hot Chocolate Fusion. + README.md + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs new file mode 100644 index 00000000000..a5ebf1301a5 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs @@ -0,0 +1,140 @@ +using System.Text; +using System.Text.Json; +using HotChocolate.Fusion.SourceSchema.Packaging; +using HotChocolate.Language; + +namespace ChilliCream.Nitro.Fusion; + +internal sealed record FusionArchiveContent( + string Schema, + string? SchemaExtensions, + string Settings) +{ + public static async Task ReadAsync( + string archivePath, + string expectedName, + CancellationToken cancellationToken) + { + await using var stream = File.OpenRead(archivePath); + return await ReadAsync(stream, expectedName, cancellationToken); + } + + public static async Task ReadAsync( + Stream stream, + string expectedName, + CancellationToken cancellationToken) + { + try + { + using var archive = FusionSourceSchemaArchive.Open( + stream, + leaveOpen: true); + var schema = await archive.TryGetSchemaAsync(cancellationToken) + ?? throw new FusionDeploymentException( + "The Fusion source schema archive contains no schema."); + var extensions = + await archive.TryGetSchemaExtensionsAsync(cancellationToken); + using var settings = await archive.TryGetSettingsAsync(cancellationToken) + ?? throw new FusionDeploymentException( + "The Fusion source schema archive contains no settings."); + + if (!settings.RootElement.TryGetProperty("name", out var name) + || name.ValueKind is not JsonValueKind.String + || !string.Equals( + name.GetString(), + expectedName, + StringComparison.Ordinal)) + { + throw new FusionDeploymentException( + "The source schema settings name must exactly match " + + $"'{expectedName}'."); + } + + return new FusionArchiveContent( + NormalizeGraphQL(schema), + extensions is null + ? null + : NormalizeGraphQL(extensions.Value), + NormalizeJson(settings.RootElement)); + } + catch (FusionDeploymentException) + { + throw; + } + catch (Exception exception) when ( + exception is InvalidDataException + or JsonException + or SyntaxException) + { + throw new FusionDeploymentException( + "The Fusion source schema archive is invalid.", + exception); + } + } + + private static string NormalizeGraphQL(ReadOnlyMemory source) + => Utf8GraphQLParser.Parse(source.Span).ToString(); + + private static string NormalizeJson(JsonElement value) + { + using var stream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(stream)) + { + WriteCanonicalJson(writer, value); + } + + return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); + } + + private static void WriteCanonicalJson( + Utf8JsonWriter writer, + JsonElement value) + { + switch (value.ValueKind) + { + case JsonValueKind.Object: + writer.WriteStartObject(); + foreach (var property in value.EnumerateObject() + .OrderBy(property => property.Name, StringComparer.Ordinal)) + { + writer.WritePropertyName(property.Name); + WriteCanonicalJson(writer, property.Value); + } + writer.WriteEndObject(); + break; + + case JsonValueKind.Array: + writer.WriteStartArray(); + foreach (var item in value.EnumerateArray()) + { + WriteCanonicalJson(writer, item); + } + writer.WriteEndArray(); + break; + + case JsonValueKind.String: + writer.WriteStringValue(value.GetString()); + break; + + case JsonValueKind.Number: + writer.WriteRawValue(value.GetRawText()); + break; + + case JsonValueKind.True: + writer.WriteBooleanValue(true); + break; + + case JsonValueKind.False: + writer.WriteBooleanValue(false); + break; + + case JsonValueKind.Null: + writer.WriteNullValue(); + break; + + default: + throw new JsonException( + $"Unsupported JSON token kind '{value.ValueKind}'."); + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs new file mode 100644 index 00000000000..73ab3035a91 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs @@ -0,0 +1,17 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// The base exception for failures reported by the Fusion deployment workflow. +/// +public class FusionDeploymentException : Exception +{ + public FusionDeploymentException(string message) + : base(message) + { + } + + public FusionDeploymentException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs new file mode 100644 index 00000000000..d595f5c818f --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs @@ -0,0 +1,593 @@ +using System.Security.Cryptography; +using ChilliCream.Nitro.Fusion.Transport; + +namespace ChilliCream.Nitro.Fusion; + +internal sealed class FusionDeploymentWorkflow( + IFusionDeploymentTransportFactory transportFactory) + : IFusionDeploymentWorkflow +{ + public async Task ReconcileSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaUpload source, + CancellationToken cancellationToken) + { + ValidateTarget(target); + ValidateSource(source); + + var localContent = await FusionArchiveContent.ReadAsync( + source.ArchivePath, + source.Name, + cancellationToken); + await VerifySha256Async(source, cancellationToken); + + await using var transport = await transportFactory.OpenAsync( + target, + cancellationToken); + var remoteArchive = await transport.DownloadSourceSchemaAsync( + source.Name, + source.Version, + cancellationToken); + + if (remoteArchive is not null) + { + await EnsureContentMatchesAsync( + source, + localContent, + remoteArchive, + cancellationToken); + return; + } + + FusionRemoteCommandResult uploadResult; + try + { + uploadResult = await transport.UploadSourceSchemaAsync( + source.Version, + source.ArchivePath, + cancellationToken); + } + catch (OperationCanceledException) when ( + cancellationToken.IsCancellationRequested) + { + throw; + } + catch (Exception exception) + { + await ReconcileAfterUncertainUploadAsync( + transport, + source, + localContent, + exception, + cancellationToken); + return; + } + + if (uploadResult.Succeeded) + { + return; + } + + if (uploadResult.IsDuplicate) + { + var racedArchive = await transport.DownloadSourceSchemaAsync( + source.Name, + source.Version, + cancellationToken); + if (racedArchive is null) + { + throw new FusionIndeterminateStateException( + $"Nitro reported source schema '{source.Name}' version " + + $"'{source.Version}' as duplicated, but the version " + + "could not be read back."); + } + + await EnsureContentMatchesAsync( + source, + localContent, + racedArchive, + cancellationToken); + return; + } + + throw CreateRemoteFailure( + "Nitro rejected the Fusion source schema upload.", + uploadResult.Errors); + } + + public async Task PublishAsync( + FusionPublicationRequest request, + string fusionArchivePath, + CancellationToken cancellationToken) + { + ValidatePublication(request, fusionArchivePath); + + using var timeout = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + timeout.CancelAfter(request.OperationTimeout); + var operationToken = timeout.Token; + string? requestId = null; + + try + { + await using var transport = await transportFactory.OpenAsync( + request.Target, + operationToken); + FusionRemoteBeginResult begin; + + try + { + begin = await transport.BeginPublishAsync( + request, + operationToken); + } + catch (Exception exception) when ( + exception is not OperationCanceledException) + { + throw new FusionIndeterminateStateException( + "The Fusion publication request may have been created, " + + "but Nitro did not return a verifiable result.", + exception); + } + + if (begin.Errors.Count > 0) + { + throw CreateRemoteFailure( + "Nitro rejected the Fusion publication request.", + begin.Errors); + } + + requestId = begin.RequestId; + if (string.IsNullOrWhiteSpace(requestId)) + { + throw new FusionIndeterminateStateException( + "Nitro accepted the Fusion publication request without " + + "returning a request identifier."); + } + + await using var events = transport.WatchPublishAsync( + requestId, + operationToken) + .GetAsyncEnumerator(operationToken); + + var readyResult = await WaitForReadyAsync( + events, + requestId, + operationToken); + if (readyResult is PublishWaitResult.Succeeded) + { + return; + } + + await EnsureRemoteCommandSucceededAsync( + () => transport.ClaimPublishAsync(requestId, operationToken), + "claim the Fusion publication slot", + requestId); + + if (!request.WaitForApproval) + { + await EnsureRemoteCommandSucceededAsync( + () => transport.ValidatePublishAsync( + requestId, + fusionArchivePath, + operationToken), + "validate the Fusion configuration", + requestId); + + var validation = await WaitForValidationAsync( + events, + requestId, + operationToken); + if (validation is { Succeeded: false } && !request.Force) + { + await ReleaseKnownFailedValidationAsync( + transport, + requestId, + operationToken); + throw CreateRemoteFailure( + "Nitro rejected the Fusion configuration validation.", + validation.Errors); + } + } + + await EnsureRemoteCommandSucceededAsync( + () => transport.CommitPublishAsync( + requestId, + fusionArchivePath, + operationToken), + "commit the Fusion configuration", + requestId); + + await WaitForTerminalPublishAsync( + events, + requestId, + timeout, + request.ApprovalTimeout, + operationToken); + } + catch (OperationCanceledException) when ( + !cancellationToken.IsCancellationRequested) + { + throw new FusionIndeterminateStateException( + "The Fusion publication timed out before a terminal result " + + "could be verified.", + requestId); + } + } + + private static async Task ReconcileAfterUncertainUploadAsync( + IFusionDeploymentTransport transport, + FusionSourceSchemaUpload source, + FusionArchiveContent localContent, + Exception uploadException, + CancellationToken cancellationToken) + { + byte[]? remoteArchive; + try + { + remoteArchive = await transport.DownloadSourceSchemaAsync( + source.Name, + source.Version, + cancellationToken); + } + catch (Exception readException) + { + throw new FusionIndeterminateStateException( + $"The upload of source schema '{source.Name}' version " + + $"'{source.Version}' may have succeeded and could not be " + + "verified.", + new AggregateException(uploadException, readException)); + } + + if (remoteArchive is null) + { + throw new FusionIndeterminateStateException( + $"The upload of source schema '{source.Name}' version " + + $"'{source.Version}' returned an uncertain result and the " + + "version was not available for verification.", + uploadException); + } + + await EnsureContentMatchesAsync( + source, + localContent, + remoteArchive, + cancellationToken); + } + + private static async Task EnsureContentMatchesAsync( + FusionSourceSchemaUpload source, + FusionArchiveContent localContent, + byte[] remoteArchive, + CancellationToken cancellationToken) + { + await using var stream = new MemoryStream(remoteArchive, writable: false); + var remoteContent = await FusionArchiveContent.ReadAsync( + stream, + source.Name, + cancellationToken); + + if (localContent != remoteContent) + { + throw new FusionIdentityCollisionException( + $"Source schema '{source.Name}' version '{source.Version}' " + + "already exists with different normalized schema, settings, " + + "or extensions."); + } + } + + private static async Task VerifySha256Async( + FusionSourceSchemaUpload source, + CancellationToken cancellationToken) + { + if (source.Sha256.Length is not 64 + || !source.Sha256.All(Uri.IsHexDigit)) + { + throw new FusionDeploymentException( + "The source schema SHA-256 must contain exactly 64 " + + "hexadecimal characters."); + } + + await using var stream = File.OpenRead(source.ArchivePath); + var actual = Convert.ToHexString( + await SHA256.HashDataAsync(stream, cancellationToken)); + if (!string.Equals( + actual, + source.Sha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new FusionDeploymentException( + $"The SHA-256 for source schema '{source.Name}' version " + + $"'{source.Version}' does not match its archive."); + } + } + + private static async Task WaitForReadyAsync( + IAsyncEnumerator events, + string requestId, + CancellationToken cancellationToken) + { + while (await MoveNextAsync(events, requestId, cancellationToken)) + { + switch (events.Current.Kind) + { + case FusionRemoteEventKind.Ready: + return PublishWaitResult.Ready; + case FusionRemoteEventKind.PublishingSucceeded: + return PublishWaitResult.Succeeded; + case FusionRemoteEventKind.PublishingFailed: + throw CreateRemoteFailure( + "The Fusion publication failed.", + events.Current.Errors); + case FusionRemoteEventKind.Queued: + continue; + default: + throw new FusionIndeterminateStateException( + "The Fusion publication request was already in an " + + $"ambiguous '{events.Current.Kind}' state.", + requestId); + } + } + + throw CreateEndedEarly(requestId); + } + + private static async Task WaitForValidationAsync( + IAsyncEnumerator events, + string requestId, + CancellationToken cancellationToken) + { + while (await MoveNextAsync(events, requestId, cancellationToken)) + { + switch (events.Current.Kind) + { + case FusionRemoteEventKind.ValidationSucceeded: + return new FusionValidationResult(true, []); + case FusionRemoteEventKind.ValidationFailed: + return new FusionValidationResult( + false, + events.Current.Errors); + case FusionRemoteEventKind.InProgress: + continue; + case FusionRemoteEventKind.PublishingFailed: + throw CreateRemoteFailure( + "The Fusion publication failed during validation.", + events.Current.Errors); + default: + throw new FusionIndeterminateStateException( + "Nitro reported an unexpected Fusion validation state " + + $"'{events.Current.Kind}'.", + requestId); + } + } + + throw CreateEndedEarly(requestId); + } + + private static async Task WaitForTerminalPublishAsync( + IAsyncEnumerator events, + string requestId, + CancellationTokenSource timeout, + TimeSpan approvalTimeout, + CancellationToken cancellationToken) + { + while (await MoveNextAsync(events, requestId, cancellationToken)) + { + switch (events.Current.Kind) + { + case FusionRemoteEventKind.PublishingSucceeded: + return; + case FusionRemoteEventKind.PublishingFailed: + throw CreateRemoteFailure( + "The Fusion publication failed.", + events.Current.Errors); + case FusionRemoteEventKind.WaitingForApproval: + timeout.CancelAfter(approvalTimeout); + continue; + case FusionRemoteEventKind.Approved: + case FusionRemoteEventKind.InProgress: + case FusionRemoteEventKind.ValidationSucceeded: + continue; + default: + throw new FusionIndeterminateStateException( + "Nitro reported an unexpected Fusion publishing state " + + $"'{events.Current.Kind}'.", + requestId); + } + } + + throw CreateEndedEarly(requestId); + } + + private static async Task MoveNextAsync( + IAsyncEnumerator events, + string requestId, + CancellationToken cancellationToken) + { + try + { + var hasNext = await events.MoveNextAsync(); + cancellationToken.ThrowIfCancellationRequested(); + return hasNext; + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exception) + { + throw new FusionIndeterminateStateException( + "The Fusion publication event stream ended with an error.", + exception, + requestId); + } + } + + private static async Task EnsureRemoteCommandSucceededAsync( + Func> command, + string operation, + string requestId) + { + FusionRemoteCommandResult result; + try + { + result = await command(); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exception) + { + throw new FusionIndeterminateStateException( + $"Nitro may have completed the operation to {operation}, but " + + "its result could not be verified.", + exception, + requestId); + } + + if (!result.Succeeded) + { + throw new FusionIndeterminateStateException( + $"Nitro could not {operation}: " + + FormatErrors(result.Errors), + requestId); + } + } + + private static async Task ReleaseKnownFailedValidationAsync( + IFusionDeploymentTransport transport, + string requestId, + CancellationToken cancellationToken) + { + var result = await transport.ReleasePublishAsync( + requestId, + cancellationToken); + if (!result.Succeeded) + { + throw new FusionIndeterminateStateException( + "The failed Fusion validation was known, but its publication " + + "slot could not be released: " + + FormatErrors(result.Errors), + requestId); + } + } + + private static FusionIndeterminateStateException CreateEndedEarly( + string requestId) + => new( + "The Fusion publication event stream ended before Nitro reported " + + "a terminal result.", + requestId); + + private static FusionDeploymentException CreateRemoteFailure( + string message, + IReadOnlyList errors) + => new($"{message} {FormatErrors(errors)}"); + + private static string FormatErrors(IReadOnlyList errors) + => errors.Count is 0 + ? "Nitro returned no error details." + : string.Join(" ", errors); + + private static void ValidateTarget(FusionTarget target) + { + ArgumentNullException.ThrowIfNull(target); + if (!target.CloudUrl.IsAbsoluteUri) + { + throw new ArgumentException( + "The Nitro cloud URL must be absolute.", + nameof(target)); + } + + ArgumentException.ThrowIfNullOrWhiteSpace(target.ApiId); + ArgumentException.ThrowIfNullOrWhiteSpace(target.ApiKey); + } + + private static void ValidateSource(FusionSourceSchemaUpload source) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Name); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Version); + ArgumentException.ThrowIfNullOrWhiteSpace(source.ArchivePath); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Sha256); + if (!File.Exists(source.ArchivePath)) + { + throw new FileNotFoundException( + "The Fusion source schema archive does not exist.", + source.ArchivePath); + } + } + + private static void ValidatePublication( + FusionPublicationRequest request, + string fusionArchivePath) + { + ArgumentNullException.ThrowIfNull(request); + ValidateTarget(request.Target); + ArgumentException.ThrowIfNullOrWhiteSpace(request.Stage); + ArgumentException.ThrowIfNullOrWhiteSpace(request.ConfigurationTag); + ArgumentException.ThrowIfNullOrWhiteSpace(fusionArchivePath); + ArgumentNullException.ThrowIfNull(request.SourceSchemas); + + if (request.SourceSchemas.Count is 0) + { + throw new ArgumentException( + "At least one source schema version must be selected.", + nameof(request)); + } + + foreach (var source in request.SourceSchemas) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Name); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Version); + } + + if (request.SourceSchemas + .GroupBy(source => source.Name, StringComparer.Ordinal) + .Any(group => group.Count() > 1)) + { + throw new ArgumentException( + "A source schema may only be selected once.", + nameof(request)); + } + + if (request.OperationTimeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException( + nameof(request), + "The operation timeout must be greater than zero."); + } + + if (request.ApprovalTimeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException( + nameof(request), + "The approval timeout must be greater than zero."); + } + + if (!File.Exists(fusionArchivePath)) + { + throw new FileNotFoundException( + "The Fusion configuration archive does not exist.", + fusionArchivePath); + } + + if (new FileInfo(fusionArchivePath).Length is 0) + { + throw new FusionDeploymentException( + "The Fusion configuration archive is empty."); + } + } + + private sealed record FusionValidationResult( + bool Succeeded, + IReadOnlyList Errors); + + private enum PublishWaitResult + { + Ready, + Succeeded + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs new file mode 100644 index 00000000000..ab12e8eb981 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs @@ -0,0 +1,12 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// A source schema name and version already exist with different content. +/// +public sealed class FusionIdentityCollisionException : FusionDeploymentException +{ + public FusionIdentityCollisionException(string message) + : base(message) + { + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs new file mode 100644 index 00000000000..fff49777d30 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs @@ -0,0 +1,27 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// A remote operation may have changed state, but its result could not be verified. +/// +public sealed class FusionIndeterminateStateException : FusionDeploymentException +{ + public FusionIndeterminateStateException(string message, string? requestId = null) + : base(message) + { + RequestId = requestId; + } + + public FusionIndeterminateStateException( + string message, + Exception innerException, + string? requestId = null) + : base(message, innerException) + { + RequestId = requestId; + } + + /// + /// Gets the remote publication request identifier when one is known. + /// + public string? RequestId { get; } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs new file mode 100644 index 00000000000..f0eb321f6c3 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs @@ -0,0 +1,14 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Describes a Fusion configuration publication. +/// +public sealed record FusionPublicationRequest( + FusionTarget Target, + string Stage, + string ConfigurationTag, + IReadOnlyList SourceSchemas, + bool WaitForApproval, + bool Force, + TimeSpan OperationTimeout, + TimeSpan ApprovalTimeout); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs new file mode 100644 index 00000000000..a06cd096c9a --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs @@ -0,0 +1,10 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Describes an immutable Fusion source schema archive to reconcile. +/// +public sealed record FusionSourceSchemaUpload( + string Name, + string Version, + string ArchivePath, + string Sha256); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs new file mode 100644 index 00000000000..2f3c8ebf331 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs @@ -0,0 +1,6 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Selects one immutable source schema version for a Fusion publication. +/// +public sealed record FusionSourceSchemaVersion(string Name, string Version); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs new file mode 100644 index 00000000000..cddc1edc94b --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs @@ -0,0 +1,10 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Identifies a Nitro API and the credentials used to access it. +/// +public sealed record FusionTarget(Uri CloudUrl, string ApiId, string ApiKey) +{ + /// + public override string ToString() => $"{CloudUrl} (API {ApiId})"; +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs new file mode 100644 index 00000000000..1c881506ba9 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs @@ -0,0 +1,11092 @@ +// +#nullable enable annotations +#nullable disable warnings + +namespace ChilliCream.Nitro.Fusion.Transport +{ + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaResult : global::System.IEquatable, IUploadFusionSourceSchemaResult + { + public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph uploadFusionSubgraph) + { + UploadFusionSubgraph = uploadFusionSubgraph; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchemaResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (UploadFusionSubgraph.Equals(other.UploadFusionSubgraph)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchemaResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * UploadFusionSubgraph.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload + { + public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? fusionSubgraphVersion, global::System.Collections.Generic.IReadOnlyList? errors) + { + FusionSubgraphVersion = fusionSubgraphVersion; + Errors = errors; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (((FusionSubgraphVersion is null && other.FusionSubgraphVersion is null) || FusionSubgraphVersion != null && FusionSubgraphVersion.Equals(other.FusionSubgraphVersion))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (FusionSubgraphVersion != null) + { + hash ^= 397 * FusionSubgraphVersion.GetHashCode(); + } + + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion + { + public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(global::System.String id) + { + Id = id; + } + + public global::System.String Id { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Id.Equals(other.Id)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Id.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError + { + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchemaResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph + { + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : IUploadFusionSourceSchema_UploadFusionSubgraph + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion + { + public global::System.String Id { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IFusionError + { + public global::System.String Message { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentResult : global::System.IEquatable, IBeginFusionDeploymentResult + { + public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish beginFusionConfigurationPublish) + { + BeginFusionConfigurationPublish = beginFusionConfigurationPublish; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (BeginFusionConfigurationPublish.Equals(other.BeginFusionConfigurationPublish)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * BeginFusionConfigurationPublish.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(global::System.String? requestId, global::System.Collections.Generic.IReadOnlyList? errors) + { + RequestId = requestId; + Errors = errors; + } + + public global::System.String? RequestId { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (((RequestId is null && other.RequestId is null) || RequestId != null && RequestId.Equals(other.RequestId))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (RequestId != null) + { + hash ^= 397 * RequestId.GetHashCode(); + } + + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError + { + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish + { + public global::System.String? RequestId { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : IBeginFusionDeployment_BeginFusionConfigurationPublish + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentResult : global::System.IEquatable, IClaimFusionDeploymentResult + { + public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition startFusionConfigurationComposition) + { + StartFusionConfigurationComposition = startFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } + + public virtual global::System.Boolean Equals(ClaimFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (StartFusionConfigurationComposition.Equals(other.StartFusionConfigurationComposition)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClaimFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * StartFusionConfigurationComposition.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload + { + public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + { + Errors = errors; + } + + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation + { + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + { + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + { + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : IClaimFusionDeployment_StartFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentResult : global::System.IEquatable, IReleaseFusionDeploymentResult + { + public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition cancelFusionConfigurationComposition) + { + CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } + + public virtual global::System.Boolean Equals(ReleaseFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (CancelFusionConfigurationComposition.Equals(other.CancelFusionConfigurationComposition)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ReleaseFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * CancelFusionConfigurationComposition.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload + { + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + { + Errors = errors; + } + + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation + { + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + { + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + { + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : IReleaseFusionDeployment_CancelFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentResult : global::System.IEquatable, IValidateFusionDeploymentResult + { + public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition validateFusionConfigurationComposition) + { + ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } + + public virtual global::System.Boolean Equals(ValidateFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (ValidateFusionConfigurationComposition.Equals(other.ValidateFusionConfigurationComposition)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ValidateFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * ValidateFusionConfigurationComposition.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload + { + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + { + Errors = errors; + } + + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation + { + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + { + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + { + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : IValidateFusionDeployment_ValidateFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentResult : global::System.IEquatable, ICommitFusionDeploymentResult + { + public CommitFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish commitFusionConfigurationPublish) + { + CommitFusionConfigurationPublish = commitFusionConfigurationPublish; + } + + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } + + public virtual global::System.Boolean Equals(CommitFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (CommitFusionConfigurationPublish.Equals(other.CommitFusionConfigurationPublish)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CommitFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * CommitFusionConfigurationPublish.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload + { + public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(global::System.Collections.Generic.IReadOnlyList? errors) + { + Errors = errors; + } + + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation + { + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError + { + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError + { + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : ICommitFusionDeployment_CommitFusionConfigurationPublish + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentResult : global::System.IEquatable, IWatchFusionDeploymentResult + { + public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged onFusionConfigurationPublishingTaskChanged) + { + OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; + } + + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeploymentResult? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (OnFusionConfigurationPublishingTaskChanged.Equals(other.OnFusionConfigurationPublishingTaskChanged)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeploymentResult)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * OnFusionConfigurationPublishingTaskChanged.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) + { + this.__typename = __typename; + State = state; + Errors = errors; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) + { + this.__typename = __typename; + State = state; + Errors = errors; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Int32 queuePosition) + { + this.__typename = __typename; + State = state; + QueuePosition = queuePosition; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Int32 QueuePosition { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::System.Object.Equals(QueuePosition, other.QueuePosition); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + hash ^= 397 * QueuePosition.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } + + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 + { + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(global::System.String message) + { + Message = message; + } + + public global::System.String Message { get; } + + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Message.Equals(other.Message)); + } + + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1)obj); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Int32 QueuePosition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + public global::System.String Message { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + public global::System.String Message { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSubgraphInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _sourceMetadataInputFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + public global::System.String TypeName => "UploadFusionSubgraphInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); + _sourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("SourceMetadataInput"); + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsApiIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("apiId", FormatApiId(input.ApiId))); + } + + if (inputInfo.IsArchiveSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("archive", FormatArchive(input.Archive))); + } + + if (inputInfo.IsSourceSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("source", FormatSource(input.Source))); + } + + if (inputInfo.IsTagSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); + } + + return fields; + } + + private global::System.Object? FormatApiId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + + private global::System.Object? FormatArchive(global::StrawberryShake.Upload input) + { + return _uploadFormatter.Format(input); + } + + private global::System.Object? FormatSource(global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? input) + { + if (input is null) + { + return input; + } + else + { + return _sourceMetadataInputFormatter.Format(input); + } + } + + private global::System.Object? FormatTag(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record UploadFusionSubgraphInput : global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo + { + public virtual global::System.Boolean Equals(UploadFusionSubgraphInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (ApiId.Equals(other.ApiId)) && Archive.Equals(other.Archive) && ((Source is null && other.Source is null) || Source != null && Source.Equals(other.Source)) && Tag.Equals(other.Tag); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * ApiId.GetHashCode(); + hash ^= 397 * Archive.GetHashCode(); + if (Source != null) + { + hash ^= 397 * Source.GetHashCode(); + } + + hash ^= 397 * Tag.GetHashCode(); + return hash; + } + } + + private global::System.String _value_apiId = default !; + private global::System.Boolean _set_apiId; + private global::StrawberryShake.Upload _value_archive; + private global::System.Boolean _set_archive; + private global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? _value_source; + private global::System.Boolean _set_source; + private global::System.String _value_tag = default !; + private global::System.Boolean _set_tag; + public global::System.String ApiId + { + get => _value_apiId; + init + { + _set_apiId = true; + _value_apiId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsApiIdSet => _set_apiId; + + public global::StrawberryShake.Upload Archive + { + get => _value_archive; + init + { + _set_archive = true; + _value_archive = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsArchiveSet => _set_archive; + + public global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? Source + { + get => _value_source; + init + { + _set_source = true; + _value_source = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsSourceSet => _set_source; + + public global::System.String Tag + { + get => _value_tag; + init + { + _set_tag = true; + _value_tag = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsTagSet => _set_tag; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class SourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _azureDevOpsSourceMetadataInputFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _gitHubSourceMetadataInputFormatter = default !; + public global::System.String TypeName => "SourceMetadataInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _azureDevOpsSourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("AzureDevOpsSourceMetadataInput"); + _gitHubSourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("GitHubSourceMetadataInput"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsAzureDevOpsSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("azureDevOps", FormatAzureDevOps(input.AzureDevOps))); + } + + if (inputInfo.IsGithubSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("github", FormatGithub(input.Github))); + } + + return fields; + } + + private global::System.Object? FormatAzureDevOps(global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? input) + { + if (input is null) + { + return input; + } + else + { + return _azureDevOpsSourceMetadataInputFormatter.Format(input); + } + } + + private global::System.Object? FormatGithub(global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? input) + { + if (input is null) + { + return input; + } + else + { + return _gitHubSourceMetadataInputFormatter.Format(input); + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record SourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo + { + public virtual global::System.Boolean Equals(SourceMetadataInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (((AzureDevOps is null && other.AzureDevOps is null) || AzureDevOps != null && AzureDevOps.Equals(other.AzureDevOps))) && ((Github is null && other.Github is null) || Github != null && Github.Equals(other.Github)); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (AzureDevOps != null) + { + hash ^= 397 * AzureDevOps.GetHashCode(); + } + + if (Github != null) + { + hash ^= 397 * Github.GetHashCode(); + } + + return hash; + } + } + + private global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? _value_azureDevOps; + private global::System.Boolean _set_azureDevOps; + private global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? _value_github; + private global::System.Boolean _set_github; + public global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? AzureDevOps + { + get => _value_azureDevOps; + init + { + _set_azureDevOps = true; + _value_azureDevOps = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo.IsAzureDevOpsSet => _set_azureDevOps; + + public global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? Github + { + get => _value_github; + init + { + _set_github = true; + _value_github = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo.IsGithubSet => _set_github; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class AzureDevOpsSourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _azureDevOpsActorInputFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _uRLFormatter = default !; + public global::System.String TypeName => "AzureDevOpsSourceMetadataInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _azureDevOpsActorInputFormatter = serializerResolver.GetInputValueFormatter("AzureDevOpsActorInput"); + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + _uRLFormatter = serializerResolver.GetInputValueFormatter("URL"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsActorSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("actor", FormatActor(input.Actor))); + } + + if (inputInfo.IsCommitHashSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("commitHash", FormatCommitHash(input.CommitHash))); + } + + if (inputInfo.IsJobIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("jobId", FormatJobId(input.JobId))); + } + + if (inputInfo.IsPipelineNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("pipelineName", FormatPipelineName(input.PipelineName))); + } + + if (inputInfo.IsProjectUrlSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("projectUrl", FormatProjectUrl(input.ProjectUrl))); + } + + if (inputInfo.IsRepositoryUrlSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("repositoryUrl", FormatRepositoryUrl(input.RepositoryUrl))); + } + + if (inputInfo.IsRunIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("runId", FormatRunId(input.RunId))); + } + + if (inputInfo.IsRunNumberSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("runNumber", FormatRunNumber(input.RunNumber))); + } + + if (inputInfo.IsTaskIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("taskId", FormatTaskId(input.TaskId))); + } + + return fields; + } + + private global::System.Object? FormatActor(global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _azureDevOpsActorInputFormatter.Format(input); + } + + private global::System.Object? FormatCommitHash(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + + private global::System.Object? FormatJobId(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + + private global::System.Object? FormatPipelineName(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatProjectUrl(global::System.Uri input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _uRLFormatter.Format(input); + } + + private global::System.Object? FormatRepositoryUrl(global::System.Uri? input) + { + if (input is null) + { + return input; + } + else + { + return _uRLFormatter.Format(input); + } + } + + private global::System.Object? FormatRunId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatRunNumber(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatTaskId(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record AzureDevOpsSourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo + { + public virtual global::System.Boolean Equals(AzureDevOpsSourceMetadataInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Actor.Equals(other.Actor)) && ((CommitHash is null && other.CommitHash is null) || CommitHash != null && CommitHash.Equals(other.CommitHash)) && ((JobId is null && other.JobId is null) || JobId != null && JobId.Equals(other.JobId)) && PipelineName.Equals(other.PipelineName) && ProjectUrl.Equals(other.ProjectUrl) && ((RepositoryUrl is null && other.RepositoryUrl is null) || RepositoryUrl != null && RepositoryUrl.Equals(other.RepositoryUrl)) && RunId.Equals(other.RunId) && RunNumber.Equals(other.RunNumber) && ((TaskId is null && other.TaskId is null) || TaskId != null && TaskId.Equals(other.TaskId)); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Actor.GetHashCode(); + if (CommitHash != null) + { + hash ^= 397 * CommitHash.GetHashCode(); + } + + if (JobId != null) + { + hash ^= 397 * JobId.GetHashCode(); + } + + hash ^= 397 * PipelineName.GetHashCode(); + hash ^= 397 * ProjectUrl.GetHashCode(); + if (RepositoryUrl != null) + { + hash ^= 397 * RepositoryUrl.GetHashCode(); + } + + hash ^= 397 * RunId.GetHashCode(); + hash ^= 397 * RunNumber.GetHashCode(); + if (TaskId != null) + { + hash ^= 397 * TaskId.GetHashCode(); + } + + return hash; + } + } + + private global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput _value_actor = default !; + private global::System.Boolean _set_actor; + private global::System.String? _value_commitHash; + private global::System.Boolean _set_commitHash; + private global::System.String? _value_jobId; + private global::System.Boolean _set_jobId; + private global::System.String _value_pipelineName = default !; + private global::System.Boolean _set_pipelineName; + private global::System.Uri _value_projectUrl = default !; + private global::System.Boolean _set_projectUrl; + private global::System.Uri? _value_repositoryUrl; + private global::System.Boolean _set_repositoryUrl; + private global::System.String _value_runId = default !; + private global::System.Boolean _set_runId; + private global::System.String _value_runNumber = default !; + private global::System.Boolean _set_runNumber; + private global::System.String? _value_taskId; + private global::System.Boolean _set_taskId; + public global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput Actor + { + get => _value_actor; + init + { + _set_actor = true; + _value_actor = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsActorSet => _set_actor; + + public global::System.String? CommitHash + { + get => _value_commitHash; + init + { + _set_commitHash = true; + _value_commitHash = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsCommitHashSet => _set_commitHash; + + public global::System.String? JobId + { + get => _value_jobId; + init + { + _set_jobId = true; + _value_jobId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsJobIdSet => _set_jobId; + + public global::System.String PipelineName + { + get => _value_pipelineName; + init + { + _set_pipelineName = true; + _value_pipelineName = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsPipelineNameSet => _set_pipelineName; + + public global::System.Uri ProjectUrl + { + get => _value_projectUrl; + init + { + _set_projectUrl = true; + _value_projectUrl = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsProjectUrlSet => _set_projectUrl; + + public global::System.Uri? RepositoryUrl + { + get => _value_repositoryUrl; + init + { + _set_repositoryUrl = true; + _value_repositoryUrl = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRepositoryUrlSet => _set_repositoryUrl; + + public global::System.String RunId + { + get => _value_runId; + init + { + _set_runId = true; + _value_runId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRunIdSet => _set_runId; + + public global::System.String RunNumber + { + get => _value_runNumber; + init + { + _set_runNumber = true; + _value_runNumber = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRunNumberSet => _set_runNumber; + + public global::System.String? TaskId + { + get => _value_taskId; + init + { + _set_taskId = true; + _value_taskId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsTaskIdSet => _set_taskId; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class AzureDevOpsActorInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + public global::System.String TypeName => "AzureDevOpsActorInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsEmailSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("email", FormatEmail(input.Email))); + } + + if (inputInfo.IsNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("name", FormatName(input.Name))); + } + + return fields; + } + + private global::System.Object? FormatEmail(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + + private global::System.Object? FormatName(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record AzureDevOpsActorInput : global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo + { + public virtual global::System.Boolean Equals(AzureDevOpsActorInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (((Email is null && other.Email is null) || Email != null && Email.Equals(other.Email))) && Name.Equals(other.Name); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + if (Email != null) + { + hash ^= 397 * Email.GetHashCode(); + } + + hash ^= 397 * Name.GetHashCode(); + return hash; + } + } + + private global::System.String? _value_email; + private global::System.Boolean _set_email; + private global::System.String _value_name = default !; + private global::System.Boolean _set_name; + public global::System.String? Email + { + get => _value_email; + init + { + _set_email = true; + _value_email = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo.IsEmailSet => _set_email; + + public global::System.String Name + { + get => _value_name; + init + { + _set_name = true; + _value_name = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo.IsNameSet => _set_name; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class GitHubSourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _uRLFormatter = default !; + public global::System.String TypeName => "GitHubSourceMetadataInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + _uRLFormatter = serializerResolver.GetInputValueFormatter("URL"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsActorSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("actor", FormatActor(input.Actor))); + } + + if (inputInfo.IsCommitHashSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("commitHash", FormatCommitHash(input.CommitHash))); + } + + if (inputInfo.IsJobIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("jobId", FormatJobId(input.JobId))); + } + + if (inputInfo.IsRepositoryUrlSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("repositoryUrl", FormatRepositoryUrl(input.RepositoryUrl))); + } + + if (inputInfo.IsRunIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("runId", FormatRunId(input.RunId))); + } + + if (inputInfo.IsRunNumberSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("runNumber", FormatRunNumber(input.RunNumber))); + } + + if (inputInfo.IsWorkflowNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("workflowName", FormatWorkflowName(input.WorkflowName))); + } + + return fields; + } + + private global::System.Object? FormatActor(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatCommitHash(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatJobId(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + + private global::System.Object? FormatRepositoryUrl(global::System.Uri input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _uRLFormatter.Format(input); + } + + private global::System.Object? FormatRunId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatRunNumber(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatWorkflowName(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record GitHubSourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo + { + public virtual global::System.Boolean Equals(GitHubSourceMetadataInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Actor.Equals(other.Actor)) && CommitHash.Equals(other.CommitHash) && ((JobId is null && other.JobId is null) || JobId != null && JobId.Equals(other.JobId)) && RepositoryUrl.Equals(other.RepositoryUrl) && RunId.Equals(other.RunId) && RunNumber.Equals(other.RunNumber) && WorkflowName.Equals(other.WorkflowName); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Actor.GetHashCode(); + hash ^= 397 * CommitHash.GetHashCode(); + if (JobId != null) + { + hash ^= 397 * JobId.GetHashCode(); + } + + hash ^= 397 * RepositoryUrl.GetHashCode(); + hash ^= 397 * RunId.GetHashCode(); + hash ^= 397 * RunNumber.GetHashCode(); + hash ^= 397 * WorkflowName.GetHashCode(); + return hash; + } + } + + private global::System.String _value_actor = default !; + private global::System.Boolean _set_actor; + private global::System.String _value_commitHash = default !; + private global::System.Boolean _set_commitHash; + private global::System.String? _value_jobId; + private global::System.Boolean _set_jobId; + private global::System.Uri _value_repositoryUrl = default !; + private global::System.Boolean _set_repositoryUrl; + private global::System.String _value_runId = default !; + private global::System.Boolean _set_runId; + private global::System.String _value_runNumber = default !; + private global::System.Boolean _set_runNumber; + private global::System.String _value_workflowName = default !; + private global::System.Boolean _set_workflowName; + public global::System.String Actor + { + get => _value_actor; + init + { + _set_actor = true; + _value_actor = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsActorSet => _set_actor; + + public global::System.String CommitHash + { + get => _value_commitHash; + init + { + _set_commitHash = true; + _value_commitHash = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsCommitHashSet => _set_commitHash; + + public global::System.String? JobId + { + get => _value_jobId; + init + { + _set_jobId = true; + _value_jobId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsJobIdSet => _set_jobId; + + public global::System.Uri RepositoryUrl + { + get => _value_repositoryUrl; + init + { + _set_repositoryUrl = true; + _value_repositoryUrl = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRepositoryUrlSet => _set_repositoryUrl; + + public global::System.String RunId + { + get => _value_runId; + init + { + _set_runId = true; + _value_runId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRunIdSet => _set_runId; + + public global::System.String RunNumber + { + get => _value_runNumber; + init + { + _set_runNumber = true; + _value_runNumber = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRunNumberSet => _set_runNumber; + + public global::System.String WorkflowName + { + get => _value_workflowName; + init + { + _set_workflowName = true; + _value_workflowName = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsWorkflowNameSet => _set_workflowName; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionConfigurationPublishInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _sourceMetadataInputFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _fusionSubgraphVersionInputFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _booleanFormatter = default !; + public global::System.String TypeName => "BeginFusionConfigurationPublishInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + _sourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("SourceMetadataInput"); + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + _fusionSubgraphVersionInputFormatter = serializerResolver.GetInputValueFormatter("FusionSubgraphVersionInput"); + _booleanFormatter = serializerResolver.GetInputValueFormatter("Boolean"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsApiIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("apiId", FormatApiId(input.ApiId))); + } + + if (inputInfo.IsSourceSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("source", FormatSource(input.Source))); + } + + if (inputInfo.IsStageNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("stageName", FormatStageName(input.StageName))); + } + + if (inputInfo.IsSubgraphApiIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphApiId", FormatSubgraphApiId(input.SubgraphApiId))); + } + + if (inputInfo.IsSubgraphNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphName", FormatSubgraphName(input.SubgraphName))); + } + + if (inputInfo.IsSubgraphsSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphs", FormatSubgraphs(input.Subgraphs))); + } + + if (inputInfo.IsTagSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); + } + + if (inputInfo.IsWaitForApprovalSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("waitForApproval", FormatWaitForApproval(input.WaitForApproval))); + } + + return fields; + } + + private global::System.Object? FormatApiId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + + private global::System.Object? FormatSource(global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? input) + { + if (input is null) + { + return input; + } + else + { + return _sourceMetadataInputFormatter.Format(input); + } + } + + private global::System.Object? FormatStageName(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatSubgraphApiId(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _iDFormatter.Format(input); + } + } + + private global::System.Object? FormatSubgraphName(global::System.String? input) + { + if (input is null) + { + return input; + } + else + { + return _stringFormatter.Format(input); + } + } + + private global::System.Object? FormatSubgraphs(global::System.Collections.Generic.IReadOnlyList? input) + { + if (input is null) + { + return input; + } + else + { + var input_list = new global::System.Collections.Generic.List(); + foreach (var input_elm in input) + { + if (input_elm is null) + { + throw new global::System.ArgumentNullException(nameof(input_elm)); + } + + input_list.Add(_fusionSubgraphVersionInputFormatter.Format(input_elm)); + } + + return input_list; + } + } + + private global::System.Object? FormatTag(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatWaitForApproval(global::System.Boolean? input) + { + if (input is null) + { + return input; + } + else + { + return _booleanFormatter.Format(input); + } + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record BeginFusionConfigurationPublishInput : global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo + { + public virtual global::System.Boolean Equals(BeginFusionConfigurationPublishInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (ApiId.Equals(other.ApiId)) && ((Source is null && other.Source is null) || Source != null && Source.Equals(other.Source)) && StageName.Equals(other.StageName) && ((SubgraphApiId is null && other.SubgraphApiId is null) || SubgraphApiId != null && SubgraphApiId.Equals(other.SubgraphApiId)) && ((SubgraphName is null && other.SubgraphName is null) || SubgraphName != null && SubgraphName.Equals(other.SubgraphName)) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Subgraphs, other.Subgraphs) && Tag.Equals(other.Tag) && global::System.Object.Equals(WaitForApproval, other.WaitForApproval); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * ApiId.GetHashCode(); + if (Source != null) + { + hash ^= 397 * Source.GetHashCode(); + } + + hash ^= 397 * StageName.GetHashCode(); + if (SubgraphApiId != null) + { + hash ^= 397 * SubgraphApiId.GetHashCode(); + } + + if (SubgraphName != null) + { + hash ^= 397 * SubgraphName.GetHashCode(); + } + + if (Subgraphs != null) + { + foreach (var Subgraphs_elm in Subgraphs) + { + hash ^= 397 * Subgraphs_elm.GetHashCode(); + } + } + + hash ^= 397 * Tag.GetHashCode(); + if (WaitForApproval != null) + { + hash ^= 397 * WaitForApproval.GetHashCode(); + } + + return hash; + } + } + + private global::System.String _value_apiId = default !; + private global::System.Boolean _set_apiId; + private global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? _value_source; + private global::System.Boolean _set_source; + private global::System.String _value_stageName = default !; + private global::System.Boolean _set_stageName; + private global::System.String? _value_subgraphApiId; + private global::System.Boolean _set_subgraphApiId; + private global::System.String? _value_subgraphName; + private global::System.Boolean _set_subgraphName; + private global::System.Collections.Generic.IReadOnlyList? _value_subgraphs; + private global::System.Boolean _set_subgraphs; + private global::System.String _value_tag = default !; + private global::System.Boolean _set_tag; + private global::System.Boolean? _value_waitForApproval; + private global::System.Boolean _set_waitForApproval; + public global::System.String ApiId + { + get => _value_apiId; + init + { + _set_apiId = true; + _value_apiId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsApiIdSet => _set_apiId; + + public global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? Source + { + get => _value_source; + init + { + _set_source = true; + _value_source = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSourceSet => _set_source; + + public global::System.String StageName + { + get => _value_stageName; + init + { + _set_stageName = true; + _value_stageName = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsStageNameSet => _set_stageName; + + public global::System.String? SubgraphApiId + { + get => _value_subgraphApiId; + init + { + _set_subgraphApiId = true; + _value_subgraphApiId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphApiIdSet => _set_subgraphApiId; + + public global::System.String? SubgraphName + { + get => _value_subgraphName; + init + { + _set_subgraphName = true; + _value_subgraphName = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphNameSet => _set_subgraphName; + + public global::System.Collections.Generic.IReadOnlyList? Subgraphs + { + get => _value_subgraphs; + init + { + _set_subgraphs = true; + _value_subgraphs = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphsSet => _set_subgraphs; + + public global::System.String Tag + { + get => _value_tag; + init + { + _set_tag = true; + _value_tag = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsTagSet => _set_tag; + + public global::System.Boolean? WaitForApproval + { + get => _value_waitForApproval; + init + { + _set_waitForApproval = true; + _value_waitForApproval = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsWaitForApprovalSet => _set_waitForApproval; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class FusionSubgraphVersionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; + public global::System.String TypeName => "FusionSubgraphVersionInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _stringFormatter = serializerResolver.GetInputValueFormatter("String"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.FusionSubgraphVersionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsNameSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("name", FormatName(input.Name))); + } + + if (inputInfo.IsTagSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); + } + + return fields; + } + + private global::System.Object? FormatName(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + + private global::System.Object? FormatTag(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _stringFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionSubgraphVersionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo + { + public virtual global::System.Boolean Equals(FusionSubgraphVersionInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Name.Equals(other.Name)) && Tag.Equals(other.Tag); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Name.GetHashCode(); + hash ^= 397 * Tag.GetHashCode(); + return hash; + } + } + + private global::System.String _value_name = default !; + private global::System.Boolean _set_name; + private global::System.String _value_tag = default !; + private global::System.Boolean _set_tag; + public global::System.String Name + { + get => _value_name; + init + { + _set_name = true; + _value_name = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo.IsNameSet => _set_name; + + public global::System.String Tag + { + get => _value_tag; + init + { + _set_tag = true; + _value_tag = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo.IsTagSet => _set_tag; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class StartFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + public global::System.String TypeName => "StartFusionConfigurationCompositionInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsRequestIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); + } + + return fields; + } + + private global::System.Object? FormatRequestId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record StartFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo + { + public virtual global::System.Boolean Equals(StartFusionConfigurationCompositionInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (RequestId.Equals(other.RequestId)); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * RequestId.GetHashCode(); + return hash; + } + } + + private global::System.String _value_requestId = default !; + private global::System.Boolean _set_requestId; + public global::System.String RequestId + { + get => _value_requestId; + init + { + _set_requestId = true; + _value_requestId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CancelFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + public global::System.String TypeName => "CancelFusionConfigurationCompositionInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsRequestIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); + } + + return fields; + } + + private global::System.Object? FormatRequestId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record CancelFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo + { + public virtual global::System.Boolean Equals(CancelFusionConfigurationCompositionInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (RequestId.Equals(other.RequestId)); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * RequestId.GetHashCode(); + return hash; + } + } + + private global::System.String _value_requestId = default !; + private global::System.Boolean _set_requestId; + public global::System.String RequestId + { + get => _value_requestId; + init + { + _set_requestId = true; + _value_requestId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + public global::System.String TypeName => "ValidateFusionConfigurationCompositionInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsConfigurationSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("configuration", FormatConfiguration(input.Configuration))); + } + + if (inputInfo.IsRequestIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); + } + + return fields; + } + + private global::System.Object? FormatConfiguration(global::StrawberryShake.Upload input) + { + return _uploadFormatter.Format(input); + } + + private global::System.Object? FormatRequestId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ValidateFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo + { + public virtual global::System.Boolean Equals(ValidateFusionConfigurationCompositionInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Configuration.Equals(other.Configuration)) && RequestId.Equals(other.RequestId); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Configuration.GetHashCode(); + hash ^= 397 * RequestId.GetHashCode(); + return hash; + } + } + + private global::StrawberryShake.Upload _value_configuration; + private global::System.Boolean _set_configuration; + private global::System.String _value_requestId = default !; + private global::System.Boolean _set_requestId; + public global::StrawberryShake.Upload Configuration + { + get => _value_configuration; + init + { + _set_configuration = true; + _value_configuration = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo.IsConfigurationSet => _set_configuration; + + public global::System.String RequestId + { + get => _value_requestId; + init + { + _set_requestId = true; + _value_requestId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionConfigurationPublishInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + { + private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + public global::System.String TypeName => "CommitFusionConfigurationPublishInput"; + + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } + + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } + + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } + + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsConfigurationSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("configuration", FormatConfiguration(input.Configuration))); + } + + if (inputInfo.IsRequestIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); + } + + return fields; + } + + private global::System.Object? FormatConfiguration(global::StrawberryShake.Upload input) + { + return _uploadFormatter.Format(input); + } + + private global::System.Object? FormatRequestId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record CommitFusionConfigurationPublishInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo + { + public virtual global::System.Boolean Equals(CommitFusionConfigurationPublishInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (Configuration.Equals(other.Configuration)) && RequestId.Equals(other.RequestId); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * Configuration.GetHashCode(); + hash ^= 397 * RequestId.GetHashCode(); + return hash; + } + } + + private global::StrawberryShake.Upload _value_configuration; + private global::System.Boolean _set_configuration; + private global::System.String _value_requestId = default !; + private global::System.Boolean _set_requestId; + public global::StrawberryShake.Upload Configuration + { + get => _value_configuration; + init + { + _set_configuration = true; + _value_configuration = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo.IsConfigurationSet => _set_configuration; + + public global::System.String RequestId + { + get => _value_requestId; + init + { + _set_requestId = true; + _value_requestId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo.IsRequestIdSet => _set_requestId; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.EnumGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal enum ProcessingState + { + Approved, + Cancelled, + Failed, + Processing, + Queued, + Ready, + Success, + WaitingForApproval + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.EnumParserGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ProcessingStateSerializer : global::StrawberryShake.Serialization.IInputValueFormatter, global::StrawberryShake.Serialization.ILeafValueParser + { + public global::System.String TypeName => "ProcessingState"; + + public ProcessingState Parse(global::System.String serializedValue) + { + return serializedValue switch + { + "APPROVED" => ProcessingState.Approved, + "CANCELLED" => ProcessingState.Cancelled, + "FAILED" => ProcessingState.Failed, + "PROCESSING" => ProcessingState.Processing, + "QUEUED" => ProcessingState.Queued, + "READY" => ProcessingState.Ready, + "SUCCESS" => ProcessingState.Success, + "WAITING_FOR_APPROVAL" => ProcessingState.WaitingForApproval, + _ => throw new global::StrawberryShake.GraphQLClientException($"String value '{serializedValue}' can't be converted to enum ProcessingState")}; + } + + public global::System.Object Format(global::System.Object? runtimeValue) + { + return runtimeValue switch + { + ProcessingState.Approved => "APPROVED", + ProcessingState.Cancelled => "CANCELLED", + ProcessingState.Failed => "FAILED", + ProcessingState.Processing => "PROCESSING", + ProcessingState.Queued => "QUEUED", + ProcessingState.Ready => "READY", + ProcessingState.Success => "SUCCESS", + ProcessingState.WaitingForApproval => "WAITING_FOR_APPROVAL", + _ => throw new global::StrawberryShake.GraphQLClientException($"Enum ProcessingState value '{runtimeValue}' can't be converted to string")}; + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { + /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaMutationDocument : global::StrawberryShake.IDocument + { + private UploadFusionSourceSchemaMutationDocument() + { + } + + public static UploadFusionSourceSchemaMutationDocument Instance { get; } = new UploadFusionSourceSchemaMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { uploadFusionSubgraph(input: $input) { __typename fusionSubgraphVersion { __typename id } errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "00a2f0dbe78ac0ebc27723990e9d8a6e"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { + /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaMutation : global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _uploadFusionSubgraphInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _uploadFusionSubgraphInputFormatter = serializerResolver.GetInputValueFormatter("UploadFusionSubgraphInput"); + } + + private UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter uploadFusionSubgraphInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _uploadFusionSubgraphInputFormatter = uploadFusionSubgraphInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IUploadFusionSourceSchemaResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchemaMutation(_operationExecutor, _configure.Add(configure), _uploadFusionSubgraphInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + private void MapFilesFromTypeUploadFusionSubgraphInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) + { + var pathArchive = path + ".archive"; + var valueArchive = value.Archive; + files.Add(pathArchive, valueArchive is global::StrawberryShake.Upload u ? u : null); + } + + private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) + { + if (value is { } value_i) + { + MapFilesFromTypeUploadFusionSubgraphInput(path, value_i, files); + } + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + var files = new global::System.Collections.Generic.Dictionary(); + MapFilesFromArgumentInput("variables.input", input, files); + return CreateRequest(variables, files); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) + { + return new global::StrawberryShake.OperationRequest(id: UploadFusionSourceSchemaMutationDocument.Instance.Hash.Value, name: "UploadFusionSourceSchema", document: UploadFusionSourceSchemaMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _uploadFusionSubgraphInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { + /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchemaMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { + /// __typename + /// requestId + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private BeginFusionDeploymentMutationDocument() + { + } + + public static BeginFusionDeploymentMutationDocument Instance { get; } = new BeginFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { beginFusionConfigurationPublish(input: $input) { __typename requestId errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "820eda44da60af366f2c12a562552673"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { + /// __typename + /// requestId + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _beginFusionConfigurationPublishInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _beginFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("BeginFusionConfigurationPublishInput"); + } + + private BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter beginFusionConfigurationPublishInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _beginFusionConfigurationPublishInputFormatter = beginFusionConfigurationPublishInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IBeginFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _beginFusionConfigurationPublishInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + return CreateRequest(variables); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return new global::StrawberryShake.OperationRequest(id: BeginFusionDeploymentMutationDocument.Instance.Hash.Value, name: "BeginFusionDeployment", document: BeginFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _beginFusionConfigurationPublishInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { + /// __typename + /// requestId + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private ClaimFusionDeploymentMutationDocument() + { + } + + public static ClaimFusionDeploymentMutationDocument Instance { get; } = new ClaimFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { startFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "260243f143261b45d8ac102e9c5459bd"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _startFusionConfigurationCompositionInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _startFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("StartFusionConfigurationCompositionInput"); + } + + private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter startFusionConfigurationCompositionInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _startFusionConfigurationCompositionInputFormatter = startFusionConfigurationCompositionInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IClaimFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _startFusionConfigurationCompositionInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + return CreateRequest(variables); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return new global::StrawberryShake.OperationRequest(id: ClaimFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ClaimFusionDeployment", document: ClaimFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _startFusionConfigurationCompositionInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation + /// + /// mutation ReleaseFusionDeployment( + /// $input: CancelFusionConfigurationCompositionInput! + /// ) { + /// cancelFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private ReleaseFusionDeploymentMutationDocument() + { + } + + public static ReleaseFusionDeploymentMutationDocument Instance { get; } = new ReleaseFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { cancelFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "66f0c2afee4b4150360b31081c019f81"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation + /// + /// mutation ReleaseFusionDeployment( + /// $input: CancelFusionConfigurationCompositionInput! + /// ) { + /// cancelFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _cancelFusionConfigurationCompositionInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public ReleaseFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _cancelFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("CancelFusionConfigurationCompositionInput"); + } + + private ReleaseFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter cancelFusionConfigurationCompositionInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _cancelFusionConfigurationCompositionInputFormatter = cancelFusionConfigurationCompositionInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IReleaseFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _cancelFusionConfigurationCompositionInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + return CreateRequest(variables); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return new global::StrawberryShake.OperationRequest(id: ReleaseFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ReleaseFusionDeployment", document: ReleaseFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _cancelFusionConfigurationCompositionInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation + /// + /// mutation ReleaseFusionDeployment( + /// $input: CancelFusionConfigurationCompositionInput! + /// ) { + /// cancelFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the ValidateFusionDeployment GraphQL operation + /// + /// mutation ValidateFusionDeployment( + /// $input: ValidateFusionConfigurationCompositionInput! + /// ) { + /// validateFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private ValidateFusionDeploymentMutationDocument() + { + } + + public static ValidateFusionDeploymentMutationDocument Instance { get; } = new ValidateFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation ValidateFusionDeployment($input: ValidateFusionConfigurationCompositionInput!) { validateFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "f33496c099bf6caf49ab60806b98a9eb"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the ValidateFusionDeployment GraphQL operation + /// + /// mutation ValidateFusionDeployment( + /// $input: ValidateFusionConfigurationCompositionInput! + /// ) { + /// validateFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _validateFusionConfigurationCompositionInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public ValidateFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _validateFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("ValidateFusionConfigurationCompositionInput"); + } + + private ValidateFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter validateFusionConfigurationCompositionInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _validateFusionConfigurationCompositionInputFormatter = validateFusionConfigurationCompositionInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IValidateFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _validateFusionConfigurationCompositionInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + private void MapFilesFromTypeValidateFusionConfigurationCompositionInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value, global::System.Collections.Generic.Dictionary files) + { + var pathConfiguration = path + ".configuration"; + var valueConfiguration = value.Configuration; + files.Add(pathConfiguration, valueConfiguration is global::StrawberryShake.Upload u ? u : null); + } + + private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value, global::System.Collections.Generic.Dictionary files) + { + if (value is { } value_i) + { + MapFilesFromTypeValidateFusionConfigurationCompositionInput(path, value_i, files); + } + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + var files = new global::System.Collections.Generic.Dictionary(); + MapFilesFromArgumentInput("variables.input", input, files); + return CreateRequest(variables, files); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) + { + return new global::StrawberryShake.OperationRequest(id: ValidateFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ValidateFusionDeployment", document: ValidateFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _validateFusionConfigurationCompositionInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the ValidateFusionDeployment GraphQL operation + /// + /// mutation ValidateFusionDeployment( + /// $input: ValidateFusionConfigurationCompositionInput! + /// ) { + /// validateFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the CommitFusionDeployment GraphQL operation + /// + /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { + /// commitFusionConfigurationPublish(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private CommitFusionDeploymentMutationDocument() + { + } + + public static CommitFusionDeploymentMutationDocument Instance { get; } = new CommitFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { commitFusionConfigurationPublish(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "1f0d0749e4b952e52a1322169df539ac"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the CommitFusionDeployment GraphQL operation + /// + /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { + /// commitFusionConfigurationPublish(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _commitFusionConfigurationPublishInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public CommitFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _commitFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("CommitFusionConfigurationPublishInput"); + } + + private CommitFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter commitFusionConfigurationPublishInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _commitFusionConfigurationPublishInputFormatter = commitFusionConfigurationPublishInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(ICommitFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _commitFusionConfigurationPublishInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + private void MapFilesFromTypeCommitFusionConfigurationPublishInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value, global::System.Collections.Generic.Dictionary files) + { + var pathConfiguration = path + ".configuration"; + var valueConfiguration = value.Configuration; + files.Add(pathConfiguration, valueConfiguration is global::StrawberryShake.Upload u ? u : null); + } + + private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value, global::System.Collections.Generic.Dictionary files) + { + if (value is { } value_i) + { + MapFilesFromTypeCommitFusionConfigurationPublishInput(path, value_i, files); + } + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + var files = new global::System.Collections.Generic.Dictionary(); + MapFilesFromArgumentInput("variables.input", input, files); + return CreateRequest(variables, files); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) + { + return new global::StrawberryShake.OperationRequest(id: CommitFusionDeploymentMutationDocument.Instance.Hash.Value, name: "CommitFusionDeployment", document: CommitFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _commitFusionConfigurationPublishInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the CommitFusionDeployment GraphQL operation + /// + /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { + /// commitFusionConfigurationPublish(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface ICommitFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the WatchFusionDeployment GraphQL operation + /// + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { + /// __typename + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// } + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentSubscriptionDocument : global::StrawberryShake.IDocument + { + private WatchFusionDeploymentSubscriptionDocument() + { + } + + public static WatchFusionDeploymentSubscriptionDocument Instance { get; } = new WatchFusionDeploymentSubscriptionDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Subscription; + public global::System.ReadOnlySpan Body => "subscription WatchFusionDeployment($requestId: ID!) { onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { __typename state ... on ProcessingTaskIsQueued { queuePosition } ... on FusionConfigurationPublishingFailed { errors { __typename message } } ... on FusionConfigurationValidationFailed { errors { __typename message } } } }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "e2015ba8e5cd3117ae71c8d44bd650d7"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the WatchFusionDeployment GraphQL operation + /// + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { + /// __typename + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// } + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentSubscription : global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter; + public WatchFusionDeploymentSubscription(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IWatchFusionDeploymentResult); + + public global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(requestId); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.String requestId) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("requestId", FormatRequestId(requestId)); + return CreateRequest(variables); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return new global::StrawberryShake.OperationRequest(id: WatchFusionDeploymentSubscriptionDocument.Instance.Hash.Value, name: "WatchFusionDeployment", document: WatchFusionDeploymentSubscriptionDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + } + + private global::System.Object? FormatRequestId(global::System.String value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _iDFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the WatchFusionDeployment GraphQL operation + /// + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { + /// __typename + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// } + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeploymentSubscription : global::StrawberryShake.IOperationRequestFactory + { + global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ClientGenerator + /// + /// Represents the FusionApiClient GraphQL client + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class FusionApiClient : global::ChilliCream.Nitro.Fusion.Transport.IFusionApiClient + { + private readonly global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation _uploadFusionSourceSchema; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation _beginFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation _claimFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation _releaseFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation _validateFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation _commitFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription _watchFusionDeployment; + public FusionApiClient(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation uploadFusionSourceSchema, global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation beginFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation claimFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation releaseFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation validateFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation commitFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription watchFusionDeployment) + { + _uploadFusionSourceSchema = uploadFusionSourceSchema ?? throw new global::System.ArgumentNullException(nameof(uploadFusionSourceSchema)); + _beginFusionDeployment = beginFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(beginFusionDeployment)); + _claimFusionDeployment = claimFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(claimFusionDeployment)); + _releaseFusionDeployment = releaseFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(releaseFusionDeployment)); + _validateFusionDeployment = validateFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(validateFusionDeployment)); + _commitFusionDeployment = commitFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(commitFusionDeployment)); + _watchFusionDeployment = watchFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(watchFusionDeployment)); + } + + public static global::System.String ClientName => "FusionApiClient"; + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema => _uploadFusionSourceSchema; + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment => _beginFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment => _claimFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment => _releaseFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment => _validateFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment => _commitFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment => _watchFusionDeployment; + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ClientInterfaceGenerator + /// + /// Represents the FusionApiClient GraphQL client + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IFusionApiClient + { + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment { get; } + } +} + +namespace ChilliCream.Nitro.Fusion.Transport.State +{ + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public UploadFusionSourceSchemaResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaResult); + + public UploadFusionSourceSchemaResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is UploadFusionSourceSchemaResultInfo info) + { + return new UploadFusionSourceSchemaResult(MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(info.UploadFusionSubgraph)); + } + + throw new global::System.ArgumentException("UploadFusionSourceSchemaResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData data) + { + IUploadFusionSourceSchema_UploadFusionSubgraph returnValue = default !; + if (data.__typename.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(MapIUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(data.FusionSubgraphVersion), MapIUploadFusionSourceSchema_UploadFusionSubgraph_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? MapIUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? data) + { + if (data is null) + { + return null; + } + + IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion returnValue = default !; + if (data.__typename.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal)) + { + returnValue = new UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(data.Id ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIUploadFusionSourceSchema_UploadFusionSubgraph_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData child in list) + { + uploadFusionSubgraphErrors.Add(MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph_Errors(child)); + } + + return uploadFusionSubgraphErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_Errors MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData data) + { + IUploadFusionSourceSchema_UploadFusionSubgraph_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData invalidFusionSourceSchemaArchiveError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(invalidFusionSourceSchemaArchiveError.__typename ?? throw new global::System.ArgumentNullException(), invalidFusionSourceSchemaArchiveError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData apiNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(apiNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), apiNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(concurrentOperationError.__typename ?? throw new global::System.ArgumentNullException(), concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData duplicatedTagError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(duplicatedTagError.__typename ?? throw new global::System.ArgumentNullException(), duplicatedTagError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData invalidSourceMetadataInputError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(invalidSourceMetadataInputError.__typename ?? throw new global::System.ArgumentNullException(), invalidSourceMetadataInputError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public UploadFusionSourceSchemaResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData uploadFusionSubgraph) + { + UploadFusionSubgraph = uploadFusionSubgraph; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData UploadFusionSubgraph { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new UploadFusionSourceSchemaResultInfo(UploadFusionSubgraph); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public BeginFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentResult); + + public BeginFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is BeginFusionDeploymentResultInfo info) + { + return new BeginFusionDeploymentResult(MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(info.BeginFusionConfigurationPublish)); + } + + throw new global::System.ArgumentException("BeginFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData data) + { + IBeginFusionDeployment_BeginFusionConfigurationPublish returnValue = default !; + if (data.__typename.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(data.RequestId, MapIBeginFusionDeployment_BeginFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIBeginFusionDeployment_BeginFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData child in list) + { + beginFusionConfigurationPublishErrors.Add(MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish_Errors(child)); + } + + return beginFusionConfigurationPublishErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData data) + { + IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData apiNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(apiNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), apiNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData stageNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(stageNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), stageNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData subgraphInvalidError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(subgraphInvalidError.__typename ?? throw new global::System.ArgumentNullException(), subgraphInvalidError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData invalidSourceMetadataInputError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(invalidSourceMetadataInputError.__typename ?? throw new global::System.ArgumentNullException(), invalidSourceMetadataInputError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public BeginFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData beginFusionConfigurationPublish) + { + BeginFusionConfigurationPublish = beginFusionConfigurationPublish; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData BeginFusionConfigurationPublish { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new BeginFusionDeploymentResultInfo(BeginFusionConfigurationPublish); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public ClaimFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentResult); + + public ClaimFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is ClaimFusionDeploymentResultInfo info) + { + return new ClaimFusionDeploymentResult(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(info.StartFusionConfigurationComposition)); + } + + throw new global::System.ArgumentException("ClaimFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData data) + { + IClaimFusionDeployment_StartFusionConfigurationComposition returnValue = default !; + if (data.__typename.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData child in list) + { + startFusionConfigurationCompositionErrors.Add(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(child)); + } + + return startFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition_Errors MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData data) + { + IClaimFusionDeployment_StartFusionConfigurationComposition_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public ClaimFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData startFusionConfigurationComposition) + { + StartFusionConfigurationComposition = startFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData StartFusionConfigurationComposition { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new ClaimFusionDeploymentResultInfo(StartFusionConfigurationComposition); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public ReleaseFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentResult); + + public ReleaseFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is ReleaseFusionDeploymentResultInfo info) + { + return new ReleaseFusionDeploymentResult(MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(info.CancelFusionConfigurationComposition)); + } + + throw new global::System.ArgumentException("ReleaseFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData data) + { + IReleaseFusionDeployment_CancelFusionConfigurationComposition returnValue = default !; + if (data.__typename.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(MapIReleaseFusionDeployment_CancelFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIReleaseFusionDeployment_CancelFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData child in list) + { + cancelFusionConfigurationCompositionErrors.Add(MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors(child)); + } + + return cancelFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData data) + { + IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public ReleaseFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData cancelFusionConfigurationComposition) + { + CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData CancelFusionConfigurationComposition { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new ReleaseFusionDeploymentResultInfo(CancelFusionConfigurationComposition); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public ValidateFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentResult); + + public ValidateFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is ValidateFusionDeploymentResultInfo info) + { + return new ValidateFusionDeploymentResult(MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(info.ValidateFusionConfigurationComposition)); + } + + throw new global::System.ArgumentException("ValidateFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData data) + { + IValidateFusionDeployment_ValidateFusionConfigurationComposition returnValue = default !; + if (data.__typename.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(MapIValidateFusionDeployment_ValidateFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIValidateFusionDeployment_ValidateFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData child in list) + { + validateFusionConfigurationCompositionErrors.Add(MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors(child)); + } + + return validateFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData data) + { + IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public ValidateFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData validateFusionConfigurationComposition) + { + ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData ValidateFusionConfigurationComposition { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new ValidateFusionDeploymentResultInfo(ValidateFusionConfigurationComposition); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public CommitFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentResult); + + public CommitFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is CommitFusionDeploymentResultInfo info) + { + return new CommitFusionDeploymentResult(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(info.CommitFusionConfigurationPublish)); + } + + throw new global::System.ArgumentException("CommitFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData data) + { + ICommitFusionDeployment_CommitFusionConfigurationPublish returnValue = default !; + if (data.__typename.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData child in list) + { + commitFusionConfigurationPublishErrors.Add(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(child)); + } + + return commitFusionConfigurationPublishErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData data) + { + ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public CommitFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData commitFusionConfigurationPublish) + { + CommitFusionConfigurationPublish = commitFusionConfigurationPublish; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData CommitFusionConfigurationPublish { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new CommitFusionDeploymentResultInfo(CommitFusionConfigurationPublish); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public WatchFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentResult); + + public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is WatchFusionDeploymentResultInfo info) + { + return new WatchFusionDeploymentResult(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(info.OnFusionConfigurationPublishingTaskChanged)); + } + + throw new global::System.ArgumentException("WatchFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData data) + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData fusionConfigurationPublishingFailed) + { + if (!fusionConfigurationPublishingFailed.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(fusionConfigurationPublishingFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(fusionConfigurationPublishingFailed.Errors)); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData fusionConfigurationPublishingSuccess) + { + if (!fusionConfigurationPublishingSuccess.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(fusionConfigurationPublishingSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingSuccess.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData fusionConfigurationValidationFailed) + { + if (!fusionConfigurationValidationFailed.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(fusionConfigurationValidationFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(fusionConfigurationValidationFailed.Errors)); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData fusionConfigurationValidationSuccess) + { + if (!fusionConfigurationValidationSuccess.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(fusionConfigurationValidationSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationSuccess.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData operationInProgress) + { + if (!operationInProgress.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(operationInProgress.__typename ?? throw new global::System.ArgumentNullException(), operationInProgress.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData processingTaskApproved) + { + if (!processingTaskApproved.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(processingTaskApproved.__typename ?? throw new global::System.ArgumentNullException(), processingTaskApproved.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData processingTaskIsQueued) + { + if (!processingTaskIsQueued.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (!processingTaskIsQueued.QueuePosition.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(processingTaskIsQueued.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsQueued.State!.Value, processingTaskIsQueued.QueuePosition!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData processingTaskIsReady) + { + if (!processingTaskIsReady.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(processingTaskIsReady.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsReady.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData validationInProgress) + { + if (!validationInProgress.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(validationInProgress.__typename ?? throw new global::System.ArgumentNullException(), validationInProgress.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData waitForApproval) + { + if (!waitForApproval.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(waitForApproval.__typename ?? throw new global::System.ArgumentNullException(), waitForApproval.State!.Value); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData child in list) + { + fusionConfigurationPublishingErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(child)); + } + + return fusionConfigurationPublishingErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData data) + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData processingTimeoutError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(processingTimeoutError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData readyTimeoutError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(readyTimeoutError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData child in list) + { + fusionConfigurationValidationErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(child)); + } + + return fusionConfigurationValidationErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData data) + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData mcpFeatureCollectionValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(mcpFeatureCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData openApiCollectionValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(openApiCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData persistedQueryValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(persistedQueryValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData schemaVersionChangeViolationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(schemaVersionChangeViolationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public WatchFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData onFusionConfigurationPublishingTaskChanged) + { + OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData OnFusionConfigurationPublishingTaskChanged { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new WatchFusionDeploymentResultInfo(OnFusionConfigurationPublishingTaskChanged); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IUploadFusionSubgraphInputInfo + { + global::System.Boolean IsApiIdSet { get; } + + global::System.Boolean IsArchiveSet { get; } + + global::System.Boolean IsSourceSet { get; } + + global::System.Boolean IsTagSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface ISourceMetadataInputInfo + { + global::System.Boolean IsAzureDevOpsSet { get; } + + global::System.Boolean IsGithubSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IAzureDevOpsSourceMetadataInputInfo + { + global::System.Boolean IsActorSet { get; } + + global::System.Boolean IsCommitHashSet { get; } + + global::System.Boolean IsJobIdSet { get; } + + global::System.Boolean IsPipelineNameSet { get; } + + global::System.Boolean IsProjectUrlSet { get; } + + global::System.Boolean IsRepositoryUrlSet { get; } + + global::System.Boolean IsRunIdSet { get; } + + global::System.Boolean IsRunNumberSet { get; } + + global::System.Boolean IsTaskIdSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IAzureDevOpsActorInputInfo + { + global::System.Boolean IsEmailSet { get; } + + global::System.Boolean IsNameSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IGitHubSourceMetadataInputInfo + { + global::System.Boolean IsActorSet { get; } + + global::System.Boolean IsCommitHashSet { get; } + + global::System.Boolean IsJobIdSet { get; } + + global::System.Boolean IsRepositoryUrlSet { get; } + + global::System.Boolean IsRunIdSet { get; } + + global::System.Boolean IsRunNumberSet { get; } + + global::System.Boolean IsWorkflowNameSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IBeginFusionConfigurationPublishInputInfo + { + global::System.Boolean IsApiIdSet { get; } + + global::System.Boolean IsSourceSet { get; } + + global::System.Boolean IsStageNameSet { get; } + + global::System.Boolean IsSubgraphApiIdSet { get; } + + global::System.Boolean IsSubgraphNameSet { get; } + + global::System.Boolean IsSubgraphsSet { get; } + + global::System.Boolean IsTagSet { get; } + + global::System.Boolean IsWaitForApprovalSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IFusionSubgraphVersionInputInfo + { + global::System.Boolean IsNameSet { get; } + + global::System.Boolean IsTagSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IStartFusionConfigurationCompositionInputInfo + { + global::System.Boolean IsRequestIdSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface ICancelFusionConfigurationCompositionInputInfo + { + global::System.Boolean IsRequestIdSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IValidateFusionConfigurationCompositionInputInfo + { + global::System.Boolean IsConfigurationSet { get; } + + global::System.Boolean IsRequestIdSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface ICommitFusionConfigurationPublishInputInfo + { + global::System.Boolean IsConfigurationSet { get; } + + global::System.Boolean IsRequestIdSet { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class UploadFusionSourceSchemaBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; + public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new UploadFusionSourceSchemaResultInfo(Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "uploadFusionSubgraph"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData(typename, fusionSubgraphVersion: Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "fusionSubgraphVersion")), errors: Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData(typename, id: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _iDParser.Parse(obj.Value.GetString()!); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + uploadFusionSubgraphErrors.Add(Deserialize_NonNullableIUploadFusionSubgraphErrorData(child)); + } + + return uploadFusionSubgraphErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData Deserialize_NonNullableIUploadFusionSubgraphErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("InvalidFusionSourceSchemaArchiveError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("DuplicatedTagError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _booleanParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; + public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _booleanParser = serializerResolver.GetLeafValueParser("Boolean") ?? throw new global::System.ArgumentException("No serializer for type `Boolean` found."); + _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new BeginFusionDeploymentResultInfo(Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "beginFusionConfigurationPublish"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData(typename, requestId: Deserialize_String(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "requestId")), errors: Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String? Deserialize_String(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + return _iDParser.Parse(obj.Value.GetString()!); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + beginFusionConfigurationPublishErrors.Add(Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(child)); + } + + return beginFusionConfigurationPublishErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("StageNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("SubgraphInvalidError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new ClaimFusionDeploymentResultInfo(Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "startFusionConfigurationComposition"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + startFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(child)); + } + + return startFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new ReleaseFusionDeploymentResultInfo(Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "cancelFusionConfigurationComposition"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + cancelFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(child)); + } + + return cancelFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ValidateFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new ValidateFusionDeploymentResultInfo(Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "validateFusionConfigurationComposition"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + validateFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(child)); + } + + return validateFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new CommitFusionDeploymentResultInfo(Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "commitFusionConfigurationPublish"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData(typename, errors: Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + commitFusionConfigurationPublishErrors.Add(Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(child)); + } + + return commitFusionConfigurationPublishErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _processingStateParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _intParser; + public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _processingStateParser = serializerResolver.GetLeafValueParser("ProcessingState") ?? throw new global::System.ArgumentException("No serializer for type `ProcessingState` found."); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _intParser = serializerResolver.GetLeafValueParser("Int") ?? throw new global::System.ArgumentException("No serializer for type `Int` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new WatchFusionDeploymentResultInfo(Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "onFusionConfigurationPublishingTaskChanged"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("FusionConfigurationPublishingFailed", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + if (typename?.Equals("FusionConfigurationPublishingSuccess", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("FusionConfigurationValidationFailed", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + if (typename?.Equals("FusionConfigurationValidationSuccess", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("OperationInProgress", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ProcessingTaskApproved", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ProcessingTaskIsQueued", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), queuePosition: Deserialize_NonNullableInt32(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "queuePosition"))); + } + + if (typename?.Equals("ProcessingTaskIsReady", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ValidationInProgress", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("WaitForApproval", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + + private global::ChilliCream.Nitro.Fusion.Transport.ProcessingState Deserialize_NonNullableProcessingState(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _processingStateParser.Parse(obj.Value.GetString()!); + } + + private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + fusionConfigurationPublishingErrors.Add(Deserialize_NonNullableIFusionConfigurationPublishingErrorData(child)); + } + + return fusionConfigurationPublishingErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData Deserialize_NonNullableIFusionConfigurationPublishingErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("ProcessingTimeoutError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("ReadyTimeoutError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + fusionConfigurationValidationErrors.Add(Deserialize_NonNullableIFusionConfigurationValidationErrorData(child)); + } + + return fusionConfigurationValidationErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData Deserialize_NonNullableIFusionConfigurationValidationErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("McpFeatureCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("OpenApiCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("PersistedQueryValidationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("SchemaVersionChangeViolationError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Int32 Deserialize_NonNullableInt32(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _intParser.Parse(obj.Value.GetInt32()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record UploadFusionSubgraphPayloadData + { + public UploadFusionSubgraphPayloadData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? fusionSubgraphVersion = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + FusionSubgraphVersion = fusionSubgraphVersion; + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? FusionSubgraphVersion { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionSubgraphVersionData + { + public FusionSubgraphVersionData(global::System.String __typename, global::System.String? id = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Id = id; + } + + public global::System.String __typename { get; init; } + public global::System.String? Id { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUploadFusionSubgraphErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record InvalidFusionSourceSchemaArchiveErrorData : IUploadFusionSubgraphErrorData, IErrorData + { + public InvalidFusionSourceSchemaArchiveErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IBeginFusionConfigurationPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateApiKeyErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateApiKeyForApiErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateClientErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateMcpFeatureCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateMockSchemaErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateOpenApiCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteApiByIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IForceDeleteStageByApiIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPublishSchemaErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateApiSettingsErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateStagesErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUploadSchemaErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IValidateSchemaErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ApiNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, IDeleteApiByIdErrorData, IForceDeleteStageByApiIdErrorData, IPublishSchemaErrorData, IUpdateApiSettingsErrorData, IUpdateStagesErrorData, IUploadFusionSubgraphErrorData, IUploadSchemaErrorData, IValidateSchemaErrorData, IErrorData + { + public ApiNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUnpublishClientErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUploadClientErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUploadMcpFeatureCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUploadOpenApiCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IFusionConfigurationPublishingErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IProcessingErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ConcurrentOperationErrorData : IUnpublishClientErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData, ISchemaVersionPublishErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionPublishErrorData, IProcessingErrorData + { + public ConcurrentOperationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IApproveDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICancelDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICancelFusionConfigurationCompositionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICommitFusionConfigurationPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateAccountErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreatePersonalAccessTokenErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ICreateWorkspaceErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteApiKeyErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteClientByIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteMcpFeatureCollectionByIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteMockSchemaByIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IDeleteOpenApiCollectionByIdErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IEnsureTunnelSessionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPollClientVersionPublishRequestErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPollClientVersionValidationRequestErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPollSchemaVersionPublishRequestErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPollSchemaVersionValidationRequestErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPublishClientErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPublishMcpFeatureCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPublishOpenApiCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPushDocumentChangesErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IPushWorkspaceChangesErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IRemoveWorkspaceErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IRenameWorkspaceErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IRevokePersonalAccessTokenErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISetActiveWorkspaceErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IStartFusionConfigurationCompositionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateFeatureFlagsErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateMockSchemaErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdatePreferencesErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateStageCompositionSettingsErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IUpdateThemeSettingsErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IValidateClientErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IValidateFusionConfigurationCompositionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IValidateMcpFeatureCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IValidateOpenApiCollectionErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record UnauthorizedOperationData : IApproveDeploymentErrorData, IBeginFusionConfigurationPublishErrorData, ICancelDeploymentErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, ICreateAccountErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, ICreatePersonalAccessTokenErrorData, ICreateWorkspaceErrorData, IDeleteApiByIdErrorData, IDeleteApiKeyErrorData, IDeleteClientByIdErrorData, IDeleteMcpFeatureCollectionByIdErrorData, IDeleteMockSchemaByIdErrorData, IDeleteOpenApiCollectionByIdErrorData, IEnsureTunnelSessionErrorData, IForceDeleteStageByApiIdErrorData, IPollClientVersionPublishRequestErrorData, IPollClientVersionValidationRequestErrorData, IPollSchemaVersionPublishRequestErrorData, IPollSchemaVersionValidationRequestErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IPushDocumentChangesErrorData, IPushWorkspaceChangesErrorData, IRemoveWorkspaceErrorData, IRenameWorkspaceErrorData, IRevokePersonalAccessTokenErrorData, ISetActiveWorkspaceErrorData, IStartFusionConfigurationCompositionErrorData, IUnpublishClientErrorData, IUpdateApiSettingsErrorData, IUpdateFeatureFlagsErrorData, IUpdateMockSchemaErrorData, IUpdatePreferencesErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateThemeSettingsErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateFusionConfigurationCompositionErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData + { + public UnauthorizedOperationData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record DuplicatedTagErrorData : IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData + { + public DuplicatedTagErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record InvalidSourceMetadataInputErrorData : IBeginFusionConfigurationPublishErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData + { + public InvalidSourceMetadataInputErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record BeginFusionConfigurationPublishPayloadData + { + public BeginFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.String? requestId = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + RequestId = requestId; + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.String? RequestId { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record StageNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, IForceDeleteStageByApiIdErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUnpublishClientErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateStagesErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData + { + public StageNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record SubgraphInvalidErrorData : IBeginFusionConfigurationPublishErrorData, IErrorData + { + public SubgraphInvalidErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record InvalidProcessingStateTransitionErrorData : IBeginFusionConfigurationPublishErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData + { + public InvalidProcessingStateTransitionErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record StartFusionConfigurationCompositionPayloadData + { + public StartFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationRequestNotFoundErrorData : ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData + { + public FusionConfigurationRequestNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record CancelFusionConfigurationCompositionPayloadData + { + public CancelFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ValidateFusionConfigurationCompositionPayloadData + { + public ValidateFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record CommitFusionConfigurationPublishPayloadData + { + public CommitFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IFusionConfigurationPublishingResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationPublishingFailedData : IFusionConfigurationPublishingResultData + { + public FusionConfigurationPublishingFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationPublishingSuccessData : IFusionConfigurationPublishingResultData + { + public FusionConfigurationPublishingSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationValidationFailedData : IFusionConfigurationPublishingResultData + { + public FusionConfigurationValidationFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationValidationSuccessData : IFusionConfigurationPublishingResultData + { + public FusionConfigurationValidationSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaVersionValidationResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaVersionPublishResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionValidationResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionPublishResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionPublishResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionValidationResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionPublishResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionValidationResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record OperationInProgressData : ISchemaVersionValidationResultData, ISchemaVersionPublishResultData, IClientVersionValidationResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionPublishResultData, IMcpFeatureCollectionVersionValidationResultData + { + public OperationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ProcessingTaskApprovedData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + { + public ProcessingTaskApprovedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ProcessingTaskIsQueuedData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + { + public ProcessingTaskIsQueuedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Int32? queuePosition = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + QueuePosition = queuePosition; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Int32? QueuePosition { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ProcessingTaskIsReadyData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + { + public ProcessingTaskIsReadyData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ValidationInProgressData : IFusionConfigurationPublishingResultData, IClientVersionValidationResultData, ISchemaVersionValidationResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionValidationResultData + { + public ValidationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record WaitForApprovalData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + { + public WaitForApprovalData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IFusionConfigurationDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaVersionValidationErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IFusionConfigurationValidationErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record InvalidGraphQLSchemaErrorData : IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public InvalidGraphQLSchemaErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionValidationErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionValidationErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionValidationErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ProcessingTimeoutErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + { + public ProcessingTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ReadyTimeoutErrorData : IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + { + public ReadyTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record UnexpectedProcessingErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + { + public UnexpectedProcessingErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record McpFeatureCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IMcpFeatureCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public McpFeatureCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record OpenApiCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IOpenApiCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public OpenApiCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientDeploymentErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record PersistedQueryValidationErrorData : IClientDeploymentErrorData, IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public PersistedQueryValidationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record SchemaVersionChangeViolationErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public SchemaVersionChangeViolationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.NoStoreAccessorGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class FusionApiClientStoreAccessor : global::StrawberryShake.IStoreAccessor + { + public global::StrawberryShake.IOperationStore OperationStore => throw new global::System.NotSupportedException("OperationStore is not supported in store less mode"); + public global::StrawberryShake.IEntityStore EntityStore => throw new global::System.NotSupportedException("EntityStore is not supported in store less mode"); + public global::StrawberryShake.IEntityIdSerializer EntityIdSerializer => throw new global::System.NotSupportedException("EntityIdSerializer is not supported in store less mode"); + + public global::StrawberryShake.IOperationRequestFactory GetOperationRequestFactory(global::System.Type resultType) + { + throw new global::System.NotSupportedException("GetOperationRequestFactory is not supported in store less mode"); + } + + public global::StrawberryShake.IOperationResultDataFactory GetOperationResultDataFactory(global::System.Type resultType) + { + throw new global::System.NotSupportedException("GetOperationResultDataFactory is not supported in store less mode"); + } + } +} + +namespace Microsoft.Extensions.DependencyInjection +{ + // StrawberryShake.CodeGeneration.CSharp.Generators.DependencyInjectionGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal static partial class FusionApiClientServiceCollectionExtensions + { + public static global::StrawberryShake.IClientBuilder AddFusionApiClient(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::StrawberryShake.ExecutionStrategy strategy = global::StrawberryShake.ExecutionStrategy.NetworkOnly) + { + var serviceCollection = new global::Microsoft.Extensions.DependencyInjection.ServiceCollection(); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => + { + ConfigureClientDefault(sp, serviceCollection, strategy); + return new ClientServiceProvider(global::Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(serviceCollection)); + }); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::ChilliCream.Nitro.Fusion.Transport.State.FusionApiClientStoreAccessor()); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + return new global::StrawberryShake.ClientBuilder("FusionApiClient", services, serviceCollection); + } + + private static global::Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureClientDefault(global::System.IServiceProvider parentServices, global::Microsoft.Extensions.DependencyInjection.ServiceCollection services, global::StrawberryShake.ExecutionStrategy strategy = global::StrawberryShake.ExecutionStrategy.NetworkOnly) + { + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => + { + var clientFactory = global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(parentServices); + return new global::StrawberryShake.Transport.Http.HttpConnection(() => clientFactory.CreateClient("FusionApiClient")); + }); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::StrawberryShake.Serialization.SerializerResolver(global::System.Linq.Enumerable.Concat(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(parentServices), global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSourceSchemaResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSourceSchemaBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ReleaseFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ReleaseFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + return services; + } + + private sealed class ClientServiceProvider : System.IServiceProvider, System.IDisposable + { + private readonly System.IServiceProvider _provider; + public ClientServiceProvider(System.IServiceProvider provider) + { + _provider = provider; + } + + public object? GetService(System.Type serviceType) + { + return _provider.GetService(serviceType); + } + + public void Dispose() + { + if (_provider is System.IDisposable d) + { + d.Dispose(); + } + } + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs new file mode 100644 index 00000000000..6e3b40a540a --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs @@ -0,0 +1,23 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Reconciles Fusion source schema versions and publishes Fusion configurations. +/// +public interface IFusionDeploymentWorkflow +{ + /// + /// Ensures that an immutable source schema version exists with the expected content. + /// + Task ReconcileSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaUpload source, + CancellationToken cancellationToken); + + /// + /// Publishes a locally composed Fusion archive and waits for a verified terminal result. + /// + Task PublishAsync( + FusionPublicationRequest request, + string fusionArchivePath, + CancellationToken cancellationToken); +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs new file mode 100644 index 00000000000..e8491e82ad1 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using ChilliCream.Nitro.Fusion.Transport; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace ChilliCream.Nitro.Fusion; + +/// +/// Registers the Nitro Fusion deployment workflow. +/// +public static class NitroFusionServiceCollectionExtensions +{ + /// + /// Adds the Nitro Fusion deployment workflow and its remote transport. + /// + public static IServiceCollection AddNitroFusionDeploymentWorkflow( + this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + services.TryAddSingleton< + IFusionDeploymentTransportFactory, + FusionApiTransportFactory>(); + services.TryAddSingleton< + IFusionDeploymentWorkflow, + FusionDeploymentWorkflow>(); + return services; + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql new file mode 100644 index 00000000000..50cb60a0e28 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql @@ -0,0 +1,3 @@ +fragment FusionError on Error { + message +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql new file mode 100644 index 00000000000..57dce1b629e --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql @@ -0,0 +1,65 @@ +mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + beginFusionConfigurationPublish(input: $input) { + requestId + errors { + __typename + ...FusionError + } + } +} + +mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { + startFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} + +mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { + cancelFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} + +mutation ValidateFusionDeployment($input: ValidateFusionConfigurationCompositionInput!) { + validateFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} + +mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { + commitFusionConfigurationPublish(input: $input) { + errors { + __typename + ...FusionError + } + } +} + +subscription WatchFusionDeployment($requestId: ID!) { + onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { + __typename + state + ... on ProcessingTaskIsQueued { + queuePosition + } + ... on FusionConfigurationPublishingFailed { + errors { + message + } + } + ... on FusionConfigurationValidationFailed { + errors { + message + } + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql new file mode 100644 index 00000000000..fb98f1cd230 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql @@ -0,0 +1,11 @@ +mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + uploadFusionSubgraph(input: $input) { + fusionSubgraphVersion { + id + } + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md new file mode 100644 index 00000000000..6a6502da3b2 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md @@ -0,0 +1,14 @@ +# ChilliCream.Nitro.Fusion + +Provides the high-level workflow used to reconcile immutable Fusion source schema versions and +publish composed Fusion configurations to Nitro. + +```csharp +services.AddNitroFusionDeploymentWorkflow(); + +var workflow = services.BuildServiceProvider() + .GetRequiredService(); +``` + +The public API uses deployment DTOs only. Nitro's generated GraphQL management types remain +internal to this package. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs new file mode 100644 index 00000000000..76b1a1a682f --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs @@ -0,0 +1,323 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using StrawberryShake; + +namespace ChilliCream.Nitro.Fusion.Transport; + +internal sealed class FusionApiTransportFactory : IFusionDeploymentTransportFactory +{ + public ValueTask OpenAsync( + FusionTarget target, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var services = new ServiceCollection(); + services.AddHttpClient( + FusionApiClient.ClientName, + client => ConfigureClient(client, target)); + services.AddFusionApiClient(); + + var provider = services.BuildServiceProvider(); + var apiClient = provider.GetRequiredService(); + var httpClientFactory = provider.GetRequiredService(); + + return ValueTask.FromResult( + new FusionApiTransport( + target, + provider, + apiClient, + httpClientFactory.CreateClient(FusionApiClient.ClientName))); + } + + private static void ConfigureClient(HttpClient client, FusionTarget target) + { + client.BaseAddress = target.CloudUrl; + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.TryAddWithoutValidation("GraphQL-Preflight", "1"); + client.DefaultRequestHeaders.TryAddWithoutValidation( + "GraphQL-Client-Version", + GetVersion()); + client.DefaultRequestHeaders.TryAddWithoutValidation( + "ccc-agent", + $"Nitro Fusion/{GetVersion()}"); + client.DefaultRequestHeaders.TryAddWithoutValidation( + "CCC-api-key", + target.ApiKey); + client.DefaultRequestVersion = new Version(2, 0); + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + } + + private static string GetVersion() + { + var version = typeof(FusionApiTransportFactory).Assembly.GetName().Version!; + return new Version(version.Major, version.Minor, version.Build).ToString(); + } + + private sealed class FusionApiTransport( + FusionTarget target, + ServiceProvider provider, + IFusionApiClient apiClient, + HttpClient httpClient) + : IFusionDeploymentTransport + { + public async Task DownloadSourceSchemaAsync( + string name, + string version, + CancellationToken cancellationToken) + { + const string path = + "/api/v1/apis/{0}/fusion-subgraphs/{1}/versions/{2}/download"; + var requestUri = string.Format( + path, + Uri.EscapeDataString(target.ApiId), + Uri.EscapeDataString(name), + Uri.EscapeDataString(version)); + using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + using var response = await httpClient.SendAsync(request, cancellationToken); + + if (response.StatusCode is HttpStatusCode.NotFound) + { + return null; + } + + EnsureSuccess(response); + return await response.Content.ReadAsByteArrayAsync(cancellationToken); + } + + public async Task UploadSourceSchemaAsync( + string version, + string archivePath, + CancellationToken cancellationToken) + { + await using var archive = File.OpenRead(archivePath); + var input = new UploadFusionSubgraphInput + { + ApiId = target.ApiId, + Tag = version, + Archive = new Upload(archive, "source-schema.zip") + }; + var result = await apiClient.UploadFusionSourceSchema.ExecuteAsync( + input, + cancellationToken); + var data = EnsureData(result).UploadFusionSubgraph; + var errors = GetErrors(data.Errors); + + return new FusionRemoteCommandResult( + data.FusionSubgraphVersion is not null && errors.Count is 0, + data.Errors?.Any(e => e.__typename is "DuplicatedTagError") is true, + errors); + } + + public async Task BeginPublishAsync( + FusionPublicationRequest request, + CancellationToken cancellationToken) + { + var input = new BeginFusionConfigurationPublishInput + { + ApiId = target.ApiId, + StageName = request.Stage, + Tag = request.ConfigurationTag, + WaitForApproval = request.WaitForApproval, + Subgraphs = request.SourceSchemas + .Select(s => new FusionSubgraphVersionInput + { + Name = s.Name, + Tag = s.Version + }) + .ToArray() + }; + var result = await apiClient.BeginFusionDeployment.ExecuteAsync( + input, + cancellationToken); + var data = EnsureData(result).BeginFusionConfigurationPublish; + + return new FusionRemoteBeginResult( + data.RequestId, + GetErrors(data.Errors)); + } + + public async IAsyncEnumerable WatchPublishAsync( + string requestId, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + using var stopSignal = new ReplaySubject(1); + await using var registration = cancellationToken.Register( + () => + { + stopSignal.OnNext(Unit.Default); + stopSignal.OnCompleted(); + }); + var subscription = apiClient.WatchFusionDeployment + .Watch(requestId, ExecutionStrategy.NetworkOnly) + .TakeUntil(stopSignal); + + await foreach (var result in subscription.ToAsyncEnumerable()) + { + var value = EnsureData(result) + .OnFusionConfigurationPublishingTaskChanged; + yield return ToRemoteEvent(value); + } + } + + public async Task ClaimPublishAsync( + string requestId, + CancellationToken cancellationToken) + { + var result = await apiClient.ClaimFusionDeployment.ExecuteAsync( + new StartFusionConfigurationCompositionInput + { + RequestId = requestId + }, + cancellationToken); + var data = EnsureData(result).StartFusionConfigurationComposition; + return ToCommandResult(data.Errors); + } + + public async Task ReleasePublishAsync( + string requestId, + CancellationToken cancellationToken) + { + var result = await apiClient.ReleaseFusionDeployment.ExecuteAsync( + new CancelFusionConfigurationCompositionInput + { + RequestId = requestId + }, + cancellationToken); + var data = EnsureData(result).CancelFusionConfigurationComposition; + return ToCommandResult(data.Errors); + } + + public async Task ValidatePublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken) + { + await using var archive = File.OpenRead(archivePath); + var result = await apiClient.ValidateFusionDeployment.ExecuteAsync( + new ValidateFusionConfigurationCompositionInput + { + RequestId = requestId, + Configuration = new Upload(archive, "gateway.far") + }, + cancellationToken); + var data = EnsureData(result).ValidateFusionConfigurationComposition; + return ToCommandResult(data.Errors); + } + + public async Task CommitPublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken) + { + await using var archive = File.OpenRead(archivePath); + var result = await apiClient.CommitFusionDeployment.ExecuteAsync( + new CommitFusionConfigurationPublishInput + { + RequestId = requestId, + Configuration = new Upload(archive, "gateway.far") + }, + cancellationToken); + var data = EnsureData(result).CommitFusionConfigurationPublish; + return ToCommandResult(data.Errors); + } + + public async ValueTask DisposeAsync() + { + httpClient.Dispose(); + await provider.DisposeAsync(); + } + + private static FusionRemoteCommandResult ToCommandResult( + IReadOnlyList? errors) + where TError : class + { + var messages = GetErrors(errors); + return new FusionRemoteCommandResult( + messages.Count is 0, + false, + messages); + } + + private static List GetErrors( + IReadOnlyList? errors) + where TError : class + => errors? + .Select(error => error as IFusionError) + .Where(error => error is not null) + .Select(error => error!.Message) + .ToList() + ?? []; + + private static FusionRemoteEvent ToRemoteEvent( + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged value) + { + var errors = value switch + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed failed + => failed.Errors.Select(error => error.Message).ToArray(), + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed failed + => failed.Errors.Select(error => error.Message).ToArray(), + _ => [] + }; + var kind = value.__typename switch + { + "ProcessingTaskIsReady" => FusionRemoteEventKind.Ready, + "ProcessingTaskIsQueued" => FusionRemoteEventKind.Queued, + "FusionConfigurationValidationSuccess" + => FusionRemoteEventKind.ValidationSucceeded, + "FusionConfigurationValidationFailed" + => FusionRemoteEventKind.ValidationFailed, + "FusionConfigurationPublishingSuccess" + => FusionRemoteEventKind.PublishingSucceeded, + "FusionConfigurationPublishingFailed" + => FusionRemoteEventKind.PublishingFailed, + "WaitForApproval" => FusionRemoteEventKind.WaitingForApproval, + "ProcessingTaskApproved" => FusionRemoteEventKind.Approved, + "OperationInProgress" or "ValidationInProgress" + => FusionRemoteEventKind.InProgress, + _ => FusionRemoteEventKind.Unknown + }; + + return new FusionRemoteEvent(kind, errors); + } + + private static TData EnsureData(IOperationResult result) + where TData : class + { + if (result.Errors is { Count: > 0 } errors) + { + throw new FusionDeploymentException( + $"Nitro GraphQL request failed: {errors[0].Message}"); + } + + return result.Data + ?? throw new FusionDeploymentException( + "Nitro GraphQL request returned no data."); + } + + private static void EnsureSuccess(HttpResponseMessage response) + { + if (response.StatusCode is HttpStatusCode.Unauthorized + or HttpStatusCode.Forbidden) + { + throw new FusionDeploymentException( + "Nitro rejected the supplied API key."); + } + + if (!response.IsSuccessStatusCode) + { + throw new FusionDeploymentException( + $"Nitro returned HTTP {(int)response.StatusCode} " + + $"({response.ReasonPhrase})."); + } + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs new file mode 100644 index 00000000000..09d08dcb87a --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs @@ -0,0 +1,78 @@ +namespace ChilliCream.Nitro.Fusion.Transport; + +internal interface IFusionDeploymentTransportFactory +{ + ValueTask OpenAsync( + FusionTarget target, + CancellationToken cancellationToken); +} + +internal interface IFusionDeploymentTransport : IAsyncDisposable +{ + Task DownloadSourceSchemaAsync( + string name, + string version, + CancellationToken cancellationToken); + + Task UploadSourceSchemaAsync( + string version, + string archivePath, + CancellationToken cancellationToken); + + Task BeginPublishAsync( + FusionPublicationRequest request, + CancellationToken cancellationToken); + + IAsyncEnumerable WatchPublishAsync( + string requestId, + CancellationToken cancellationToken); + + Task ClaimPublishAsync( + string requestId, + CancellationToken cancellationToken); + + Task ReleasePublishAsync( + string requestId, + CancellationToken cancellationToken); + + Task ValidatePublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken); + + Task CommitPublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken); +} + +internal sealed record FusionRemoteCommandResult( + bool Succeeded, + bool IsDuplicate, + IReadOnlyList Errors) +{ + public static FusionRemoteCommandResult Success { get; } = + new(true, false, []); +} + +internal sealed record FusionRemoteBeginResult( + string? RequestId, + IReadOnlyList Errors); + +internal sealed record FusionRemoteEvent( + FusionRemoteEventKind Kind, + IReadOnlyList Errors); + +internal enum FusionRemoteEventKind +{ + Ready, + Queued, + ValidationSucceeded, + ValidationFailed, + PublishingSucceeded, + PublishingFailed, + InProgress, + WaitingForApproval, + Approved, + Unknown +} diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj new file mode 100644 index 00000000000..dd44a20ace0 --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj @@ -0,0 +1,15 @@ + + + + + + ChilliCream.Nitro.Aspire.Tests + ChilliCream.Nitro.Aspire + net11.0;net10.0;net9.0 + + + + + + + diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs new file mode 100644 index 00000000000..8b787069118 --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs @@ -0,0 +1,230 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Pipelines; +using System.Text.Json; + +namespace ChilliCream.Nitro.Aspire; + +#pragma warning disable ASPIREPIPELINES001 + +public sealed class FusionPipelineTests +{ + [Fact] + public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() + { + var builder = DistributedApplication.CreateBuilder(); + var nitro = builder + .AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products"); + nitro + .AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag("release-1"); + nitro + .AddFusionDeployment("staging") + .ForEnvironment("Staging") + .ToStage("staging") + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + var deployments = FusionPipeline.SelectDeployments( + model, + "Production"); + + Assert.Equal(["production"], deployments.Select(x => x.Name)); + } + + [Fact] + public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() + { + var builder = DistributedApplication.CreateBuilder(); + builder + .AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products") + .AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + var deployments = FusionPipeline.SelectDeployments( + model, + "Development"); + + Assert.Empty(deployments); + } + + [Fact] + public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() + { + var builder = DistributedApplication.CreateBuilder(); + var nitro = builder + .AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products"); + nitro + .AddFusionDeployment("production-a") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag("release-1"); + nitro + .AddFusionDeployment("production-b") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + var exception = Assert.Throws( + () => FusionPipeline.SelectDeployments(model, "Production")); + + Assert.Equal( + "Multiple Fusion deployments map environment 'Production' to Nitro " + + "API 'products' stage 'production'.", + exception.Message); + } + + [Fact] + public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() + { + var resource = new FusionPipelineResource("fusion-pipeline"); + + var steps = FusionPipeline.CreateStepDefinitionsForTest(resource); + + string.Join( + Environment.NewLine, + steps.Select(step => + $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]; " + + $"requiredBy=[{string.Join(", ", step.RequiredBySteps)}]")) + .MatchInlineSnapshot( + """ + fusion-artifacts: depends=[]; requiredBy=[publish] + fusion-readiness: depends=[fusion-artifacts]; requiredBy=[] + fusion-upload: depends=[fusion-artifacts]; requiredBy=[] + fusion-publish: depends=[fusion-upload, fusion-readiness]; requiredBy=[deploy] + """); + } + + [Fact] + public void GetTransportEndpoint_Should_Fail_WhenProductionBindingIsMissing() + { + using var settings = JsonDocument.Parse( + """ + { + "name": "products" + } + """); + + var exception = Assert.Throws( + () => FusionPipelineExecutor.GetTransportEndpoint(settings)); + + Assert.Equal( + "Fusion deployment settings must specify an absolute production " + + "transports.http.url.", + exception.Message); + } + + [Fact] + public void ReplaceDirectoryAtomically_Should_RemoveSource_WhenSourceWasRemoved() + { + using var testDirectory = new TestDirectory(); + var destination = Path.Combine(testDirectory.Path, "production"); + var replacement = Path.Combine(testDirectory.Path, "replacement"); + WriteArtifactFile(destination, "sources/products/schema.graphqls"); + WriteArtifactFile(destination, "sources/reviews/schema.graphqls"); + WriteArtifactFile(destination, "nitro-deployment.json"); + WriteArtifactFile(replacement, "sources/products/schema.graphqls"); + WriteArtifactFile(replacement, "nitro-deployment-template.json"); + + FusionPipelineExecutor.ReplaceDirectoryAtomically( + replacement, + destination); + + GetArtifactFiles(destination) + .MatchInlineSnapshot( + """ + nitro-deployment-template.json + sources/products/schema.graphqls + """); + } + + [Fact] + public void ReplaceDirectoryAtomically_Should_RemoveExtensions_WhenExtensionsWereRemoved() + { + using var testDirectory = new TestDirectory(); + var destination = Path.Combine(testDirectory.Path, "production"); + var replacement = Path.Combine(testDirectory.Path, "replacement"); + WriteArtifactFile(destination, "sources/products/schema.graphqls"); + WriteArtifactFile( + destination, + "sources/products/schema-extensions.graphqls"); + WriteArtifactFile(replacement, "sources/products/schema.graphqls"); + + FusionPipelineExecutor.ReplaceDirectoryAtomically( + replacement, + destination); + + GetArtifactFiles(destination) + .MatchInlineSnapshot("sources/products/schema.graphqls"); + } + + [Theory] + [InlineData("release/1")] + [InlineData(@"release\1")] + [InlineData("release:1")] + [InlineData("..")] + public void ValidatePathSegment_Should_Fail_WhenValueIsNotPortable( + string value) + { + var exception = Assert.Throws( + () => FusionPipelineExecutor.ValidatePathSegment( + value, + "configuration tag")); + + Assert.Equal( + $"Fusion configuration tag '{value}' cannot be used as a portable path segment.", + exception.Message); + } + + private static void WriteArtifactFile( + string root, + string relativePath) + { + var path = Path.Combine( + root, + relativePath.Replace('/', Path.DirectorySeparatorChar)); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, "artifact"); + } + + private static string GetArtifactFiles(string root) + => string.Join( + Environment.NewLine, + Directory + .EnumerateFiles(root, "*", SearchOption.AllDirectories) + .Select(path => Path.GetRelativePath(root, path).Replace('\\', '/')) + .Order()); + + private sealed class TestDirectory : IDisposable + { + public TestDirectory() + { + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "chilicream-nitro-aspire-tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + Directory.Delete(Path, recursive: true); + } + } +} + +#pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj new file mode 100644 index 00000000000..cec0c7f1668 --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj @@ -0,0 +1,15 @@ + + + + + + ChilliCream.Nitro.Fusion.Tests + ChilliCream.Nitro.Fusion + net10.0 + + + + + + + diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs new file mode 100644 index 00000000000..edade30db5c --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs @@ -0,0 +1,454 @@ +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using ChilliCream.Nitro.Fusion.Transport; +using HotChocolate.Fusion.SourceSchema.Packaging; + +namespace ChilliCream.Nitro.Fusion; + +public sealed class FusionDeploymentWorkflowTests +{ + [Fact] + public void Assembly_Should_ExposeOnlyWorkflowContract_When_Inspected() + { + var exportedTypes = typeof(IFusionDeploymentWorkflow) + .Assembly + .GetExportedTypes() + .Select(type => type.FullName) + .Order(StringComparer.Ordinal) + .ToArray(); + + string.Join(Environment.NewLine, exportedTypes) + .MatchInlineSnapshot( + """ + ChilliCream.Nitro.Fusion.FusionDeploymentException + ChilliCream.Nitro.Fusion.FusionIdentityCollisionException + ChilliCream.Nitro.Fusion.FusionIndeterminateStateException + ChilliCream.Nitro.Fusion.FusionPublicationRequest + ChilliCream.Nitro.Fusion.FusionSourceSchemaUpload + ChilliCream.Nitro.Fusion.FusionSourceSchemaVersion + ChilliCream.Nitro.Fusion.FusionTarget + ChilliCream.Nitro.Fusion.IFusionDeploymentWorkflow + ChilliCream.Nitro.Fusion.NitroFusionServiceCollectionExtensions + """); + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_NoOp_When_NormalizedContentMatches() + { + var directory = CreateTemporaryDirectory(); + try + { + var localPath = Path.Combine(directory, "local.fss"); + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + localPath, + "products", + "type Query { product: String }", + """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); + await CreateArchiveAsync( + remotePath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + var transport = new FakeTransport + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(transport); + + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(localPath), + TestContext.Current.CancellationToken); + + Assert.Equal(0, transport.UploadCount); + Assert.Equal(1, transport.DownloadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_ThrowCollision_When_ContentDiffers() + { + var directory = CreateTemporaryDirectory(); + try + { + var localPath = Path.Combine(directory, "local.fss"); + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + localPath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + await CreateArchiveAsync( + remotePath, + "products", + "type Query { product: Int }", + """{"name":"products"}"""); + var transport = new FakeTransport + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(transport); + var upload = await CreateUploadAsync(localPath); + + var exception = await Assert.ThrowsAsync( + () => workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + upload, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "Source schema 'products' version '20260730' already exists " + + "with different normalized schema, settings, or extensions.", + exception.Message); + Assert.Equal(0, transport.UploadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_VerifyReadBack_When_UploadIsUncertain() + { + var directory = CreateTemporaryDirectory(); + try + { + var archivePath = Path.Combine(directory, "products.fss"); + await CreateArchiveAsync( + archivePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var archive = await File.ReadAllBytesAsync( + archivePath, + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + UploadException = new IOException("Connection reset."), + RemoteArchiveAfterUpload = archive + }; + var workflow = CreateWorkflow(transport); + + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(archivePath), + TestContext.Current.CancellationToken); + + Assert.Equal(1, transport.UploadCount); + Assert.Equal(2, transport.DownloadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task PublishAsync_Should_Commit_When_ValidationSucceeds() + { + var directory = CreateTemporaryDirectory(); + try + { + var fusionArchivePath = Path.Combine(directory, "gateway.far"); + await File.WriteAllTextAsync( + fusionArchivePath, + "fusion archive", + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + Events = + [ + new(FusionRemoteEventKind.Ready, []), + new(FusionRemoteEventKind.ValidationSucceeded, []), + new(FusionRemoteEventKind.PublishingSucceeded, []) + ] + }; + var workflow = CreateWorkflow(transport); + + await workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchivePath, + TestContext.Current.CancellationToken); + + Assert.Equal( + ["begin", "watch", "claim", "validate", "commit"], + transport.Calls); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task PublishAsync_Should_ReleaseAndFail_When_ValidationFailsWithoutForce() + { + var directory = CreateTemporaryDirectory(); + try + { + var fusionArchivePath = Path.Combine(directory, "gateway.far"); + await File.WriteAllTextAsync( + fusionArchivePath, + "fusion archive", + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + Events = + [ + new(FusionRemoteEventKind.Ready, []), + new( + FusionRemoteEventKind.ValidationFailed, + ["Breaking schema change."]) + ] + }; + var workflow = CreateWorkflow(transport); + + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchivePath, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "Nitro rejected the Fusion configuration validation. " + + "Breaking schema change.", + exception.Message); + Assert.Equal( + ["begin", "watch", "claim", "validate", "release"], + transport.Calls); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task PublishAsync_Should_Commit_When_ValidationFailsWithForce() + { + var directory = CreateTemporaryDirectory(); + try + { + var fusionArchivePath = Path.Combine(directory, "gateway.far"); + await File.WriteAllTextAsync( + fusionArchivePath, + "fusion archive", + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + Events = + [ + new( + FusionRemoteEventKind.Ready, + []), + new( + FusionRemoteEventKind.ValidationFailed, + ["Breaking schema change."]), + new( + FusionRemoteEventKind.PublishingSucceeded, + []) + ] + }; + var workflow = CreateWorkflow(transport); + + await workflow.PublishAsync( + CreatePublicationRequest(force: true), + fusionArchivePath, + TestContext.Current.CancellationToken); + + Assert.Equal( + ["begin", "watch", "claim", "validate", "commit"], + transport.Calls); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + private static FusionDeploymentWorkflow CreateWorkflow( + FakeTransport transport) + => new(new FakeTransportFactory(transport)); + + private static FusionTarget CreateTarget() + => new( + new Uri("https://api.chillicream.com"), + "api-id", + "secret"); + + private static FusionPublicationRequest CreatePublicationRequest(bool force) + => new( + CreateTarget(), + "production", + "20260730", + [new FusionSourceSchemaVersion("products", "20260730")], + WaitForApproval: false, + Force: force, + OperationTimeout: TimeSpan.FromMinutes(1), + ApprovalTimeout: TimeSpan.FromMinutes(1)); + + private static async Task CreateUploadAsync( + string archivePath) + { + await using var stream = File.OpenRead(archivePath); + var sha256 = Convert.ToHexString( + await SHA256.HashDataAsync( + stream, + TestContext.Current.CancellationToken)); + return new FusionSourceSchemaUpload( + "products", + "20260730", + archivePath, + sha256); + } + + private static async Task CreateArchiveAsync( + string path, + string name, + string schema, + string settings) + { + using var archive = FusionSourceSchemaArchive.Create(path); + await archive.SetArchiveMetadataAsync( + new ArchiveMetadata(), + TestContext.Current.CancellationToken); + await archive.SetSchemaAsync( + Encoding.UTF8.GetBytes(schema), + TestContext.Current.CancellationToken); + using var settingsDocument = JsonDocument.Parse(settings); + await archive.SetSettingsAsync( + settingsDocument, + TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + private static string CreateTemporaryDirectory() + { + var path = Path.Combine( + Path.GetTempPath(), + "nitro-fusion-tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } + + private sealed class FakeTransportFactory(FakeTransport transport) + : IFusionDeploymentTransportFactory + { + public ValueTask OpenAsync( + FusionTarget target, + CancellationToken cancellationToken) + => ValueTask.FromResult(transport); + } + + private sealed class FakeTransport : IFusionDeploymentTransport + { + public byte[]? RemoteArchive { get; set; } + + public byte[]? RemoteArchiveAfterUpload { get; set; } + + public Exception? UploadException { get; set; } + + public IReadOnlyList Events { get; init; } = []; + + public int DownloadCount { get; private set; } + + public int UploadCount { get; private set; } + + public List Calls { get; } = []; + + public Task DownloadSourceSchemaAsync( + string name, + string version, + CancellationToken cancellationToken) + { + DownloadCount++; + return Task.FromResult( + DownloadCount > 1 && RemoteArchiveAfterUpload is not null + ? RemoteArchiveAfterUpload + : RemoteArchive); + } + + public Task UploadSourceSchemaAsync( + string version, + string archivePath, + CancellationToken cancellationToken) + { + UploadCount++; + if (UploadException is not null) + { + throw UploadException; + } + + return Task.FromResult(FusionRemoteCommandResult.Success); + } + + public Task BeginPublishAsync( + FusionPublicationRequest request, + CancellationToken cancellationToken) + { + Calls.Add("begin"); + return Task.FromResult( + new FusionRemoteBeginResult("request-id", [])); + } + + public async IAsyncEnumerable WatchPublishAsync( + string requestId, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + Calls.Add("watch"); + foreach (var @event in Events) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return @event; + await Task.Yield(); + } + } + + public Task ClaimPublishAsync( + string requestId, + CancellationToken cancellationToken) + => Success("claim"); + + public Task ReleasePublishAsync( + string requestId, + CancellationToken cancellationToken) + => Success("release"); + + public Task ValidatePublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken) + => Success("validate"); + + public Task CommitPublishAsync( + string requestId, + string archivePath, + CancellationToken cancellationToken) + => Success("commit"); + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + + private Task Success(string call) + { + Calls.Add(call); + return Task.FromResult(FusionRemoteCommandResult.Success); + } + } +} From 56a865a1fab256f4d170d0674064a8b7f3cbb6d6 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:01:16 +0200 Subject: [PATCH 02/11] wip --- .../Fusion.Aspire/AspireCompositionHelper.cs | 63 +- .../AspireCompositionHelperTests.cs | 245 ++++ .../FusionDeploymentResource.cs | 4 + .../FusionPipeline.cs | 177 ++- .../FusionPipelineExecutor.cs | 1056 ++++++++++++++++- .../FusionReleaseManifest.cs | 156 +++ .../FusionReleaseStore.cs | 475 ++++++++ .../IFusionPipelineExecutor.cs | 4 + .../NitroResourceBuilderExtensions.cs | 59 +- .../src/ChilliCream.Nitro.Aspire/README.md | 19 +- .../FusionArchiveContent.cs | 61 + .../FusionDeploymentWorkflow.cs | 99 +- .../FusionSourceSchemaContent.cs | 32 + .../FusionSourceSchemaDownload.cs | 10 + .../IFusionDeploymentWorkflow.cs | 9 + .../src/ChilliCream.Nitro.Fusion/README.md | 5 + .../FusionPipelineTests.cs | 565 ++++++++- .../FusionReleaseAcceptanceTests.cs | 473 ++++++++ .../ChilliCream.Nitro.Fusion.Tests.csproj | 2 +- .../FusionDeploymentWorkflowTests.cs | 275 +++++ 20 files changed, 3748 insertions(+), 41 deletions(-) create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs index b354272996e..d4375b84ddb 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; using System.Text; +using System.Text.Json; +using HotChocolate.Buffers; using HotChocolate.Fusion.Logging; using HotChocolate.Fusion.Packaging; using HotChocolate.Fusion.SourceSchema.Packaging; @@ -9,13 +11,30 @@ namespace HotChocolate.Fusion.Aspire; internal static class AspireCompositionHelper { + public static Task TryComposeArchivesAsync( + string fusionArchivePath, + IReadOnlyList archives, + GraphQLCompositionSettings settings, + ILogger logger, + CancellationToken cancellationToken) + => TryComposeArchivesAsync( + fusionArchivePath, + archives, + settings.EnvironmentName ?? "Aspire", + settings, + logger, + cancellationToken); + public static async Task TryComposeArchivesAsync( string fusionArchivePath, IReadOnlyList archives, + string environmentName, GraphQLCompositionSettings settings, ILogger logger, CancellationToken cancellationToken) { + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + var sourceSchemas = new List(archives.Count); try @@ -51,6 +70,7 @@ extensions is null return await TryComposeAsync( fusionArchivePath, [.. sourceSchemas], + environmentName, settings, logger, cancellationToken); @@ -64,19 +84,35 @@ extensions is null } } + public static Task TryComposeAsync( + string fusionArchivePath, + ImmutableArray newSourceSchemas, + GraphQLCompositionSettings settings, + ILogger logger, + CancellationToken cancellationToken) + => TryComposeAsync( + fusionArchivePath, + newSourceSchemas, + settings.EnvironmentName ?? "Aspire", + settings, + logger, + cancellationToken); + public static async Task TryComposeAsync( string fusionArchivePath, ImmutableArray newSourceSchemas, + string environmentName, GraphQLCompositionSettings settings, ILogger logger, CancellationToken cancellationToken) { + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + using var archive = File.Exists(fusionArchivePath) ? FusionArchive.Open(fusionArchivePath, FusionArchiveMode.Update) : FusionArchive.Create(fusionArchivePath); var compositionLog = new CompositionLog(); - var environment = settings.EnvironmentName ?? "Aspire"; var compositionSettings = CreateCompositionSettings(settings); var sourceSchemas = newSourceSchemas.ToDictionary( s => s.Name, @@ -86,7 +122,7 @@ public static async Task TryComposeAsync( compositionLog, sourceSchemas, archive, - environment, + environmentName, compositionSettings, legacyArchive: null, cancellationToken); @@ -118,6 +154,29 @@ public static async Task TryComposeAsync( return true; } + internal static JsonDocument ResolveSourceSchemaSettings( + JsonDocument sourceSchemaSettings, + string environmentName) + { + ArgumentNullException.ThrowIfNull(sourceSchemaSettings); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + using var buffer = new PooledArrayWriter(); + new SettingsComposer().Compose( + buffer, + [sourceSchemaSettings.RootElement], + environmentName); + using var gatewaySettings = JsonDocument.Parse(buffer.WrittenMemory); + var sourceSchemas = gatewaySettings.RootElement + .GetProperty("sourceSchemas"); + var resolvedSettings = sourceSchemas + .EnumerateObject() + .Single() + .Value; + + return JsonSerializer.SerializeToDocument(resolvedSettings); + } + internal static CompositionSettings CreateCompositionSettings( GraphQLCompositionSettings settings) { diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs index 70afd211e2c..68b02c00528 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs @@ -1,12 +1,161 @@ +using System.Text; using System.Text.Json; using HotChocolate.Fusion.Options; using HotChocolate.Fusion.Packaging; +using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.Logging.Abstractions; +using IOPath = System.IO.Path; namespace HotChocolate.Fusion.Aspire; public sealed class AspireCompositionHelperTests { + [Fact] + public async Task TryComposeArchivesAsync_Should_ResolveSettings_WhenEnvironmentIsExplicit() + { + using var directory = new TestDirectory(); + var sourceArchivePath = IOPath.Combine(directory.Path, "products.zip"); + var stagingArchivePath = IOPath.Combine(directory.Path, "staging.far"); + var productionArchivePath = IOPath.Combine(directory.Path, "production.far"); + await CreateSourceArchiveAsync(sourceArchivePath); + var sourceArchives = + new[] { new SourceSchemaArchiveInfo("Products", sourceArchivePath) }; + var compositionSettings = new GraphQLCompositionSettings + { + EnvironmentName = "Aspire" + }; + + var stagingSuccess = await AspireCompositionHelper.TryComposeArchivesAsync( + stagingArchivePath, + sourceArchives, + "Staging", + compositionSettings, + NullLogger.Instance, + TestContext.Current.CancellationToken); + var productionSuccess = await AspireCompositionHelper.TryComposeArchivesAsync( + productionArchivePath, + sourceArchives, + "Production", + compositionSettings, + NullLogger.Instance, + TestContext.Current.CancellationToken); + + Assert.True(stagingSuccess); + Assert.True(productionSuccess); + var stagingSettings = await ReadGatewaySettingsAsync(stagingArchivePath); + var productionSettings = await ReadGatewaySettingsAsync(productionArchivePath); + string.Join( + Environment.NewLine, + "## Staging", + stagingSettings, + "", + "## Production", + productionSettings) + .MatchInlineSnapshot( + """ + ## Staging + { + "sourceSchemas": { + "Products": { + "transports": { + "http": { + "url": "https://staging.products.example.com/graphql", + "capabilities": { + "subscriptions": { + "supported": true + } + } + } + }, + "extensions": { + "timeout": 5000, + "label": "staging-green" + } + } + } + } + + ## Production + { + "sourceSchemas": { + "Products": { + "transports": { + "http": { + "url": "https://products.example.com/graphql", + "capabilities": { + "subscriptions": { + "supported": false + } + } + } + }, + "extensions": { + "timeout": 10000, + "label": "production-blue" + } + } + } + } + """); + } + + [Theory] + [InlineData("staging")] + [InlineData("Preview")] + public async Task TryComposeArchivesAsync_Should_Fail_WhenEnvironmentDoesNotProvideVariables( + string environmentName) + { + using var directory = new TestDirectory(); + var sourceArchivePath = IOPath.Combine(directory.Path, "products.zip"); + var fusionArchivePath = IOPath.Combine(directory.Path, "gateway.far"); + await CreateSourceArchiveAsync(sourceArchivePath); + + var exception = await Assert.ThrowsAsync( + () => AspireCompositionHelper.TryComposeArchivesAsync( + fusionArchivePath, + [new SourceSchemaArchiveInfo("Products", sourceArchivePath)], + environmentName, + default, + NullLogger.Instance, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "Variable 'BASE_URL' not found in environment", + exception.Message); + } + + [Fact] + public void ResolveSourceSchemaSettings_Should_RemoveEnvironmentMap_WhenEnvironmentIsResolved() + { + using var sourceSettings = CreateSourceSettings(); + using var resolved = AspireCompositionHelper.ResolveSourceSchemaSettings( + sourceSettings, + "Staging"); + + JsonSerializer.Serialize( + resolved.RootElement, + new JsonSerializerOptions { WriteIndented = true }) + .MatchInlineSnapshot( + """ + { + "transports": { + "http": { + "url": "https://staging.products.example.com/graphql", + "capabilities": { + "subscriptions": { + "supported": true + } + } + } + }, + "extensions": { + "timeout": 5000, + "label": "staging-green" + } + } + """); + } + [Theory] [InlineData(null)] [InlineData(NodeResolution.Gateway)] @@ -236,4 +385,100 @@ type Product @key(fields: "id") @extends { } } } + + private static async Task CreateSourceArchiveAsync(string archivePath) + { + using var settings = CreateSourceSettings(); + using var archive = FusionSourceSchemaArchive.Create(archivePath); + await archive.SetArchiveMetadataAsync( + new HotChocolate.Fusion.SourceSchema.Packaging.ArchiveMetadata(), + TestContext.Current.CancellationToken); + await archive.SetSchemaAsync( + Encoding.UTF8.GetBytes( + """ + type Query { + product: Product + } + + type Product { + id: ID! + name: String! + } + """), + TestContext.Current.CancellationToken); + await archive.SetSettingsAsync( + settings, + TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + private static JsonDocument CreateSourceSettings() + => JsonDocument.Parse( + """ + { + "name": "Products", + "transports": { + "http": { + "url": "{{BASE_URL}}/graphql", + "capabilities": { + "subscriptions": { + "supported": "{{SUBSCRIPTIONS_ENABLED}}" + } + } + } + }, + "extensions": { + "timeout": "{{TIMEOUT}}", + "label": "{{ENVIRONMENT}}-{{COLOR}}" + }, + "environments": { + "Staging": { + "BASE_URL": "https://staging.products.example.com", + "SUBSCRIPTIONS_ENABLED": true, + "TIMEOUT": 5000, + "ENVIRONMENT": "staging", + "COLOR": "green" + }, + "Production": { + "BASE_URL": "https://products.example.com", + "SUBSCRIPTIONS_ENABLED": false, + "TIMEOUT": 10000, + "ENVIRONMENT": "production", + "COLOR": "blue" + } + } + } + """); + + private static async Task ReadGatewaySettingsAsync( + string fusionArchivePath) + { + using var archive = FusionArchive.Open(fusionArchivePath); + using var configuration = await archive.TryGetGatewayConfigurationAsync( + new Version(99, 0), + TestContext.Current.CancellationToken); + Assert.NotNull(configuration); + return JsonSerializer.Serialize( + configuration.Settings.RootElement, + new JsonSerializerOptions { WriteIndented = true }); + } + + private sealed class TestDirectory : IDisposable + { + public TestDirectory() + { + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "fusion-aspire-tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + Directory.Delete(Path, recursive: true); + } + } } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs index 8527f9d0b37..629be45a00e 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs @@ -16,10 +16,14 @@ public sealed class FusionDeploymentResource( internal string? StageName { get; set; } + internal string? CompositionEnvironmentName { get; set; } + internal string? ConfigurationTag { get; set; } internal ParameterResource? ConfigurationTagParameter { get; set; } + internal ParameterResource? FusionReleaseManifestParameter { get; set; } + internal bool UseGitCommitAsSourceVersion { get; set; } internal bool WaitForApproval { get; set; } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs index 028d46d681b..ba0449883cd 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs @@ -12,6 +12,8 @@ namespace ChilliCream.Nitro.Aspire; internal static class FusionPipeline { internal const string ArtifactsStepName = "fusion-artifacts"; + internal const string PrepareReleaseStepName = "fusion-release-prepare"; + internal const string ComposeReleaseStepName = "fusion-compose"; internal const string ReadinessStepName = "fusion-readiness"; internal const string UploadStepName = "fusion-upload"; internal const string PublishStepName = "fusion-publish"; @@ -64,6 +66,45 @@ internal static IReadOnlyList SelectDeployments( return deployments; } + internal static IReadOnlyList + GetManifestDeployments(DistributedApplicationModel model) + { + var deployments = model.Resources + .OfType() + .Where(deployment => + deployment.FusionReleaseManifestParameter is not null) + .ToArray(); + + foreach (var deployment in deployments) + { + ValidateDeclaration(deployment); + } + + return deployments; + } + + internal static bool ShouldUseManifestProducer( + DistributedApplicationModel model, + string environmentName) + { + var selected = SelectDeployments(model, environmentName); + if (selected.Count > 0) + { + var manifestCount = selected.Count(deployment => + deployment.FusionReleaseManifestParameter is not null); + if (manifestCount > 0 && manifestCount != selected.Count) + { + throw new InvalidOperationException( + $"Aspire environment '{environmentName}' mixes " + + "promoted-manifest and legacy Fusion deployments."); + } + + return manifestCount > 0; + } + + return GetManifestDeployments(model).Count > 0; + } + internal static IResourceWithEndpoints GetCompositionResource( DistributedApplicationModel model) { @@ -91,19 +132,45 @@ private static IEnumerable CreateSteps( topology.EnvironmentName = environment; topology.HasDeployments = deployments.Count > 0; + topology.BuildOnlyManifestProducer = deployments.Count == 0 + && GetManifestDeployments( + context.PipelineContext.Model).Count > 0; + topology.UseManifestApply = deployments.Count > 0 + && deployments.All( + deployment => + deployment.FusionReleaseManifestParameter is not null); + + if (deployments.Any( + deployment => + deployment.FusionReleaseManifestParameter is not null) + && !topology.UseManifestApply) + { + throw new InvalidOperationException( + $"Aspire environment '{environment}' mixes promoted-manifest " + + "and legacy Fusion deployments."); + } return CreateStepDefinitions(context.Resource, topology); } internal static PipelineStep[] CreateStepDefinitionsForTest( - IResource resource) - => CreateStepDefinitions(resource, new FusionPipelineTopology()); + IResource resource, + bool useManifestApply = false, + bool buildOnlyManifestProducer = false) + => CreateStepDefinitions( + resource, + new FusionPipelineTopology + { + UseManifestApply = useManifestApply, + BuildOnlyManifestProducer = buildOnlyManifestProducer + }); private static PipelineStep[] CreateStepDefinitions( IResource resource, FusionPipelineTopology topology) - => - [ + { + var buildSteps = new[] + { new PipelineStep { Name = ArtifactsStepName, @@ -114,19 +181,87 @@ private static PipelineStep[] CreateStepDefinitions( }, new PipelineStep { - Name = ReadinessStepName, - Description = "Verify deployed Fusion source services are ready.", + Name = UploadStepName, + Description = "Reconcile immutable Fusion source schema versions.", Resource = resource, DependsOnSteps = [ArtifactsStepName], - Action = stepContext => ExecuteReadinessAsync(stepContext, topology) - }, + Action = ExecuteUploadAsync + } + }; + + if (topology.UseManifestApply) + { + return + [ + .. buildSteps, + new PipelineStep + { + Name = PrepareReleaseStepName, + Description = "Download and verify a promoted Fusion release.", + Resource = resource, + Action = ExecutePrepareReleaseAsync + }, + new PipelineStep + { + Name = ComposeReleaseStepName, + Description = "Compose the promoted Fusion release for this environment.", + Resource = resource, + DependsOnSteps = [PrepareReleaseStepName], + Action = ExecuteComposeReleaseAsync + }, + new PipelineStep + { + Name = ReadinessStepName, + Description = "Verify deployed Fusion source services are ready.", + Resource = resource, + DependsOnSteps = [ComposeReleaseStepName], + Action = stepContext => ExecuteReadinessAsync(stepContext, topology) + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Publish the promoted Fusion configuration to Nitro.", + Resource = resource, + DependsOnSteps = [ReadinessStepName], + RequiredBySteps = [WellKnownPipelineSteps.Deploy], + Action = ExecutePublishAsync + } + ]; + } + + if (topology.BuildOnlyManifestProducer) + { + return + [ + .. buildSteps, + new PipelineStep + { + Name = ReadinessStepName, + Description = "Verify deployed Fusion source services are ready.", + Resource = resource, + Action = stepContext => ExecuteReadinessAsync(stepContext, topology) + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Publish a Fusion configuration to Nitro.", + Resource = resource, + DependsOnSteps = [ReadinessStepName], + Action = ExecutePublishAsync + } + ]; + } + + return + [ + .. buildSteps, new PipelineStep { - Name = UploadStepName, - Description = "Reconcile immutable Fusion source schema versions.", + Name = ReadinessStepName, + Description = "Verify deployed Fusion source services are ready.", Resource = resource, DependsOnSteps = [ArtifactsStepName], - Action = ExecuteUploadAsync + Action = stepContext => ExecuteReadinessAsync(stepContext, topology) }, new PipelineStep { @@ -138,6 +273,7 @@ private static PipelineStep[] CreateStepDefinitions( Action = ExecutePublishAsync } ]; + } private static void ConfigureSteps( PipelineConfigurationContext context, @@ -196,6 +332,12 @@ private static Task ExecuteReadinessAsync( private static Task ExecuteUploadAsync(PipelineStepContext context) => GetExecutor(context).UploadAsync(context); + private static Task ExecutePrepareReleaseAsync(PipelineStepContext context) + => GetExecutor(context).PrepareReleaseAsync(context); + + private static Task ExecuteComposeReleaseAsync(PipelineStepContext context) + => GetExecutor(context).ComposeReleaseAsync(context); + private static Task ExecutePublishAsync(PipelineStepContext context) => GetExecutor(context).PublishAsync(context); @@ -235,6 +377,15 @@ private static void ValidateDeclaration( $"Nitro target '{deployment.Nitro.Name}' cloud URL must use HTTPS."); } + if (!string.IsNullOrEmpty(cloudUri.UserInfo) + || cloudUri.AbsolutePath is not "/" + || !string.IsNullOrEmpty(cloudUri.Query) + || !string.IsNullOrEmpty(cloudUri.Fragment)) + { + throw new InvalidOperationException( + $"Nitro target '{deployment.Nitro.Name}' cloud URL must be an origin."); + } + if (string.IsNullOrWhiteSpace(deployment.Nitro.ApiId)) { throw new InvalidOperationException( @@ -255,6 +406,10 @@ private sealed class FusionPipelineTopology public bool HasDeployments { get; set; } + public bool UseManifestApply { get; set; } + + public bool BuildOnlyManifestProducer { get; set; } + public HashSet SourcesWithoutCompute { get; } = new(StringComparer.Ordinal); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs index 759015a5160..fb3155c2ed6 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs @@ -5,7 +5,9 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; using ChilliCream.Nitro.Fusion; +using HotChocolate.Fusion; using HotChocolate.Fusion.Aspire; +using HotChocolate.Fusion.Packaging; using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; @@ -26,6 +28,16 @@ public async Task CreateArtifactsAsync(PipelineStepContext context) var environment = context.Services .GetRequiredService() .EnvironmentName; + if (FusionPipeline.ShouldUseManifestProducer( + context.Model, + environment)) + { + await CreateReleaseArtifactsAsync( + FusionPipeline.GetManifestDeployments(context.Model), + context); + return; + } + var deployments = FusionPipeline.SelectDeployments( context.Model, environment); @@ -74,6 +86,16 @@ public async Task VerifyReadinessAsync(PipelineStepContext context) return; } + if (deployments.All( + deployment => + deployment.FusionReleaseManifestParameter is not null)) + { + await VerifyReleaseReadinessAsync( + deployments, + context); + return; + } + var output = context.Services .GetRequiredService() .GetOutputDirectory(); @@ -81,9 +103,16 @@ public async Task VerifyReadinessAsync(PipelineStepContext context) { Timeout = TimeSpan.FromSeconds(10) }; + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var composition = GraphQLResourceModel.GetComposition( + compositionResource); foreach (var deployment in deployments) { + var compositionEnvironment = ResolveCompositionEnvironment( + deployment, + composition.Settings); var deploymentDirectory = GetDeploymentDirectory(output, deployment); foreach (var sourceDirectory in Directory.EnumerateDirectories( @@ -96,7 +125,11 @@ public async Task VerifyReadinessAsync(PipelineStepContext context) await File.ReadAllTextAsync( settingsPath, context.CancellationToken)); - var endpoint = GetTransportEndpoint(settings); + using var resolvedSettings = + AspireCompositionHelper.ResolveSourceSchemaSettings( + settings, + compositionEnvironment); + var endpoint = GetTransportEndpoint(resolvedSettings); RejectLoopbackEndpoint(endpoint); using var response = await httpClient.GetAsync( @@ -114,6 +147,19 @@ await File.ReadAllTextAsync( public async Task UploadAsync(PipelineStepContext context) { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + if (FusionPipeline.ShouldUseManifestProducer( + context.Model, + environment)) + { + await UploadReleaseAsync( + FusionPipeline.GetManifestDeployments(context.Model), + context); + return; + } + var artifacts = await MaterializeArchivesAsync(context); if (artifacts.Count == 0) { @@ -143,8 +189,249 @@ await workflow.ReconcileSourceSchemaAsync( } } + public async Task PrepareReleaseAsync(PipelineStepContext context) + { + var deployments = GetSelectedManifestDeployments(context); + if (deployments.Count == 0) + { + return; + } + + var workflow = context.Services + .GetRequiredService(); + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var composition = FusionPipeline.GetCompositionResource( + context.Model); + var providerSourceNames = GraphQLResourceModel + .GetReferencedSourceSchemas(composition, context.Model) + .Select(source => + source.Declaration.SourceSchemaName + ?? source.Resource.Name) + .ToArray(); + + foreach (var deployment in deployments) + { + var manifestPath = await ResolveManifestPathAsync( + deployment, + context.CancellationToken); + var manifest = await FusionReleaseStore.ReadFinalAsync( + manifestPath, + context.CancellationToken); + FusionReleaseCompatibility.ValidateCompositionToolVersion( + manifest); + var manifestSha256 = await ComputeFileDigestAsync( + manifestPath, + context.CancellationToken); + ValidateManifestSourceNames( + manifest, + providerSourceNames); + await ValidateReleaseIdAsync( + deployment, + manifest, + context.CancellationToken); + GetReleaseTarget(manifest, deployment); + + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var applyDirectory = GetApplyDirectory(output, deployment); + var applyParent = Path.GetDirectoryName(applyDirectory)!; + Directory.CreateDirectory(applyParent); + var temporaryDirectory = Path.Combine( + applyParent, + $".{deployment.Name}.{Guid.NewGuid():N}.tmp"); + + try + { + var sources = new List( + manifest.Sources.Count); + + foreach (var source in manifest.Sources) + { + var download = await workflow.DownloadSourceSchemaAsync( + target, + new FusionSourceSchemaVersion( + source.Name, + source.Version), + source.ContentSha256, + context.CancellationToken) + ?? throw new InvalidOperationException( + $"Promoted Fusion source '{source.Name}' version " + + $"'{source.Version}' does not exist on target " + + $"'{deployment.Nitro.ApiId}'."); + var relativePath = Path.Combine( + "sources", + source.Name, + $"{source.Version}.zip"); + var archivePath = Path.Combine( + temporaryDirectory, + relativePath); + Directory.CreateDirectory( + Path.GetDirectoryName(archivePath)!); + await File.WriteAllBytesAsync( + archivePath, + download.Archive, + context.CancellationToken); + sources.Add( + new FusionReleaseApplySource( + source.Name, + source.Version, + relativePath.Replace( + Path.DirectorySeparatorChar, + '/'), + download.ContentSha256)); + } + + await WriteJsonAtomicallyAsync( + Path.Combine( + temporaryDirectory, + "fusion-apply.json"), + new FusionReleaseApplyState( + manifestPath, + manifestSha256, + manifest.ReleaseId, + deployment.Nitro.CloudUrl!, + deployment.Nitro.ApiId!, + CompositionEnvironment: null, + FusionArchivePath: null, + FusionArchiveSha256: null, + sources), + context.CancellationToken); + + ReplaceDirectoryAtomically( + temporaryDirectory, + applyDirectory); + } + finally + { + DeleteDirectoryBestEffort(temporaryDirectory); + } + } + } + + public async Task ComposeReleaseAsync(PipelineStepContext context) + { + var deployments = GetSelectedManifestDeployments(context); + if (deployments.Count == 0) + { + return; + } + + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var currentComposition = GraphQLResourceModel.GetComposition( + compositionResource); + + foreach (var deployment in deployments) + { + var applyDirectory = GetApplyDirectory(output, deployment); + var state = await ReadApplyStateAsync( + applyDirectory, + context.CancellationToken); + await VerifyFileDigestAsync( + state.ManifestPath, + state.ManifestSha256, + "promoted Fusion release manifest", + context.CancellationToken); + var manifest = await FusionReleaseStore.ReadFinalAsync( + state.ManifestPath, + context.CancellationToken); + ValidateApplyState(state, manifest, deployment); + + var compositionEnvironment = ResolveCompositionEnvironment( + deployment, + currentComposition.Settings); + var farPath = Path.Combine( + applyDirectory, + "fusion-configuration.far"); + var settings = manifest.Composition.Settings + .ToCompositionSettings(compositionEnvironment); + + foreach (var source in state.Sources) + { + var archivePath = ResolveApplyPath( + applyDirectory, + source.ArchivePath); + var contentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + archivePath, + source.Name, + context.CancellationToken); + if (!string.Equals( + contentSha256, + source.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Prepared Fusion source '{source.Name}' content changed after download."); + } + } + + if (File.Exists(farPath)) + { + File.Delete(farPath); + } + + var logger = context.Services + .GetRequiredService>(); + if (!await AspireCompositionHelper.TryComposeArchivesAsync( + farPath, + state.Sources.Select( + source => new SourceSchemaArchiveInfo( + source.Name, + ResolveApplyPath( + applyDirectory, + source.ArchivePath))) + .ToArray(), + compositionEnvironment, + settings, + logger, + context.CancellationToken)) + { + throw new InvalidOperationException( + "Fusion configuration composition failed."); + } + + await WriteJsonAtomicallyAsync( + Path.Combine(applyDirectory, "fusion-apply.json"), + state with + { + CompositionEnvironment = compositionEnvironment, + FusionArchivePath = Path.GetRelativePath( + applyDirectory, + farPath) + .Replace(Path.DirectorySeparatorChar, '/'), + FusionArchiveSha256 = await ComputeFileDigestAsync( + farPath, + context.CancellationToken) + }, + context.CancellationToken); + } + } + public async Task PublishAsync(PipelineStepContext context) { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + if (deployments.Count > 0 + && deployments.All( + deployment => + deployment.FusionReleaseManifestParameter is not null)) + { + await PublishReleaseAsync(deployments, context); + return; + } + var artifacts = await MaterializeArchivesAsync(context); if (artifacts.Count == 0) { @@ -168,11 +455,16 @@ public async Task PublishAsync(PipelineStepContext context) var farPath = Path.Combine( Path.GetDirectoryName(group.First().ArchivePath)!, $"{releaseId}.far"); + var compositionSettings = composition.Settings; + compositionSettings.EnvironmentName = + ResolveCompositionEnvironment( + deployment, + compositionSettings); await ComposeAsync( farPath, group.ToArray(), - composition.Settings, + compositionSettings, context, context.CancellationToken); @@ -203,6 +495,388 @@ await WriteDeploymentManifestAsync( } } + private static async Task CreateReleaseArtifactsAsync( + IReadOnlyList deployments, + PipelineStepContext context) + { + var releaseId = await ResolveSharedReleaseIdAsync( + deployments, + context.CancellationToken); + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var composition = GraphQLResourceModel.GetComposition( + compositionResource); + var sources = GraphQLResourceModel.GetReferencedSourceSchemas( + compositionResource, + context.Model); + + if (sources.Count == 0) + { + throw new InvalidOperationException( + $"Fusion composition resource '{compositionResource.Name}' " + + "has no declared source schemas."); + } + + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var releaseDirectory = GetReleaseDirectory(output, releaseId); + var finalManifestPath = Path.Combine( + releaseDirectory, + "fusion-release.json"); + if (File.Exists(finalManifestPath)) + { + var existing = await FusionReleaseStore.ReadFinalAsync( + Path.GetFullPath(finalManifestPath), + context.CancellationToken); + if (!string.Equals( + existing.ReleaseId, + releaseId, + StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"Existing Fusion release '{existing.ReleaseId}' does not " + + $"match requested release '{releaseId}'."); + } + + return; + } + + var releaseParent = Path.GetDirectoryName(releaseDirectory)!; + Directory.CreateDirectory(releaseParent); + var temporaryDirectory = Path.Combine( + releaseParent, + $".{releaseId}.{Guid.NewGuid():N}.tmp"); + + try + { + var inputsDirectory = Path.Combine( + temporaryDirectory, + ".inputs"); + Directory.CreateDirectory(inputsDirectory); + var releaseSources = new List( + sources.Count); + + foreach (var source in sources) + { + var name = await CreateSourceArtifactsAsync( + source, + inputsDirectory, + context.CancellationToken); + var sourceDirectory = Path.Combine( + inputsDirectory, + name); + var archiveRelativePath = Path.Combine( + "sources", + name, + $"{releaseId}.zip") + .Replace(Path.DirectorySeparatorChar, '/'); + var archivePath = Path.Combine( + temporaryDirectory, + archiveRelativePath.Replace( + '/', + Path.DirectorySeparatorChar)); + Directory.CreateDirectory( + Path.GetDirectoryName(archivePath)!); + using var settings = JsonDocument.Parse( + await File.ReadAllTextAsync( + Path.Combine( + sourceDirectory, + "schema-settings.template.json"), + context.CancellationToken)); + ValidateSettingsName(name, settings); + await CreateArchiveAsync( + archivePath, + await File.ReadAllBytesAsync( + Path.Combine(sourceDirectory, "schema.graphqls"), + context.CancellationToken), + settings, + GetExtensionsPath(sourceDirectory), + context.CancellationToken); + + releaseSources.Add( + new FusionReleaseSource( + name, + releaseId, + archiveRelativePath, + await ComputeFileDigestAsync( + archivePath, + context.CancellationToken), + await FusionSourceSchemaContent.ComputeSha256Async( + archivePath, + name, + context.CancellationToken))); + } + + Directory.Delete(inputsDirectory, recursive: true); + releaseSources.Sort( + (left, right) => StringComparer.Ordinal.Compare( + left.Name, + right.Name)); + var compositionSettings = + FusionReleaseCompositionSettings.From( + composition.Settings); + var manifest = new FusionReleaseManifest( + FusionReleaseManifest.CurrentFormatVersion, + releaseId, + FusionReleaseCompatibility.CompositionToolVersion, + FusionReleaseDigests.ComputeSourceSetSha256( + releaseSources), + new FusionReleaseComposition( + FusionReleaseDigests.ComputeCompositionSha256( + compositionSettings), + compositionSettings), + releaseSources, + Targets: []); + + await FusionReleaseStore.WriteDraftAsync( + temporaryDirectory, + manifest, + context.CancellationToken); + ReplaceDirectoryAtomically( + temporaryDirectory, + releaseDirectory); + } + finally + { + DeleteDirectoryBestEffort(temporaryDirectory); + } + } + + private static async Task UploadReleaseAsync( + IReadOnlyList deployments, + PipelineStepContext context) + { + var releaseId = await ResolveSharedReleaseIdAsync( + deployments, + context.CancellationToken); + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var releaseDirectory = GetReleaseDirectory(output, releaseId); + var finalManifestPath = Path.Combine( + releaseDirectory, + "fusion-release.json"); + var draft = File.Exists(finalManifestPath) + ? await FusionReleaseStore.ReadFinalAsync( + Path.GetFullPath(finalManifestPath), + context.CancellationToken) + : await FusionReleaseStore.ReadDraftAsync( + releaseDirectory, + context.CancellationToken); + ValidateReleaseManifestId(draft, releaseId); + var workflow = context.Services + .GetRequiredService(); + var targets = new List(); + var distinctDeployments = deployments + .DistinctBy( + deployment => ( + deployment.Nitro.CloudUrl!.TrimEnd('/').ToUpperInvariant(), + deployment.Nitro.ApiId), + EqualityComparer<(string, string?)>.Default) + .OrderBy( + deployment => deployment.Nitro.CloudUrl, + StringComparer.OrdinalIgnoreCase) + .ThenBy( + deployment => deployment.Nitro.ApiId, + StringComparer.Ordinal) + .ToArray(); + + if (File.Exists(finalManifestPath) + && !TargetsMatch(draft.Targets, distinctDeployments)) + { + throw new InvalidOperationException( + $"Existing Fusion release '{releaseId}' target set does not " + + "match the declared Nitro targets."); + } + + foreach (var deployment in distinctDeployments) + { + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + + foreach (var source in draft.Sources) + { + var archivePath = FusionReleaseStore.ResolveArchivePath( + Path.Combine( + releaseDirectory, + "fusion-release.draft.json"), + source); + await FusionReleaseStore.VerifyArchiveAsync( + archivePath, + source, + context.CancellationToken); + await workflow.ReconcileSourceSchemaAsync( + target, + new FusionSourceSchemaUpload( + source.Name, + source.Version, + archivePath, + source.ArchiveSha256), + context.CancellationToken); + } + + targets.Add( + new FusionReleaseTarget( + deployment.Nitro.CloudUrl!, + deployment.Nitro.ApiId!, + draft.SourceSetSha256, + draft.Sources.Select( + source => + new FusionReleaseSourceReference( + source.Name, + source.Version, + source.ContentSha256)) + .ToArray())); + } + + await FusionReleaseStore.WriteFinalAsync( + releaseDirectory, + draft with { Targets = targets }, + context.CancellationToken); + } + + private static async Task VerifyReleaseReadinessAsync( + IReadOnlyList deployments, + PipelineStepContext context) + { + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + using var httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(10) + }; + + foreach (var deployment in deployments) + { + var applyDirectory = GetApplyDirectory(output, deployment); + var state = await ReadApplyStateAsync( + applyDirectory, + context.CancellationToken); + await VerifyFileDigestAsync( + state.ManifestPath, + state.ManifestSha256, + "promoted Fusion release manifest", + context.CancellationToken); + var farPath = ResolveApplyPath( + applyDirectory, + state.FusionArchivePath + ?? throw new InvalidOperationException( + "The promoted Fusion release has not been composed.")); + await VerifyFileDigestAsync( + farPath, + state.FusionArchiveSha256 + ?? throw new InvalidOperationException( + "The promoted Fusion release has no composed archive digest."), + "composed Fusion archive", + context.CancellationToken); + using var archive = FusionArchive.Open(farPath); + using var configuration = + await archive.TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + context.CancellationToken) + ?? throw new InvalidDataException( + "The composed Fusion archive contains no gateway configuration."); + + foreach (var (name, endpoint) in GetTransportEndpoints( + configuration.Settings)) + { + RejectLoopbackEndpoint(endpoint); + using var response = await httpClient.GetAsync( + endpoint, + context.CancellationToken); + if ((int)response.StatusCode + >= (int)HttpStatusCode.InternalServerError) + { + throw new InvalidOperationException( + $"Fusion source '{name}' did not pass its production readiness check."); + } + } + } + } + + private static async Task PublishReleaseAsync( + IReadOnlyList deployments, + PipelineStepContext context) + { + var output = context.Services + .GetRequiredService() + .GetOutputDirectory(); + var workflow = context.Services + .GetRequiredService(); + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var currentComposition = GraphQLResourceModel.GetComposition( + compositionResource); + + foreach (var deployment in deployments) + { + var applyDirectory = GetApplyDirectory(output, deployment); + var state = await ReadApplyStateAsync( + applyDirectory, + context.CancellationToken); + await VerifyFileDigestAsync( + state.ManifestPath, + state.ManifestSha256, + "promoted Fusion release manifest", + context.CancellationToken); + var manifest = await FusionReleaseStore.ReadFinalAsync( + state.ManifestPath, + context.CancellationToken); + ValidateApplyState(state, manifest, deployment); + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var farPath = ResolveApplyPath( + applyDirectory, + state.FusionArchivePath + ?? throw new InvalidOperationException( + "The promoted Fusion release has not been composed.")); + var expectedEnvironment = ResolveCompositionEnvironment( + deployment, + currentComposition.Settings); + if (!string.Equals( + state.CompositionEnvironment, + expectedEnvironment, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + "The composed Fusion archive environment does not match " + + $"deployment '{deployment.Name}'."); + } + + await VerifyFileDigestAsync( + farPath, + state.FusionArchiveSha256 + ?? throw new InvalidOperationException( + "The promoted Fusion release has no composed archive digest."), + "composed Fusion archive", + context.CancellationToken); + + await workflow.PublishAsync( + new FusionPublicationRequest( + target, + deployment.StageName!, + manifest.ReleaseId, + manifest.Sources.Select( + source => + new FusionSourceSchemaVersion( + source.Name, + source.Version)) + .ToArray(), + deployment.WaitForApproval, + deployment.Force, + deployment.OperationTimeout, + deployment.ApprovalTimeout), + farPath, + context.CancellationToken); + } + } + internal async Task> MaterializeArchivesAsync( PipelineStepContext context) { @@ -222,9 +896,16 @@ internal async Task> MaterializeArchivesAsyn .GetRequiredService() .GetOutputDirectory(); var artifacts = new List(); + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var composition = GraphQLResourceModel.GetComposition( + compositionResource); foreach (var deployment in deployments) { + var compositionEnvironment = ResolveCompositionEnvironment( + deployment, + composition.Settings); var releaseId = await ResolveConfigurationTagAsync( deployment, context.CancellationToken); @@ -257,7 +938,11 @@ await File.ReadAllTextAsync( ValidateSettingsName(name, settings); - var endpoint = GetTransportEndpoint(settings); + using var resolvedSettings = + AspireCompositionHelper.ResolveSourceSchemaSettings( + settings, + compositionEnvironment); + var endpoint = GetTransportEndpoint(resolvedSettings); RejectLoopbackEndpoint(endpoint); var archivePath = Path.Combine( @@ -606,6 +1291,325 @@ private static async Task ComposeAsync( } } + private static IReadOnlyList + GetSelectedManifestDeployments(PipelineStepContext context) + { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + + if (deployments.Any( + deployment => + deployment.FusionReleaseManifestParameter is null)) + { + throw new InvalidOperationException( + $"Aspire environment '{environment}' does not exclusively use " + + "promoted Fusion release manifests."); + } + + return deployments; + } + + private static async Task ResolveSharedReleaseIdAsync( + IReadOnlyList deployments, + CancellationToken cancellationToken) + { + var releaseIds = new HashSet(StringComparer.Ordinal); + foreach (var deployment in deployments) + { + releaseIds.Add( + await ResolveConfigurationTagAsync( + deployment, + cancellationToken)); + } + + if (releaseIds.Count is not 1) + { + throw new InvalidOperationException( + "All promoted-manifest Fusion deployments must use the same release ID."); + } + + return releaseIds.Single(); + } + + private static async Task ResolveManifestPathAsync( + FusionDeploymentResource deployment, + CancellationToken cancellationToken) + { + var parameter = deployment.FusionReleaseManifestParameter + ?? throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' has no release manifest parameter."); + var value = await parameter.GetValueAsync(cancellationToken); + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' release manifest path resolved to an empty value."); + } + + if (!Path.IsPathFullyQualified(value)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' release manifest path must be absolute."); + } + + return Path.GetFullPath(value); + } + + internal static string ResolveCompositionEnvironment( + FusionDeploymentResource deployment, + GraphQLCompositionSettings settings) + => deployment.CompositionEnvironmentName + ?? settings.EnvironmentName + ?? deployment.StageName + ?? throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' has no composition environment."); + + internal static JsonDocument ResolveSourceSchemaSettings( + JsonDocument settings, + string environmentName) + => AspireCompositionHelper.ResolveSourceSchemaSettings( + settings, + environmentName); + + internal static void ValidateManifestSourceNames( + FusionReleaseManifest manifest, + IReadOnlyList providerSourceNames) + { + var duplicateProvider = providerSourceNames + .GroupBy(name => name, StringComparer.Ordinal) + .FirstOrDefault(group => group.Count() > 1); + if (duplicateProvider is not null) + { + throw new InvalidOperationException( + "Multiple provider resources map to promoted Fusion source " + + $"'{duplicateProvider.Key}'."); + } + + var manifestNames = manifest.Sources + .Select(source => source.Name) + .ToHashSet(StringComparer.Ordinal); + var providerNames = providerSourceNames + .ToHashSet(StringComparer.Ordinal); + if (manifestNames.SetEquals(providerNames)) + { + return; + } + + var missingProviders = manifestNames + .Except(providerNames, StringComparer.Ordinal) + .Order(StringComparer.Ordinal); + var unexpectedProviders = providerNames + .Except(manifestNames, StringComparer.Ordinal) + .Order(StringComparer.Ordinal); + throw new InvalidOperationException( + "The promoted Fusion source set does not match the AppHost provider " + + $"resources. Missing providers: [{string.Join(", ", missingProviders)}]. " + + $"Unexpected providers: [{string.Join(", ", unexpectedProviders)}]."); + } + + internal static void ValidateReleaseManifestId( + FusionReleaseManifest manifest, + string expectedReleaseId) + { + if (!string.Equals( + manifest.ReleaseId, + expectedReleaseId, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Fusion release manifest ID '{manifest.ReleaseId}' does not " + + $"match expected release '{expectedReleaseId}'."); + } + } + + internal static FusionReleaseTarget GetReleaseTarget( + FusionReleaseManifest manifest, + FusionDeploymentResource deployment) + => manifest.Targets.SingleOrDefault(target => + string.Equals( + target.CloudUrl.TrimEnd('/'), + deployment.Nitro.CloudUrl!.TrimEnd('/'), + StringComparison.OrdinalIgnoreCase) + && string.Equals( + target.ApiId, + deployment.Nitro.ApiId, + StringComparison.Ordinal)) + ?? throw new InvalidOperationException( + $"Fusion release '{manifest.ReleaseId}' was not uploaded to " + + $"Nitro API '{deployment.Nitro.ApiId}' at " + + $"'{deployment.Nitro.CloudUrl}'."); + + private static bool TargetsMatch( + IReadOnlyList targets, + IReadOnlyList deployments) + => targets.Count == deployments.Count + && deployments.All(deployment => + targets.Any(target => + string.Equals( + target.CloudUrl.TrimEnd('/'), + deployment.Nitro.CloudUrl!.TrimEnd('/'), + StringComparison.OrdinalIgnoreCase) + && string.Equals( + target.ApiId, + deployment.Nitro.ApiId, + StringComparison.Ordinal))); + + private static async Task ValidateReleaseIdAsync( + FusionDeploymentResource deployment, + FusionReleaseManifest manifest, + CancellationToken cancellationToken) + { + var configuredReleaseId = await ResolveConfigurationTagAsync( + deployment, + cancellationToken); + if (!string.Equals( + configuredReleaseId, + manifest.ReleaseId, + StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' selected release " + + $"'{configuredReleaseId}', but the promoted manifest contains " + + $"release '{manifest.ReleaseId}'."); + } + } + + private static void ValidateApplyState( + FusionReleaseApplyState state, + FusionReleaseManifest manifest, + FusionDeploymentResource deployment) + { + if (!string.Equals( + state.ReleaseId, + manifest.ReleaseId, + StringComparison.Ordinal) + || !string.Equals( + state.CloudUrl.TrimEnd('/'), + deployment.Nitro.CloudUrl!.TrimEnd('/'), + StringComparison.OrdinalIgnoreCase) + || !string.Equals( + state.ApiId, + deployment.Nitro.ApiId, + StringComparison.Ordinal) + || state.Sources.Count != manifest.Sources.Count) + { + throw new InvalidDataException( + "Prepared Fusion release state for deployment " + + $"'{deployment.Name}' does not match the promoted manifest."); + } + + GetReleaseTarget(manifest, deployment); + foreach (var source in manifest.Sources) + { + var prepared = state.Sources.SingleOrDefault(candidate => + string.Equals( + candidate.Name, + source.Name, + StringComparison.Ordinal)); + if (prepared is null + || !string.Equals( + prepared.Version, + source.Version, + StringComparison.Ordinal) + || !string.Equals( + prepared.ContentSha256, + source.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Prepared Fusion source '{source.Name}' does not match the promoted manifest."); + } + } + } + + private static async Task ReadApplyStateAsync( + string applyDirectory, + CancellationToken cancellationToken) + { + var path = Path.Combine( + applyDirectory, + "fusion-apply.json"); + if (!File.Exists(path)) + { + throw new FileNotFoundException( + "The promoted Fusion release has not been prepared.", + path); + } + + await using var stream = File.OpenRead(path); + return await JsonSerializer.DeserializeAsync( + stream, + FusionReleaseStore.SerializerOptions, + cancellationToken) + ?? throw new InvalidDataException( + "The promoted Fusion apply state is empty."); + } + + private static string ResolveApplyPath( + string applyDirectory, + string relativePath) + { + if (Path.IsPathFullyQualified(relativePath)) + { + throw new InvalidDataException( + "A promoted Fusion apply path must be relative."); + } + + var fullApplyDirectory = Path.GetFullPath(applyDirectory); + var path = Path.GetFullPath(relativePath, fullApplyDirectory); + var prefix = fullApplyDirectory.EndsWith( + Path.DirectorySeparatorChar) + ? fullApplyDirectory + : fullApplyDirectory + Path.DirectorySeparatorChar; + if (!path.StartsWith(prefix, StringComparison.Ordinal)) + { + throw new InvalidDataException( + "A promoted Fusion apply path escapes the apply output directory."); + } + + return path; + } + + internal static IReadOnlyList<(string Name, Uri Endpoint)> + GetTransportEndpoints(JsonDocument gatewaySettings) + { + if (!gatewaySettings.RootElement.TryGetProperty( + "sourceSchemas", + out var sourceSchemas) + || sourceSchemas.ValueKind is not JsonValueKind.Object) + { + throw new InvalidDataException( + "The Fusion gateway settings contain no source schemas."); + } + + var endpoints = new List<(string Name, Uri Endpoint)>(); + foreach (var sourceSchema in sourceSchemas.EnumerateObject()) + { + if (!sourceSchema.Value.TryGetProperty( + "transports", + out var transports) + || !transports.TryGetProperty("http", out var http) + || !http.TryGetProperty("url", out var url) + || url.ValueKind is not JsonValueKind.String + || !Uri.TryCreate( + url.GetString(), + UriKind.Absolute, + out var endpoint)) + { + throw new InvalidDataException( + $"Fusion gateway settings source '{sourceSchema.Name}' " + + "must specify an absolute transports.http.url."); + } + + endpoints.Add((sourceSchema.Name, endpoint)); + } + + return endpoints; + } + private static async Task ResolveTargetAsync( FusionDeploymentResource deployment, PipelineStepContext context, @@ -797,6 +1801,25 @@ private static async Task ComputeFileDigestAsync( return Convert.ToHexStringLower(digest); } + internal static async Task VerifyFileDigestAsync( + string path, + string expectedSha256, + string description, + CancellationToken cancellationToken) + { + var actualSha256 = await ComputeFileDigestAsync( + path, + cancellationToken); + if (!string.Equals( + actualSha256, + expectedSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"The {description} SHA-256 does not match prepared apply state."); + } + } + private static async Task WriteJsonAtomicallyAsync( string path, T value, @@ -836,6 +1859,16 @@ private static string GetDeploymentDirectory( FusionDeploymentResource deployment) => Path.Combine(output, "fusion", deployment.Name); + private static string GetReleaseDirectory( + string output, + string releaseId) + => Path.Combine(output, "fusion", "releases", releaseId); + + private static string GetApplyDirectory( + string output, + FusionDeploymentResource deployment) + => Path.Combine(output, "fusion", "apply", deployment.Name); + private static void DeleteDirectoryBestEffort(string path) { try @@ -926,5 +1959,22 @@ internal sealed record FusionDeploymentManifestSource( string Archive, string Sha256); +internal sealed record FusionReleaseApplyState( + string ManifestPath, + string ManifestSha256, + string ReleaseId, + string CloudUrl, + string ApiId, + string? CompositionEnvironment, + string? FusionArchivePath, + string? FusionArchiveSha256, + IReadOnlyList Sources); + +internal sealed record FusionReleaseApplySource( + string Name, + string Version, + string ArchivePath, + string ContentSha256); + #pragma warning restore ASPIREPIPELINES004 #pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs new file mode 100644 index 00000000000..70792d25401 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs @@ -0,0 +1,156 @@ +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using HotChocolate.Fusion; +using HotChocolate.Fusion.Aspire; +using HotChocolate.Fusion.Options; + +namespace ChilliCream.Nitro.Aspire; + +internal sealed record FusionReleaseManifest( + int FormatVersion, + string ReleaseId, + string CompositionToolVersion, + string SourceSetSha256, + FusionReleaseComposition Composition, + IReadOnlyList Sources, + IReadOnlyList Targets) +{ + public const int CurrentFormatVersion = 1; +} + +internal static class FusionReleaseCompatibility +{ + public static string CompositionToolVersion { get; } = + GetCompositionToolVersion(); + + public static void ValidateCompositionToolVersion( + FusionReleaseManifest manifest) + { + if (!string.Equals( + manifest.CompositionToolVersion, + CompositionToolVersion, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Fusion release '{manifest.ReleaseId}' was created with " + + $"composition tool version '{manifest.CompositionToolVersion}', " + + $"but apply is running version '{CompositionToolVersion}'."); + } + } + + private static string GetCompositionToolVersion() + { + var assembly = typeof(GraphQLCompositionSettings).Assembly; + return assembly + .GetCustomAttribute() + ?.InformationalVersion + ?? assembly.GetName().Version?.ToString() + ?? throw new InvalidOperationException( + "The Fusion composition tool version could not be determined."); + } +} + +internal sealed record FusionReleaseComposition( + string SettingsSha256, + FusionReleaseCompositionSettings Settings); + +internal sealed record FusionReleaseCompositionSettings( + DirectiveMergeBehavior? CacheControlMergeBehavior, + bool? EnableGlobalObjectIdentification, + NodeResolution? NodeResolution, + DirectiveMergeBehavior? TagMergeBehavior, + bool? IncludeSatisfiabilityPaths, + bool? AllowNonResolvableInterfaceObjects, + ShareableFieldRuntimeTypeRouting? ShareableFieldRuntimeTypeRouting, + IReadOnlyList ExcludeByTag) +{ + public static FusionReleaseCompositionSettings From( + GraphQLCompositionSettings settings) + => new( + settings.CacheControlMergeBehavior, + settings.EnableGlobalObjectIdentification, + settings.NodeResolution, + settings.TagMergeBehavior, + settings.IncludeSatisfiabilityPaths, + settings.AllowNonResolvableInterfaceObjects, + settings.ShareableFieldRuntimeTypeRouting, + settings.ExcludeByTag? + .Order(StringComparer.Ordinal) + .ToArray() + ?? []); + + public GraphQLCompositionSettings ToCompositionSettings( + string environmentName) + => new() + { + CacheControlMergeBehavior = CacheControlMergeBehavior, + EnableGlobalObjectIdentification = EnableGlobalObjectIdentification, + NodeResolution = NodeResolution, + TagMergeBehavior = TagMergeBehavior, + IncludeSatisfiabilityPaths = IncludeSatisfiabilityPaths, + AllowNonResolvableInterfaceObjects = AllowNonResolvableInterfaceObjects, + ShareableFieldRuntimeTypeRouting = ShareableFieldRuntimeTypeRouting, + ExcludeByTag = ExcludeByTag.ToHashSet(StringComparer.Ordinal), + EnvironmentName = environmentName + }; +} + +internal sealed record FusionReleaseSource( + string Name, + string Version, + string ArchivePath, + string ArchiveSha256, + string ContentSha256); + +internal sealed record FusionReleaseTarget( + string CloudUrl, + string ApiId, + string SourceSetSha256, + IReadOnlyList Sources); + +internal sealed record FusionReleaseSourceReference( + string Name, + string Version, + string ContentSha256); + +internal static class FusionReleaseDigests +{ + public static string ComputeCompositionSha256( + FusionReleaseCompositionSettings settings) + => ComputeSha256(JsonSerializer.SerializeToUtf8Bytes( + settings, + FusionReleaseStore.SerializerOptions)); + + public static string ComputeSourceSetSha256( + IEnumerable sources) + { + using var stream = new MemoryStream(); + + foreach (var source in sources.OrderBy( + source => source.Name, + StringComparer.Ordinal)) + { + WriteFramed(stream, source.Name); + WriteFramed(stream, source.Version); + WriteFramed(stream, source.ContentSha256); + } + + return ComputeSha256(stream.GetBuffer().AsSpan(0, (int)stream.Length)); + } + + private static void WriteFramed(Stream stream, string value) + { + var bytes = Encoding.UTF8.GetBytes(value); + Span length = stackalloc byte[sizeof(int)]; + System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian( + length, + bytes.Length); + stream.Write(length); + stream.Write(bytes); + } + + private static string ComputeSha256(ReadOnlySpan value) + => Convert.ToHexStringLower(SHA256.HashData(value)); +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs new file mode 100644 index 00000000000..74cfef79ca9 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs @@ -0,0 +1,475 @@ +using System.Security.Cryptography; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ChilliCream.Nitro.Aspire; + +internal static class FusionReleaseStore +{ + internal static JsonSerializerOptions SerializerOptions { get; } = + CreateSerializerOptions(); + + public static async Task ReadFinalAsync( + string manifestPath, + CancellationToken cancellationToken) + { + if (!Path.IsPathFullyQualified(manifestPath)) + { + throw new InvalidOperationException( + "The Fusion release manifest parameter must resolve to an absolute path."); + } + + if (!File.Exists(manifestPath)) + { + throw new FileNotFoundException( + "The promoted Fusion release manifest was not found.", + manifestPath); + } + + try + { + await using var stream = File.OpenRead(manifestPath); + var manifest = + await JsonSerializer.DeserializeAsync( + stream, + SerializerOptions, + cancellationToken) + ?? throw new InvalidDataException( + "The Fusion release manifest is empty."); + + Validate(manifest, requireTargets: true); + return manifest; + } + catch (JsonException exception) + { + throw new InvalidDataException( + "The Fusion release manifest contains invalid JSON.", + exception); + } + } + + public static async Task ReadDraftAsync( + string releaseDirectory, + CancellationToken cancellationToken) + { + var path = Path.Combine( + releaseDirectory, + "fusion-release.draft.json"); + if (!File.Exists(path)) + { + throw new FileNotFoundException( + "The Fusion release draft manifest was not found.", + path); + } + + try + { + await using var stream = File.OpenRead(path); + var manifest = + await JsonSerializer.DeserializeAsync( + stream, + SerializerOptions, + cancellationToken) + ?? throw new InvalidDataException( + "The Fusion release draft manifest is empty."); + Validate(manifest, requireTargets: false); + return manifest; + } + catch (JsonException exception) + { + throw new InvalidDataException( + "The Fusion release draft manifest contains invalid JSON.", + exception); + } + } + + public static Task WriteDraftAsync( + string releaseDirectory, + FusionReleaseManifest manifest, + CancellationToken cancellationToken) + { + Validate(manifest, requireTargets: false); + return WriteAtomicallyAsync( + Path.Combine(releaseDirectory, "fusion-release.draft.json"), + manifest, + overwrite: true, + cancellationToken); + } + + public static Task WriteFinalAsync( + string releaseDirectory, + FusionReleaseManifest manifest, + CancellationToken cancellationToken) + { + Validate(manifest, requireTargets: true); + return WriteAtomicallyAsync( + Path.Combine(releaseDirectory, "fusion-release.json"), + manifest, + overwrite: false, + cancellationToken); + } + + public static string ResolveArchivePath( + string manifestPath, + FusionReleaseSource source) + { + if (Path.IsPathFullyQualified(source.ArchivePath)) + { + throw new InvalidDataException( + $"Fusion source '{source.Name}' archive path must be relative."); + } + + var manifestDirectory = Path.GetDirectoryName( + Path.GetFullPath(manifestPath))!; + var archivePath = Path.GetFullPath( + source.ArchivePath, + manifestDirectory); + var expectedPrefix = manifestDirectory.EndsWith( + Path.DirectorySeparatorChar) + ? manifestDirectory + : manifestDirectory + Path.DirectorySeparatorChar; + + if (!archivePath.StartsWith( + expectedPrefix, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Fusion source '{source.Name}' archive path escapes the release directory."); + } + + return archivePath; + } + + public static async Task VerifyArchiveAsync( + string archivePath, + FusionReleaseSource source, + CancellationToken cancellationToken) + { + if (!File.Exists(archivePath)) + { + throw new FileNotFoundException( + $"Fusion source '{source.Name}' archive was not found.", + archivePath); + } + + await using var stream = File.OpenRead(archivePath); + var digest = Convert.ToHexStringLower( + await SHA256.HashDataAsync(stream, cancellationToken)); + + if (!string.Equals( + digest, + source.ArchiveSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Fusion source '{source.Name}' archive SHA-256 does not match the release manifest."); + } + } + + public static void Validate( + FusionReleaseManifest manifest, + bool requireTargets) + { + ArgumentNullException.ThrowIfNull(manifest); + + if (manifest.FormatVersion is not FusionReleaseManifest.CurrentFormatVersion) + { + throw new InvalidDataException( + $"Unsupported Fusion release manifest format version '{manifest.FormatVersion}'."); + } + + if (manifest.Composition is null + || manifest.Composition.Settings is null + || manifest.Composition.Settings.ExcludeByTag is null + || manifest.Sources is null + || manifest.Targets is null) + { + throw new InvalidDataException( + "The Fusion release manifest is missing required content."); + } + + ValidatePathSegment(manifest.ReleaseId, "release ID"); + if (string.IsNullOrWhiteSpace(manifest.CompositionToolVersion)) + { + throw new InvalidDataException( + "The Fusion release manifest is missing the composition tool version."); + } + + ValidateSha256(manifest.SourceSetSha256, "source set"); + ValidateSha256( + manifest.Composition.SettingsSha256, + "composition settings"); + + var expectedCompositionDigest = + FusionReleaseDigests.ComputeCompositionSha256( + manifest.Composition.Settings); + if (!string.Equals( + expectedCompositionDigest, + manifest.Composition.SettingsSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + "The Fusion release composition settings digest is invalid."); + } + + if (manifest.Sources.Count == 0) + { + throw new InvalidDataException( + "The Fusion release manifest must contain at least one source."); + } + + if (manifest.Sources.Any(source => + source is null + || string.IsNullOrWhiteSpace(source.Name) + || string.IsNullOrWhiteSpace(source.Version) + || string.IsNullOrWhiteSpace(source.ArchivePath) + || string.IsNullOrWhiteSpace(source.ArchiveSha256) + || string.IsNullOrWhiteSpace(source.ContentSha256))) + { + throw new InvalidDataException( + "The Fusion release manifest contains an invalid source."); + } + + var duplicateSource = manifest.Sources + .GroupBy(source => source.Name, StringComparer.Ordinal) + .FirstOrDefault(group => group.Count() > 1); + if (duplicateSource is not null) + { + throw new InvalidDataException( + $"Fusion release source '{duplicateSource.Key}' is duplicated."); + } + + foreach (var source in manifest.Sources) + { + ValidatePathSegment(source.Name, "source name"); + ValidatePathSegment(source.Version, "source version"); + ValidateSha256( + source.ArchiveSha256, + $"source '{source.Name}' archive"); + ValidateSha256( + source.ContentSha256, + $"source '{source.Name}' content"); + + if (source.ArchivePath.Contains('\\') + || Path.IsPathFullyQualified(source.ArchivePath) + || source.ArchivePath.Split( + [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], + StringSplitOptions.RemoveEmptyEntries) + .Any(segment => segment is "." or "..")) + { + throw new InvalidDataException( + $"Fusion source '{source.Name}' archive path must remain inside the release."); + } + } + + var expectedSourceSetDigest = + FusionReleaseDigests.ComputeSourceSetSha256(manifest.Sources); + if (!string.Equals( + expectedSourceSetDigest, + manifest.SourceSetSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + "The Fusion release source set digest is invalid."); + } + + if (requireTargets && manifest.Targets.Count == 0) + { + throw new InvalidDataException( + "The final Fusion release manifest must contain an uploaded target."); + } + + if (manifest.Targets.Any(target => + target is null + || string.IsNullOrWhiteSpace(target.CloudUrl) + || string.IsNullOrWhiteSpace(target.ApiId) + || string.IsNullOrWhiteSpace(target.SourceSetSha256) + || target.Sources is null + || target.Sources.Any(source => + source is null + || string.IsNullOrWhiteSpace(source.Name) + || string.IsNullOrWhiteSpace(source.Version) + || string.IsNullOrWhiteSpace(source.ContentSha256)))) + { + throw new InvalidDataException( + "The Fusion release manifest contains an invalid target."); + } + + var duplicateTarget = manifest.Targets + .GroupBy( + target => (target.CloudUrl, target.ApiId), + FusionReleaseTargetKeyComparer.Instance) + .FirstOrDefault(group => group.Count() > 1); + if (duplicateTarget is not null) + { + throw new InvalidDataException( + $"Fusion release target '{duplicateTarget.Key.ApiId}' is duplicated."); + } + + foreach (var target in manifest.Targets) + { + if (target is null + || target.Sources is null + || target.Sources.Any(source => source is null)) + { + throw new InvalidDataException( + "The Fusion release manifest contains an invalid target."); + } + + if (!Uri.TryCreate( + target.CloudUrl, + UriKind.Absolute, + out var cloudUri) + || cloudUri.Scheme is not "https" + || !string.IsNullOrEmpty(cloudUri.UserInfo) + || cloudUri.AbsolutePath is not "/" + || !string.IsNullOrEmpty(cloudUri.Query) + || !string.IsNullOrEmpty(cloudUri.Fragment)) + { + throw new InvalidDataException( + $"Fusion release target '{target.ApiId}' cloud URL must be " + + "an absolute HTTPS origin."); + } + + if (string.IsNullOrWhiteSpace(target.ApiId)) + { + throw new InvalidDataException( + "A Fusion release target must specify an API ID."); + } + + if (!string.Equals( + target.SourceSetSha256, + manifest.SourceSetSha256, + StringComparison.OrdinalIgnoreCase) + || target.Sources.Count != manifest.Sources.Count) + { + throw new InvalidDataException( + $"Fusion release target '{target.ApiId}' does not contain the release source set."); + } + + foreach (var source in manifest.Sources) + { + var reference = target.Sources.SingleOrDefault(candidate => + string.Equals(candidate.Name, source.Name, StringComparison.Ordinal)); + if (reference is null + || !string.Equals( + reference.Version, + source.Version, + StringComparison.Ordinal) + || !string.Equals( + reference.ContentSha256, + source.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Fusion release target '{target.ApiId}' source set does not match the release."); + } + } + } + } + + private static async Task WriteAtomicallyAsync( + string path, + FusionReleaseManifest manifest, + bool overwrite, + CancellationToken cancellationToken) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + var bytes = JsonSerializer.SerializeToUtf8Bytes( + manifest, + SerializerOptions); + + if (!overwrite && File.Exists(path)) + { + var current = await File.ReadAllBytesAsync( + path, + cancellationToken); + if (current.AsSpan().SequenceEqual(bytes)) + { + return; + } + + throw new InvalidOperationException( + $"Fusion release manifest '{path}' already exists with different content."); + } + + var temporaryPath = + path + "." + Guid.NewGuid().ToString("N") + ".tmp"; + try + { + await File.WriteAllBytesAsync( + temporaryPath, + bytes, + cancellationToken); + File.Move(temporaryPath, path, overwrite); + } + finally + { + if (File.Exists(temporaryPath)) + { + File.Delete(temporaryPath); + } + } + } + + private static void ValidatePathSegment( + string value, + string description) + { + if (string.IsNullOrWhiteSpace(value) + || value is "." or ".." + || value.Any(character => + !char.IsAsciiLetterOrDigit(character) + && character is not '.' and not '_' and not '-')) + { + throw new InvalidDataException( + $"Fusion {description} '{value}' is not a portable path segment."); + } + } + + private static void ValidateSha256( + string value, + string description) + { + if (string.IsNullOrEmpty(value) + || value.Length is not 64 + || !value.All(Uri.IsHexDigit)) + { + throw new InvalidDataException( + $"Fusion {description} SHA-256 must contain 64 hexadecimal characters."); + } + } + + private static JsonSerializerOptions CreateSerializerOptions() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = false, + WriteIndented = true + }; + options.Converters.Add(new JsonStringEnumConverter()); + return options; + } + + private sealed class FusionReleaseTargetKeyComparer + : IEqualityComparer<(string CloudUrl, string ApiId)> + { + public static FusionReleaseTargetKeyComparer Instance { get; } = new(); + + public bool Equals( + (string CloudUrl, string ApiId) x, + (string CloudUrl, string ApiId) y) + => string.Equals( + x.CloudUrl.TrimEnd('/'), + y.CloudUrl.TrimEnd('/'), + StringComparison.OrdinalIgnoreCase) + && string.Equals(x.ApiId, y.ApiId, StringComparison.Ordinal); + + public int GetHashCode((string CloudUrl, string ApiId) obj) + => HashCode.Combine( + StringComparer.OrdinalIgnoreCase.GetHashCode( + obj.CloudUrl.TrimEnd('/')), + StringComparer.Ordinal.GetHashCode(obj.ApiId)); + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs index 3b3cf938912..d0a9ca7e4dc 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs @@ -12,6 +12,10 @@ internal interface IFusionPipelineExecutor Task UploadAsync(PipelineStepContext context); + Task PrepareReleaseAsync(PipelineStepContext context); + + Task ComposeReleaseAsync(PipelineStepContext context); + Task PublishAsync(PipelineStepContext context); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs index 5a59ce81f6e..5ee4142dae6 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs @@ -128,6 +128,47 @@ public static IResourceBuilder ToStage( return builder; } + /// + /// Selects the exact schema-settings.json environment used for composition. + /// + /// + /// When this is not configured, an environment selected by the GraphQL composition is used, + /// followed by the Nitro stage name. + /// + public static IResourceBuilder WithCompositionEnvironment( + this IResourceBuilder builder, + string environmentName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + builder.Resource.CompositionEnvironmentName = environmentName; + return builder; + } + + /// + /// Uses a promoted Fusion release manifest for this deployment. + /// + /// + /// The parameter must resolve to the absolute path of fusion-release.json. + /// + public static IResourceBuilder WithFusionReleaseManifest( + this IResourceBuilder builder, + IResourceBuilder manifestPath) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(manifestPath); + + if (builder.Resource.UseGitCommitAsSourceVersion) + { + throw new InvalidOperationException( + "A promoted Fusion release cannot rediscover source versions from Git."); + } + + builder.Resource.FusionReleaseManifestParameter = manifestPath.Resource; + return builder; + } + /// /// Sets the immutable release tag. /// @@ -166,6 +207,13 @@ public static IResourceBuilder this IResourceBuilder builder) { ArgumentNullException.ThrowIfNull(builder); + + if (builder.Resource.FusionReleaseManifestParameter is not null) + { + throw new InvalidOperationException( + "A promoted Fusion release cannot rediscover source versions from Git."); + } + builder.Resource.UseGitCommitAsSourceVersion = true; return builder; } @@ -226,13 +274,18 @@ private static void EnsureFusionPipeline(IDistributedApplicationBuilder builder) private static string NormalizeCloudUrl(string cloudUrl) { if (!Uri.TryCreate(cloudUrl, UriKind.Absolute, out var uri) - || uri.Scheme is not "https") + || uri.Scheme is not "https" + || !string.IsNullOrEmpty(uri.UserInfo) + || uri.AbsolutePath is not "/" + || !string.IsNullOrEmpty(uri.Query) + || !string.IsNullOrEmpty(uri.Fragment)) { throw new ArgumentException( - "The Nitro cloud URL must be an absolute HTTPS URL.", + "The Nitro cloud URL must be an absolute HTTPS origin without " + + "a path, query, fragment, or user information.", nameof(cloudUrl)); } - return uri.AbsoluteUri.TrimEnd('/'); + return uri.GetLeftPart(UriPartial.Authority); } } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md index a357409c4a8..360795af791 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md @@ -2,9 +2,13 @@ Publishes Hot Chocolate Fusion configurations through the .NET Aspire deployment pipeline. +See [Publishing Fusion with Aspire](FUSION_PUBLISHING.md) for the command contract, artifact +layout, failure behavior, and CI/CD examples. + ```csharp var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); var releaseId = builder.AddParameter("releaseId"); +var releaseManifest = builder.AddParameter("fusionReleaseManifest"); var nitro = builder.AddNitro("nitro") .WithCloudUrl("https://api.chillicream.com") @@ -14,8 +18,9 @@ var nitro = builder.AddNitro("nitro") nitro.AddFusionDeployment("production") .ForEnvironment("Production") .ToStage("production") + .WithCompositionEnvironment("production") .WithConfigurationTag(releaseId) - .WithDefaultSourceVersionFromGitCommit() + .WithFusionReleaseManifest(releaseManifest) .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -23,7 +28,11 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -`aspire publish` creates portable Fusion artifacts and has no Nitro side effects. Use -`aspire do fusion-upload` to reconcile source versions and `aspire do fusion-publish` to run the -full readiness, composition, and publication graph. A matching `aspire deploy` also requires -`fusion-publish` to finish successfully. +The Fusion `aspire publish` step creates environment-neutral source archives without resolving a +Nitro credential or calling Nitro. Use `aspire do fusion-upload` in the build job to upload the +immutable source versions and finalize a portable release manifest. Deployment jobs pass that exact +manifest through `Parameters__fusionReleaseManifest` and run `aspire do fusion-publish`. Only the +final `fusion-release.json` needs to cross runners; apply downloads exact verified sources from +Nitro and rejects a different Fusion composition-tool version. A matching `aspire deploy` reaches +the same download, verification, environment-specific composition, readiness, and publication +graph. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs index a5ebf1301a5..359a616d298 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs @@ -1,3 +1,6 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Security.Cryptography; using System.Text; using System.Text.Json; using HotChocolate.Fusion.SourceSchema.Packaging; @@ -10,6 +13,9 @@ internal sealed record FusionArchiveContent( string? SchemaExtensions, string Settings) { + private static ReadOnlySpan DigestDomain + => "ChilliCream.Nitro.Fusion.SourceSchemaContent.v1"u8; + public static async Task ReadAsync( string archivePath, string expectedName, @@ -63,6 +69,7 @@ extensions is null } catch (Exception exception) when ( exception is InvalidDataException + or InvalidOperationException or JsonException or SyntaxException) { @@ -72,6 +79,16 @@ or JsonException } } + public string ComputeSha256(CancellationToken cancellationToken) + { + using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + hash.AppendData(DigestDomain); + Append(hash, Schema, cancellationToken); + Append(hash, SchemaExtensions, cancellationToken); + Append(hash, Settings, cancellationToken); + return Convert.ToHexString(hash.GetHashAndReset()); + } + private static string NormalizeGraphQL(ReadOnlyMemory source) => Utf8GraphQLParser.Parse(source.Span).ToString(); @@ -137,4 +154,48 @@ private static void WriteCanonicalJson( $"Unsupported JSON token kind '{value.ValueKind}'."); } } + + private static void Append( + IncrementalHash hash, + string? value, + CancellationToken cancellationToken) + { + Span header = stackalloc byte[9]; + header[0] = value is null ? (byte)0 : (byte)1; + BinaryPrimitives.WriteInt64BigEndian( + header[1..], + value is null ? 0 : Encoding.UTF8.GetByteCount(value)); + hash.AppendData(header); + + if (value is null) + { + return; + } + + var buffer = ArrayPool.Shared.Rent(4096); + try + { + var encoder = Encoding.UTF8.GetEncoder(); + var remaining = value.AsSpan(); + var completed = false; + + while (!completed) + { + cancellationToken.ThrowIfCancellationRequested(); + encoder.Convert( + remaining, + buffer, + flush: true, + out var charsUsed, + out var bytesUsed, + out completed); + hash.AppendData(buffer.AsSpan(0, bytesUsed)); + remaining = remaining[charsUsed..]; + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs index d595f5c818f..225d23a7ed2 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs @@ -7,6 +7,53 @@ internal sealed class FusionDeploymentWorkflow( IFusionDeploymentTransportFactory transportFactory) : IFusionDeploymentWorkflow { + public async Task DownloadSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaVersion source, + string expectedContentSha256, + CancellationToken cancellationToken) + { + ValidateTarget(target); + ValidateSourceVersion(source); + ValidateContentSha256(expectedContentSha256); + + await using var transport = await transportFactory.OpenAsync( + target, + cancellationToken); + var archive = await transport.DownloadSourceSchemaAsync( + source.Name, + source.Version, + cancellationToken); + + if (archive is null) + { + return null; + } + + await using var stream = new MemoryStream(archive, writable: false); + var content = await FusionArchiveContent.ReadAsync( + stream, + source.Name, + cancellationToken); + var contentSha256 = content.ComputeSha256(cancellationToken); + + if (!string.Equals( + contentSha256, + expectedContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new FusionIdentityCollisionException( + $"Source schema '{source.Name}' version '{source.Version}' " + + "was downloaded with a different canonical content SHA-256."); + } + + return new FusionSourceSchemaDownload( + source.Name, + source.Version, + archive, + contentSha256); + } + public async Task ReconcileSourceSchemaAsync( FusionTarget target, FusionSourceSchemaUpload source, @@ -19,6 +66,7 @@ public async Task ReconcileSourceSchemaAsync( source.ArchivePath, source.Name, cancellationToken); + var localContentSha256 = localContent.ComputeSha256(cancellationToken); await VerifySha256Async(source, cancellationToken); await using var transport = await transportFactory.OpenAsync( @@ -33,7 +81,7 @@ public async Task ReconcileSourceSchemaAsync( { await EnsureContentMatchesAsync( source, - localContent, + localContentSha256, remoteArchive, cancellationToken); return; @@ -57,7 +105,7 @@ await EnsureContentMatchesAsync( await ReconcileAfterUncertainUploadAsync( transport, source, - localContent, + localContentSha256, exception, cancellationToken); return; @@ -84,7 +132,7 @@ await ReconcileAfterUncertainUploadAsync( await EnsureContentMatchesAsync( source, - localContent, + localContentSha256, racedArchive, cancellationToken); return; @@ -218,7 +266,7 @@ await WaitForTerminalPublishAsync( private static async Task ReconcileAfterUncertainUploadAsync( IFusionDeploymentTransport transport, FusionSourceSchemaUpload source, - FusionArchiveContent localContent, + string localContentSha256, Exception uploadException, CancellationToken cancellationToken) { @@ -250,14 +298,14 @@ private static async Task ReconcileAfterUncertainUploadAsync( await EnsureContentMatchesAsync( source, - localContent, + localContentSha256, remoteArchive, cancellationToken); } private static async Task EnsureContentMatchesAsync( FusionSourceSchemaUpload source, - FusionArchiveContent localContent, + string localContentSha256, byte[] remoteArchive, CancellationToken cancellationToken) { @@ -267,7 +315,10 @@ private static async Task EnsureContentMatchesAsync( source.Name, cancellationToken); - if (localContent != remoteContent) + if (!string.Equals( + localContentSha256, + remoteContent.ComputeSha256(cancellationToken), + StringComparison.Ordinal)) { throw new FusionIdentityCollisionException( $"Source schema '{source.Name}' version '{source.Version}' " @@ -280,13 +331,7 @@ private static async Task VerifySha256Async( FusionSourceSchemaUpload source, CancellationToken cancellationToken) { - if (source.Sha256.Length is not 64 - || !source.Sha256.All(Uri.IsHexDigit)) - { - throw new FusionDeploymentException( - "The source schema SHA-256 must contain exactly 64 " - + "hexadecimal characters."); - } + ValidateSha256(source.Sha256, "The source schema SHA-256"); await using var stream = File.OpenRead(source.ArchivePath); var actual = Convert.ToHexString( @@ -519,6 +564,32 @@ private static void ValidateSource(FusionSourceSchemaUpload source) } } + private static void ValidateSourceVersion(FusionSourceSchemaVersion source) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Name); + ArgumentException.ThrowIfNullOrWhiteSpace(source.Version); + } + + private static void ValidateContentSha256(string contentSha256) + { + ArgumentException.ThrowIfNullOrWhiteSpace(contentSha256); + ValidateSha256( + contentSha256, + "The source schema canonical content SHA-256"); + } + + private static void ValidateSha256(string sha256, string description) + { + if (sha256.Length is not 64 + || !sha256.All(Uri.IsHexDigit)) + { + throw new FusionDeploymentException( + $"{description} must contain exactly 64 hexadecimal " + + "characters."); + } + } + private static void ValidatePublication( FusionPublicationRequest request, string fusionArchivePath) diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs new file mode 100644 index 00000000000..380deda6e3d --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs @@ -0,0 +1,32 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// Computes the canonical content identity of a Fusion source schema archive. +/// +public static class FusionSourceSchemaContent +{ + /// + /// Computes a SHA-256 over the normalized schema, settings, and extensions. + /// + public static async Task ComputeSha256Async( + string archivePath, + string expectedName, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(archivePath); + ArgumentException.ThrowIfNullOrWhiteSpace(expectedName); + + if (!File.Exists(archivePath)) + { + throw new FileNotFoundException( + "The Fusion source schema archive does not exist.", + archivePath); + } + + var content = await FusionArchiveContent.ReadAsync( + archivePath, + expectedName, + cancellationToken); + return content.ComputeSha256(cancellationToken); + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs new file mode 100644 index 00000000000..306cd8b0ade --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs @@ -0,0 +1,10 @@ +namespace ChilliCream.Nitro.Fusion; + +/// +/// An exact immutable source schema archive downloaded from Nitro. +/// +public sealed record FusionSourceSchemaDownload( + string Name, + string Version, + ReadOnlyMemory Archive, + string ContentSha256); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs index 6e3b40a540a..dfe5481e20b 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs @@ -5,6 +5,15 @@ namespace ChilliCream.Nitro.Fusion; /// public interface IFusionDeploymentWorkflow { + /// + /// Downloads an exact immutable source schema version and verifies its canonical content. + /// + Task DownloadSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaVersion source, + string expectedContentSha256, + CancellationToken cancellationToken); + /// /// Ensures that an immutable source schema version exists with the expected content. /// diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md index 6a6502da3b2..cd489e3195b 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md @@ -12,3 +12,8 @@ var workflow = services.BuildServiceProvider() The public API uses deployment DTOs only. Nitro's generated GraphQL management types remain internal to this package. + +`FusionSourceSchemaContent.ComputeSha256Async` computes the normalized content identity recorded +in a release manifest. `IFusionDeploymentWorkflow.DownloadSourceSchemaAsync` downloads an exact +name and version, validates the archive settings, and verifies that identity before returning the +archive bytes. diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs index 8b787069118..a8d6a284a60 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs @@ -1,7 +1,8 @@ +using System.Text.Json; using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; -using System.Text.Json; +using HotChocolate.Fusion.Aspire; namespace ChilliCream.Nitro.Aspire; @@ -36,6 +37,60 @@ public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() Assert.Equal(["production"], deployments.Select(x => x.Name)); } + [Fact] + public void WithFusionReleaseManifest_Should_ConfigureExactParameterAndCompositionEnvironment() + { + var builder = DistributedApplication.CreateBuilder(); + var manifest = builder.AddParameter("fusionReleaseManifest"); + var deployment = builder + .AddNitro("nitro") + .AddFusionDeployment("production") + .WithFusionReleaseManifest(manifest) + .WithCompositionEnvironment("prod"); + + Assert.Same( + manifest.Resource, + deployment.Resource.FusionReleaseManifestParameter); + Assert.Equal( + "prod", + deployment.Resource.CompositionEnvironmentName); + } + + [Fact] + public void WithDefaultSourceVersionFromGitCommit_Should_Fail_WhenManifestIsConfigured() + { + var builder = DistributedApplication.CreateBuilder(); + var deployment = builder + .AddNitro("nitro") + .AddFusionDeployment("production") + .WithFusionReleaseManifest( + builder.AddParameter("fusionReleaseManifest")); + + var exception = Assert.Throws( + deployment.WithDefaultSourceVersionFromGitCommit); + + Assert.Equal( + "A promoted Fusion release cannot rediscover source versions from Git.", + exception.Message); + } + + [Fact] + public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() + { + var builder = DistributedApplication.CreateBuilder(); + + var exception = Assert.Throws( + () => builder + .AddNitro("nitro") + .WithCloudUrl( + "https://api.chillicream.com/CaseSensitivePath")); + + Assert.Equal( + "The Nitro cloud URL must be an absolute HTTPS origin without " + + "a path, query, fragment, or user information. (Parameter 'cloudUrl')", + exception.Message); + } + [Fact] public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() { @@ -57,6 +112,41 @@ public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() Assert.Empty(deployments); } + [Fact] + public void ShouldUseManifestProducer_Should_PreserveLegacyMode_WhenAnotherEnvironmentUsesManifest() + { + var builder = DistributedApplication.CreateBuilder(); + var nitro = builder + .AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products"); + nitro + .AddFusionDeployment("development") + .ForEnvironment("Development") + .ToStage("development") + .WithConfigurationTag("release-1"); + nitro + .AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithConfigurationTag("release-1") + .WithFusionReleaseManifest( + builder.AddParameter("fusionReleaseManifest")); + var model = new DistributedApplicationModel(builder.Resources); + + string.Join( + Environment.NewLine, + $"Development: {FusionPipeline.ShouldUseManifestProducer(model, "Development")}", + $"Production: {FusionPipeline.ShouldUseManifestProducer(model, "Production")}", + $"Release: {FusionPipeline.ShouldUseManifestProducer(model, "Release")}") + .MatchInlineSnapshot( + """ + Development: False + Production: True + Release: True + """); + } + [Fact] public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() { @@ -101,12 +191,416 @@ public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() .MatchInlineSnapshot( """ fusion-artifacts: depends=[]; requiredBy=[publish] - fusion-readiness: depends=[fusion-artifacts]; requiredBy=[] fusion-upload: depends=[fusion-artifacts]; requiredBy=[] + fusion-readiness: depends=[fusion-artifacts]; requiredBy=[] fusion-publish: depends=[fusion-upload, fusion-readiness]; requiredBy=[deploy] """); } + [Fact] + public void CreateStepDefinitions_Should_IsolateManifestApplyFromBuildSteps() + { + var resource = new FusionPipelineResource("fusion-pipeline"); + + var steps = FusionPipeline.CreateStepDefinitionsForTest( + resource, + useManifestApply: true); + + string.Join( + Environment.NewLine, + steps.Select(step => + $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]; " + + $"requiredBy=[{string.Join(", ", step.RequiredBySteps)}]")) + .MatchInlineSnapshot( + """ + fusion-artifacts: depends=[]; requiredBy=[publish] + fusion-upload: depends=[fusion-artifacts]; requiredBy=[] + fusion-release-prepare: depends=[]; requiredBy=[] + fusion-compose: depends=[fusion-release-prepare]; requiredBy=[] + fusion-readiness: depends=[fusion-compose]; requiredBy=[] + fusion-publish: depends=[fusion-readiness]; requiredBy=[deploy] + """); + } + + [Fact] + public void CreateStepDefinitions_Should_NotReachExportGitOrUpload_WhenApplyingManifest() + { + var steps = FusionPipeline.CreateStepDefinitionsForTest( + new FusionPipelineResource("fusion-pipeline"), + useManifestApply: true); + var stepsByName = steps.ToDictionary( + step => step.Name, + StringComparer.Ordinal); + + string.Join( + Environment.NewLine, + GetTransitiveDependencies( + stepsByName, + FusionPipeline.PublishStepName) + .Order(StringComparer.Ordinal)) + .MatchInlineSnapshot( + """ + fusion-compose + fusion-readiness + fusion-release-prepare + """); + } + + [Fact] + public void CreateStepDefinitions_Should_NotAttachPublishToDeploy_WhenEnvironmentIsBuildOnly() + { + var steps = FusionPipeline.CreateStepDefinitionsForTest( + new FusionPipelineResource("fusion-pipeline"), + buildOnlyManifestProducer: true); + + string.Join( + Environment.NewLine, + steps.Select(step => + $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]; " + + $"requiredBy=[{string.Join(", ", step.RequiredBySteps)}]")) + .MatchInlineSnapshot( + """ + fusion-artifacts: depends=[]; requiredBy=[publish] + fusion-upload: depends=[fusion-artifacts]; requiredBy=[] + fusion-readiness: depends=[]; requiredBy=[] + fusion-publish: depends=[fusion-readiness]; requiredBy=[] + """); + } + + [Fact] + public void ResolveSourceSchemaSettings_Should_UseDifferentEnvironmentOverrides_WhenSourceIsShared() + { + using var sourceSettings = JsonDocument.Parse( + """ + { + "name": "products", + "transports": { + "http": { + "url": "{{PRODUCTS_URL}}/graphql" + } + }, + "environments": { + "staging": { + "PRODUCTS_URL": "https://products.staging.example.com" + }, + "production": { + "PRODUCTS_URL": "https://products.example.com" + } + } + } + """); + + using var staging = + FusionPipelineExecutor.ResolveSourceSchemaSettings( + sourceSettings, + "staging"); + using var production = + FusionPipelineExecutor.ResolveSourceSchemaSettings( + sourceSettings, + "production"); + + string.Join( + Environment.NewLine, + "source: products@release-1", + $"staging: {GetHttpUrl(staging)}", + $"production: {GetHttpUrl(production)}") + .MatchInlineSnapshot( + """ + source: products@release-1 + staging: https://products.staging.example.com/graphql + production: https://products.example.com/graphql + """); + } + + [Fact] + public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() + { + var deployment = new FusionDeploymentResource( + "production", + new NitroResource("nitro")) + { + StageName = "production" + }; + + var environment = + FusionPipelineExecutor.ResolveCompositionEnvironment( + deployment, + new GraphQLCompositionSettings()); + + Assert.Equal("production", environment); + } + + [Fact] + public async Task WriteFinalAsync_Should_PreserveExistingManifest_WhenContentChanges() + { + using var testDirectory = new TestDirectory(); + var manifest = CreateReleaseManifest(); + await FusionReleaseStore.WriteFinalAsync( + testDirectory.Path, + manifest, + TestContext.Current.CancellationToken); + var manifestPath = Path.Combine( + testDirectory.Path, + "fusion-release.json"); + var original = await File.ReadAllBytesAsync( + manifestPath, + TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => FusionReleaseStore.WriteFinalAsync( + testDirectory.Path, + manifest with { ReleaseId = "release-2" }, + TestContext.Current.CancellationToken)); + + Assert.Equal( + $"Fusion release manifest '{manifestPath}' already exists with different content.", + exception.Message); + Assert.Equal( + original, + await File.ReadAllBytesAsync( + manifestPath, + TestContext.Current.CancellationToken)); + } + + [Fact] + public async Task VerifyArchiveAsync_Should_Fail_WhenArtifactIntegrityDoesNotMatch() + { + using var testDirectory = new TestDirectory(); + var manifest = CreateReleaseManifest(); + var source = Assert.Single(manifest.Sources); + var archivePath = Path.Combine( + testDirectory.Path, + source.ArchivePath); + Directory.CreateDirectory(Path.GetDirectoryName(archivePath)!); + await File.WriteAllTextAsync( + archivePath, + "tampered", + TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => FusionReleaseStore.VerifyArchiveAsync( + archivePath, + source, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "Fusion source 'products' archive SHA-256 does not match the release manifest.", + exception.Message); + } + + [Fact] + public async Task VerifyFileDigestAsync_Should_Fail_WhenComposedArchiveChanges() + { + using var testDirectory = new TestDirectory(); + var farPath = Path.Combine( + testDirectory.Path, + "fusion-configuration.far"); + await File.WriteAllTextAsync( + farPath, + "tampered", + TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => FusionPipelineExecutor.VerifyFileDigestAsync( + farPath, + new string('0', 64), + "composed Fusion archive", + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The composed Fusion archive SHA-256 does not match prepared apply state.", + exception.Message); + } + + [Fact] + public async Task ReadFinalAsync_Should_Fail_WhenManifestPathIsRelative() + { + var exception = await Assert.ThrowsAsync( + () => FusionReleaseStore.ReadFinalAsync( + "fusion-release.json", + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The Fusion release manifest parameter must resolve to an absolute path.", + exception.Message); + } + + [Fact] + public void ValidateCompositionToolVersion_Should_Fail_WhenVersionDoesNotMatch() + { + var manifest = CreateReleaseManifest() with + { + CompositionToolVersion = "incompatible" + }; + + var exception = Assert.Throws( + () => FusionReleaseCompatibility.ValidateCompositionToolVersion( + manifest)); + + Assert.Equal( + "Fusion release 'release-1' was created with composition tool " + + "version 'incompatible', but apply is running version " + + $"'{FusionReleaseCompatibility.CompositionToolVersion}'.", + exception.Message); + } + + [Fact] + public async Task ReadFinalAsync_Should_ReturnInvalidData_WhenRequiredJsonIsNull() + { + using var testDirectory = new TestDirectory(); + var manifestPath = Path.Combine( + testDirectory.Path, + "fusion-release.json"); + await File.WriteAllTextAsync( + manifestPath, + """ + { + "formatVersion": 1, + "releaseId": "release-1", + "sourceSetSha256": null, + "composition": null, + "sources": null, + "targets": null + } + """, + TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => FusionReleaseStore.ReadFinalAsync( + manifestPath, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The Fusion release manifest is missing required content.", + exception.Message); + } + + [Theory] + [InlineData( + "\"name\": \"products\"", + "\"name\": null", + "The Fusion release manifest contains an invalid source.")] + [InlineData( + "\"cloudUrl\": \"https://api.chillicream.com\"", + "\"cloudUrl\": null", + "The Fusion release manifest contains an invalid target.")] + [InlineData( + "\"apiId\": \"products\"", + "\"apiId\": null", + "The Fusion release manifest contains an invalid target.")] + public async Task ReadFinalAsync_Should_ReturnInvalidData_WhenNestedStringIsNull( + string oldValue, + string newValue, + string expectedMessage) + { + using var testDirectory = new TestDirectory(); + var manifestPath = Path.Combine( + testDirectory.Path, + "fusion-release.json"); + var json = JsonSerializer.Serialize( + CreateReleaseManifest(), + FusionReleaseStore.SerializerOptions) + .Replace( + oldValue, + newValue, + StringComparison.Ordinal); + await File.WriteAllTextAsync( + manifestPath, + json, + TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => FusionReleaseStore.ReadFinalAsync( + manifestPath, + TestContext.Current.CancellationToken)); + + Assert.Equal(expectedMessage, exception.Message); + } + + [Fact] + public void Validate_Should_Fail_WhenArchivePathEscapesRelease() + { + var manifest = CreateReleaseManifest(); + var source = Assert.Single(manifest.Sources); + var invalidSource = source with + { + ArchivePath = "../secret.zip" + }; + var invalidSources = new[] { invalidSource }; + var sourceSetSha256 = + FusionReleaseDigests.ComputeSourceSetSha256(invalidSources); + + var exception = Assert.Throws( + () => FusionReleaseStore.Validate( + manifest with + { + SourceSetSha256 = sourceSetSha256, + Sources = invalidSources, + Targets = + [ + manifest.Targets[0] with + { + SourceSetSha256 = sourceSetSha256 + } + ] + }, + requireTargets: true)); + + Assert.Equal( + "Fusion source 'products' archive path must remain inside the release.", + exception.Message); + } + + [Fact] + public void GetReleaseTarget_Should_Fail_WhenManifestWasNotUploadedToSelectedApi() + { + var deployment = new FusionDeploymentResource( + "production", + new NitroResource("nitro") + { + CloudUrl = "https://api.chillicream.com", + ApiId = "other-api" + }); + + var exception = Assert.Throws( + () => FusionPipelineExecutor.GetReleaseTarget( + CreateReleaseManifest(), + deployment)); + + Assert.Equal( + "Fusion release 'release-1' was not uploaded to Nitro API " + + "'other-api' at 'https://api.chillicream.com'.", + exception.Message); + } + + [Fact] + public void ValidateManifestSourceNames_Should_Fail_WhenProviderGraphDrifts() + { + var exception = Assert.Throws( + () => FusionPipelineExecutor.ValidateManifestSourceNames( + CreateReleaseManifest(), + ["reviews"])); + + Assert.Equal( + "The promoted Fusion source set does not match the AppHost provider " + + "resources. Missing providers: [products]. Unexpected providers: [reviews].", + exception.Message); + } + + [Fact] + public void ValidateReleaseManifestId_Should_Fail_WhenDraftIsInWrongReleaseDirectory() + { + var exception = Assert.Throws( + () => FusionPipelineExecutor.ValidateReleaseManifestId( + CreateReleaseManifest(), + "release-2")); + + Assert.Equal( + "Fusion release manifest ID 'release-1' does not match expected " + + "release 'release-2'.", + exception.Message); + } + [Fact] public void GetTransportEndpoint_Should_Fail_WhenProductionBindingIsMissing() { @@ -207,6 +701,73 @@ private static string GetArtifactFiles(string root) .Select(path => Path.GetRelativePath(root, path).Replace('\\', '/')) .Order()); + private static HashSet GetTransitiveDependencies( + IReadOnlyDictionary steps, + string stepName) + { + var dependencies = new HashSet(StringComparer.Ordinal); + + Visit(stepName); + return dependencies; + + void Visit(string current) + { + foreach (var dependency in steps[current].DependsOnSteps) + { + if (dependencies.Add(dependency)) + { + Visit(dependency); + } + } + } + } + + private static string GetHttpUrl(JsonDocument settings) + => settings.RootElement + .GetProperty("transports") + .GetProperty("http") + .GetProperty("url") + .GetString()!; + + private static FusionReleaseManifest CreateReleaseManifest() + { + var compositionSettings = + FusionReleaseCompositionSettings.From( + new GraphQLCompositionSettings()); + var source = new FusionReleaseSource( + "products", + "release-1", + "sources/products/release-1.zip", + new string('a', 64), + new string('b', 64)); + var sources = new[] { source }; + var sourceSetSha256 = + FusionReleaseDigests.ComputeSourceSetSha256(sources); + + return new FusionReleaseManifest( + FusionReleaseManifest.CurrentFormatVersion, + "release-1", + FusionReleaseCompatibility.CompositionToolVersion, + sourceSetSha256, + new FusionReleaseComposition( + FusionReleaseDigests.ComputeCompositionSha256( + compositionSettings), + compositionSettings), + sources, + [ + new FusionReleaseTarget( + "https://api.chillicream.com", + "products", + sourceSetSha256, + [ + new FusionReleaseSourceReference( + source.Name, + source.Version, + source.ContentSha256) + ]) + ]); + } + private sealed class TestDirectory : IDisposable { public TestDirectory() diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs new file mode 100644 index 00000000000..654bb049937 --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -0,0 +1,473 @@ +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Pipelines; +using ChilliCream.Nitro.Fusion; +using HotChocolate.Fusion; +using HotChocolate.Fusion.Aspire; +using HotChocolate.Fusion.Packaging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace ChilliCream.Nitro.Aspire; + +#pragma warning disable ASPIREPIPELINES001 +#pragma warning disable ASPIREPIPELINES004 + +public sealed class FusionReleaseAcceptanceTests +{ + [Fact] + public async Task Release_Should_BuildOnceAndApplyFromOnlyManifestAcrossRunners() + { + using var testDirectory = new TestDirectory(); + var sourceCheckout = Path.Combine( + testDirectory.Path, + "runner-a-checkout"); + var runnerAOutput = Path.Combine( + testDirectory.Path, + "runner-a-output"); + var runnerB = Path.Combine(testDirectory.Path, "unrelated-runner-b"); + var runnerC = Path.Combine(testDirectory.Path, "unrelated-runner-c"); + Directory.CreateDirectory(sourceCheckout); + + var sourceProjectPath = Path.Combine( + sourceCheckout, + "Products.csproj"); + var gatewayProjectPath = Path.Combine( + sourceCheckout, + "Gateway.csproj"); + await File.WriteAllTextAsync( + sourceProjectPath, + "", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + gatewayProjectPath, + "", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(sourceCheckout, "schema.graphqls"), + """ + type Query { + product: Product + } + + type Product { + id: ID! + name: String! + } + """, + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(sourceCheckout, "schema-settings.json"), + """ + { + "name": "products", + "transports": { + "http": { + "url": "{{PRODUCTS_URL}}/graphql" + } + }, + "environments": { + "staging": { + "PRODUCTS_URL": "https://products.staging.example.com" + }, + "production": { + "PRODUCTS_URL": "https://products.example.com" + } + } + } + """, + TestContext.Current.CancellationToken); + + var workflow = new RecordingFusionDeploymentWorkflow(); + var executor = FusionPipelineExecutor.Instance; + var buildManifestPath = Path.Combine( + runnerAOutput, + "fusion", + "releases", + "release-1", + "fusion-release.json"); + var buildModel = CreateModel( + sourceProjectPath, + gatewayProjectPath, + buildManifestPath); + var buildContext = CreateContext( + buildModel, + "Release", + runnerAOutput, + workflow); + + await executor.CreateArtifactsAsync(buildContext); + await executor.UploadAsync(buildContext); + + var reconcile = Assert.Single(workflow.Reconciliations); + Assert.Equal("products", reconcile.Name); + Assert.Equal("release-1", reconcile.Version); + Assert.True(File.Exists(buildManifestPath)); + var manifest = await FusionReleaseStore.ReadFinalAsync( + buildManifestPath, + TestContext.Current.CancellationToken); + Assert.Equal( + FusionReleaseCompatibility.CompositionToolVersion, + manifest.CompositionToolVersion); + + var runnerBManifestPath = CopyOnlyFinalManifest( + buildManifestPath, + runnerB); + var runnerCManifestPath = CopyOnlyFinalManifest( + buildManifestPath, + runnerC); + var runnerBProjects = await CreateAppHostProjectStubsAsync(runnerB); + var runnerCProjects = await CreateAppHostProjectStubsAsync(runnerC); + Directory.Delete(sourceCheckout, recursive: true); + Assert.False(Directory.Exists(sourceCheckout)); + + var stagingContext = CreateContext( + CreateModel( + runnerBProjects.SourceProjectPath, + runnerBProjects.GatewayProjectPath, + runnerBManifestPath), + "Staging", + Path.Combine(runnerB, "apply-output"), + workflow); + await executor.PrepareReleaseAsync(stagingContext); + await executor.ComposeReleaseAsync(stagingContext); + await executor.PublishAsync(stagingContext); + + var productionContext = CreateContext( + CreateModel( + runnerCProjects.SourceProjectPath, + runnerCProjects.GatewayProjectPath, + runnerCManifestPath), + "Production", + Path.Combine(runnerC, "different-apply-output"), + workflow); + await executor.PrepareReleaseAsync(productionContext); + await executor.ComposeReleaseAsync(productionContext); + await executor.PublishAsync(productionContext); + + Assert.Single(workflow.Reconciliations); + Assert.Equal(2, workflow.Downloads.Count); + Assert.Collection( + workflow.Publications.OrderBy( + publication => publication.Stage, + StringComparer.Ordinal), + production => + { + Assert.Equal("production", production.Stage); + Assert.Equal( + "https://products.example.com/graphql", + production.SourceUrl); + Assert.Equal( + [new FusionSourceSchemaVersion("products", "release-1")], + production.Sources); + }, + staging => + { + Assert.Equal("staging", staging.Stage); + Assert.Equal( + "https://products.staging.example.com/graphql", + staging.SourceUrl); + Assert.Equal( + [new FusionSourceSchemaVersion("products", "release-1")], + staging.Sources); + }); + } + + private static DistributedApplicationModel CreateModel( + string sourceProjectPath, + string gatewayProjectPath, + string manifestPath) + { + var builder = DistributedApplication.CreateBuilder(); + var manifest = builder.AddParameter( + "fusionReleaseManifest", + manifestPath); + var apiKey = builder.AddParameter( + "nitroApiKey", + "test-api-key", + secret: true); + var products = builder + .AddProject("products", sourceProjectPath) + .WithGraphQLSchemaFile(); + builder + .AddProject("gateway", gatewayProjectPath) + .WithReference(products) + .WithGraphQLSchemaComposition(); + var nitro = builder + .AddNitro("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products") + .WithApiKey(apiKey); + nitro + .AddFusionDeployment("staging") + .ForEnvironment("Staging") + .ToStage("staging") + .WithCompositionEnvironment("staging") + .WithConfigurationTag("release-1") + .WithFusionReleaseManifest(manifest); + nitro + .AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithCompositionEnvironment("production") + .WithConfigurationTag("release-1") + .WithFusionReleaseManifest(manifest); + + return new DistributedApplicationModel(builder.Resources); + } + + private static PipelineStepContext CreateContext( + DistributedApplicationModel model, + string environmentName, + string outputPath, + IFusionDeploymentWorkflow workflow) + { + var services = new ServiceCollection() + .AddSingleton( + new TestHostEnvironment(environmentName)) + .AddSingleton( + new TestPipelineOutputService(outputPath)) + .AddSingleton(workflow) + .AddSingleton( + new ConfigurationBuilder().Build()); + var schemaCompositionType = + typeof(GraphQLCompositionSettings).Assembly.GetType( + "HotChocolate.Fusion.Aspire.SchemaComposition", + throwOnError: true)!; + var loggerType = typeof(ILogger<>).MakeGenericType( + schemaCompositionType); + var nullLogger = Activator.CreateInstance( + typeof(NullLogger<>).MakeGenericType( + schemaCompositionType))!; + services.AddSingleton(loggerType, nullLogger); + var serviceProvider = services.BuildServiceProvider(); + var pipelineContext = new PipelineContext( + model, + new DistributedApplicationExecutionContext( + DistributedApplicationOperation.Publish), + serviceProvider, + NullLogger.Instance, + TestContext.Current.CancellationToken); + + return new PipelineStepContext + { + PipelineContext = pipelineContext, + ReportingStep = null! + }; + } + + private static string CopyOnlyFinalManifest( + string sourceManifestPath, + string runnerPath) + { + var promotedDirectory = Path.Combine(runnerPath, "promoted"); + Directory.CreateDirectory(promotedDirectory); + var destination = Path.Combine( + promotedDirectory, + "fusion-release.json"); + File.Copy(sourceManifestPath, destination); + + Assert.Equal( + [destination], + Directory.GetFiles( + promotedDirectory, + "*", + SearchOption.AllDirectories)); + return destination; + } + + private static async Task<( + string SourceProjectPath, + string GatewayProjectPath)> CreateAppHostProjectStubsAsync( + string runnerPath) + { + var appHostDirectory = Path.Combine(runnerPath, "apphost-model"); + Directory.CreateDirectory(appHostDirectory); + var sourceProjectPath = Path.Combine( + appHostDirectory, + "Products.csproj"); + var gatewayProjectPath = Path.Combine( + appHostDirectory, + "Gateway.csproj"); + await File.WriteAllTextAsync( + sourceProjectPath, + "", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + gatewayProjectPath, + "", + TestContext.Current.CancellationToken); + + Assert.False( + File.Exists(Path.Combine(appHostDirectory, "schema.graphqls"))); + Assert.False( + File.Exists( + Path.Combine( + appHostDirectory, + "schema-settings.json"))); + return (sourceProjectPath, gatewayProjectPath); + } + + private sealed class RecordingFusionDeploymentWorkflow + : IFusionDeploymentWorkflow + { + private readonly Dictionary< + (string CloudUrl, string ApiId, string Name, string Version), + FusionSourceSchemaDownload> _sources = []; + + public List Reconciliations { get; } = []; + + public List Downloads { get; } = []; + + public List Publications { get; } = []; + + public async Task ReconcileSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaUpload source, + CancellationToken cancellationToken) + { + var archive = await File.ReadAllBytesAsync( + source.ArchivePath, + cancellationToken); + var contentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + source.ArchivePath, + source.Name, + cancellationToken); + _sources.Add( + CreateKey(target, source.Name, source.Version), + new FusionSourceSchemaDownload( + source.Name, + source.Version, + archive, + contentSha256)); + Reconciliations.Add(source); + } + + public Task DownloadSourceSchemaAsync( + FusionTarget target, + FusionSourceSchemaVersion source, + string expectedContentSha256, + CancellationToken cancellationToken) + { + Downloads.Add(source); + _sources.TryGetValue( + CreateKey(target, source.Name, source.Version), + out var download); + if (download is not null + && !string.Equals( + download.ContentSha256, + expectedContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + "The requested source content digest does not match."); + } + + return Task.FromResult(download); + } + + public async Task PublishAsync( + FusionPublicationRequest request, + string fusionArchivePath, + CancellationToken cancellationToken) + { + using var archive = FusionArchive.Open(fusionArchivePath); + using var configuration = + await archive.TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + cancellationToken) + ?? throw new InvalidDataException( + "The composed Fusion archive has no gateway configuration."); + var sourceUrl = configuration.Settings.RootElement + .GetProperty("sourceSchemas") + .GetProperty("products") + .GetProperty("transports") + .GetProperty("http") + .GetProperty("url") + .GetString() + ?? throw new InvalidDataException( + "The composed source URL is missing."); + Publications.Add( + new RecordedPublication( + request.Stage, + request.SourceSchemas.ToArray(), + sourceUrl)); + } + + private static ( + string CloudUrl, + string ApiId, + string Name, + string Version) CreateKey( + FusionTarget target, + string name, + string version) + => ( + target.CloudUrl.AbsoluteUri.TrimEnd('/'), + target.ApiId, + name, + version); + } + + private sealed record RecordedPublication( + string Stage, + IReadOnlyList Sources, + string SourceUrl); + + private sealed class TestPipelineOutputService(string outputPath) + : IPipelineOutputService + { + public string GetOutputDirectory() => outputPath; + + public string GetOutputDirectory(IResource resource) + => Path.Combine(outputPath, resource.Name); + + public string GetTempDirectory() + => Path.Combine(outputPath, ".temp"); + + public string GetTempDirectory(IResource resource) + => Path.Combine(outputPath, ".temp", resource.Name); + } + + private sealed class TestHostEnvironment(string environmentName) + : IHostEnvironment + { + public string EnvironmentName { get; set; } = environmentName; + + public string ApplicationName { get; set; } = + nameof(FusionReleaseAcceptanceTests); + + public string ContentRootPath { get; set; } = + Directory.GetCurrentDirectory(); + + public IFileProvider ContentRootFileProvider { get; set; } = + new NullFileProvider(); + } + + private sealed class TestDirectory : IDisposable + { + public TestDirectory() + { + Path = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + "chilicream-nitro-aspire-acceptance-tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public void Dispose() + { + Directory.Delete(Path, recursive: true); + } + } +} + +#pragma warning restore ASPIREPIPELINES001 +#pragma warning restore ASPIREPIPELINES004 diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj index cec0c7f1668..e0e990035fd 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj @@ -5,7 +5,7 @@ ChilliCream.Nitro.Fusion.Tests ChilliCream.Nitro.Fusion - net10.0 + net11.0;net10.0;net9.0 diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs index edade30db5c..90724f2e0cf 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs @@ -1,3 +1,4 @@ +using System.IO.Compression; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; @@ -26,6 +27,8 @@ public void Assembly_Should_ExposeOnlyWorkflowContract_When_Inspected() ChilliCream.Nitro.Fusion.FusionIdentityCollisionException ChilliCream.Nitro.Fusion.FusionIndeterminateStateException ChilliCream.Nitro.Fusion.FusionPublicationRequest + ChilliCream.Nitro.Fusion.FusionSourceSchemaContent + ChilliCream.Nitro.Fusion.FusionSourceSchemaDownload ChilliCream.Nitro.Fusion.FusionSourceSchemaUpload ChilliCream.Nitro.Fusion.FusionSourceSchemaVersion ChilliCream.Nitro.Fusion.FusionTarget @@ -34,6 +37,244 @@ public void Assembly_Should_ExposeOnlyWorkflowContract_When_Inspected() """); } + [Fact] + public async Task ComputeSha256Async_Should_ReturnSameDigest_When_ContentIsNormalized() + { + var directory = CreateTemporaryDirectory(); + try + { + var firstPath = Path.Combine(directory, "first.fss"); + var secondPath = Path.Combine(directory, "second.fss"); + await CreateArchiveAsync( + firstPath, + "products", + "type Query { product: String }", + """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); + await CreateArchiveAsync( + secondPath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + + var first = await FusionSourceSchemaContent.ComputeSha256Async( + firstPath, + "products", + TestContext.Current.CancellationToken); + var second = await FusionSourceSchemaContent.ComputeSha256Async( + secondPath, + "products", + TestContext.Current.CancellationToken); + + Assert.Equal(first, second); + first.MatchInlineSnapshot( + """ + 3B7C4FBDBEC6ECB8C072794A67BD1CB8B4DCFB5153B6D284E662A0D3CB49EC08 + """); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_ReturnExactArchive_When_ContentMatches() + { + var directory = CreateTemporaryDirectory(); + try + { + var expectedPath = Path.Combine(directory, "expected.fss"); + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + expectedPath, + "products", + "type Query { product: String }", + """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); + await CreateArchiveAsync( + remotePath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + var remoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken); + var expectedContentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + expectedPath, + "products", + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + RemoteArchive = remoteArchive + }; + var workflow = CreateWorkflow(transport); + + var result = await workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + expectedContentSha256, + TestContext.Current.CancellationToken); + + Assert.NotNull(result); + Assert.Equal("products", result.Name); + Assert.Equal("20260730", result.Version); + Assert.Equal(expectedContentSha256, result.ContentSha256); + Assert.Equal(remoteArchive, result.Archive.ToArray()); + Assert.Equal("products", transport.LastDownloadName); + Assert.Equal("20260730", transport.LastDownloadVersion); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_ReturnNull_When_VersionDoesNotExist() + { + var transport = new FakeTransport(); + var workflow = CreateWorkflow(transport); + + var result = await workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "missing"), + new string('0', 64), + TestContext.Current.CancellationToken); + + Assert.Null(result); + Assert.Equal(1, transport.DownloadCount); + Assert.Equal("products", transport.LastDownloadName); + Assert.Equal("missing", transport.LastDownloadVersion); + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_ThrowCollision_When_DigestDiffers() + { + var directory = CreateTemporaryDirectory(); + try + { + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + remotePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var transport = new FakeTransport + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(transport); + + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + new string('0', 64), + TestContext.Current.CancellationToken)); + + Assert.Equal( + "Source schema 'products' version '20260730' was downloaded " + + "with a different canonical content SHA-256.", + exception.Message); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_NameDiffers() + { + var directory = CreateTemporaryDirectory(); + try + { + var expectedPath = Path.Combine(directory, "expected.fss"); + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + expectedPath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + await CreateArchiveAsync( + remotePath, + "inventory", + "type Query { product: String }", + """{"name":"inventory"}"""); + var expectedContentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + expectedPath, + "products", + TestContext.Current.CancellationToken); + var transport = new FakeTransport + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(transport); + + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + expectedContentSha256, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The source schema settings name must exactly match 'products'.", + exception.Message); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_DecompressedEntryIsTooLarge() + { + var directory = CreateTemporaryDirectory(); + try + { + var remotePath = Path.Combine(directory, "remote.fss"); + await CreateOversizedSettingsArchiveAsync(remotePath); + var transport = new FakeTransport + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(transport); + + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + new string('0', 64), + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The Fusion source schema archive is invalid.", + exception.Message); + Assert.IsType(exception.InnerException); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + [Fact] public async Task ReconcileSourceSchemaAsync_Should_NoOp_When_NormalizedContentMatches() { @@ -338,6 +579,34 @@ await archive.SetSettingsAsync( await archive.CommitAsync(TestContext.Current.CancellationToken); } + private static async Task CreateOversizedSettingsArchiveAsync(string path) + { + await using var stream = File.Create(path); +#if NET10_0_OR_GREATER + await using var archive = new ZipArchive( +#else + using var archive = new ZipArchive( +#endif + stream, + ZipArchiveMode.Create, + leaveOpen: true); + await using (var schema = archive.CreateEntry("schema.graphqls").Open()) + { + await schema.WriteAsync( + Encoding.UTF8.GetBytes("type Query { product: String }"), + TestContext.Current.CancellationToken); + } + + await using (var settings = + archive.CreateEntry("schema-settings.json").Open()) + { + await settings.WriteAsync( + Encoding.UTF8.GetBytes( + $$"""{"name":"products","padding":"{{new string('a', 512_000)}}"}"""), + TestContext.Current.CancellationToken); + } + } + private static string CreateTemporaryDirectory() { var path = Path.Combine( @@ -371,6 +640,10 @@ private sealed class FakeTransport : IFusionDeploymentTransport public int UploadCount { get; private set; } + public string? LastDownloadName { get; private set; } + + public string? LastDownloadVersion { get; private set; } + public List Calls { get; } = []; public Task DownloadSourceSchemaAsync( @@ -379,6 +652,8 @@ private sealed class FakeTransport : IFusionDeploymentTransport CancellationToken cancellationToken) { DownloadCount++; + LastDownloadName = name; + LastDownloadVersion = version; return Task.FromResult( DownloadCount > 1 && RemoteArchiveAfterUpload is not null ? RemoteArchiveAfterUpload From a410c260f5d7dc36cfef6fee2052be80239c0d18 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:31:23 +0200 Subject: [PATCH 03/11] Update Aspire Fusion release publishing pipeline --- .../FUSION_PUBLISHING.md | 364 ++++++++++++++++++ .../FusionPipeline.cs | 126 +++++- .../FusionPipelineExecutor.cs | 98 ++++- .../NitroResourceBuilderExtensions.cs | 2 +- .../src/ChilliCream.Nitro.Aspire/README.md | 2 +- .../FUSION_ASPIRE_PUBLISH_DEPLOY.md | 286 ++++++-------- .../FusionPipelineTests.cs | 270 ++++++++++++- .../FusionReleaseAcceptanceTests.cs | 2 +- 8 files changed, 949 insertions(+), 201 deletions(-) create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md new file mode 100644 index 00000000000..8ac6df632da --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md @@ -0,0 +1,364 @@ +# Promoting Fusion releases with Aspire + +This package supports a build-once, deploy-many Fusion release. One build job exports the source +schemas, creates immutable source-schema archives, uploads them to Nitro, and emits a portable +release manifest. Environment deployment jobs consume that exact manifest, download and verify the +uploaded archives, compose with the selected `schema-settings.json` environment, wait for deployed +services, and publish the resulting Fusion archive. + +The promoted-manifest workflow is opt-in. Deployments without +`WithFusionReleaseManifest(...)` continue to use the legacy same-runner workflow. +The examples use Aspire CLI 13.4.6, whose AppHost selector is `--apphost`. + +## AppHost declaration + +Declare the release ID and manifest path as external parameters. The manifest path is required only +by deployment jobs and must resolve to an absolute path. + +```csharp +var releaseId = builder.AddParameter("releaseId"); +var releaseManifest = builder.AddParameter("fusionReleaseManifest"); +var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); + +var nitro = builder.AddNitroTarget("nitro") + .WithCloudUrl("https://api.chillicream.com") + .WithApiId("products-fusion") + .WithApiKey(nitroApiKey); + +nitro.AddFusionDeployment("staging") + .ForEnvironment("Staging") + .ToStage("staging") + .WithCompositionEnvironment("staging") + .WithConfigurationTag(releaseId) + .WithFusionReleaseManifest(releaseManifest); + +nitro.AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithCompositionEnvironment("production") + .WithConfigurationTag(releaseId) + .WithFusionReleaseManifest(releaseManifest) + .WithApproval(waitForApproval: true); +``` + +`ForEnvironment` selects an Aspire deployment. `ToStage` selects the Nitro stage. +`WithCompositionEnvironment` selects the exact case-sensitive key under +`schema-settings.json.environments`. When it is omitted, an existing composition +`EnvironmentName` is used, then the Nitro stage name is the default. + +The source archives are shared across these declarations. For example: + +```json +{ + "name": "products", + "transports": { + "http": { + "url": "{{PRODUCTS_URL}}/graphql" + } + }, + "environments": { + "staging": { + "PRODUCTS_URL": "https://products.staging.example.com" + }, + "production": { + "PRODUCTS_URL": "https://products.example.com" + } + } +} +``` + +The release contains this settings document once. Staging and production resolve different gateway +settings while retaining the same source name, version, and content digest. + +Promoted releases use the release ID as the source version. The legacy +`WithDefaultSourceVersionFromGitCommit()` option applies only to deployments without a release +manifest; apply never invokes Git to rediscover a promoted version. + +## Build and upload + +The build job runs the named upload step: + +```bash +export Parameters__releaseId="$RELEASE_ID" +export Parameters__nitroApiKey="$NITRO_API_KEY" + +aspire do fusion-upload \ + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Release \ + --output-path "$RUNNER_TEMP/fusion-release" \ + --non-interactive +``` + +`fusion-upload` depends on `fusion-artifacts`. This single command therefore: + +1. discovers the composition source schemas; +2. reads checked-in schema files or executes explicitly configured command-line exporters once; +3. creates one immutable source archive per source; +4. computes both the archive SHA-256 and the normalized Fusion content SHA-256; +5. reconciles each source version into every distinct declared Nitro API target; +6. writes the final manifest only after all uploads are verified. + +The final release is: + +```text +/ + fusion/ + releases/ + / + fusion-release.json + fusion-release.draft.json + sources/ + products/ + .zip + reviews/ + .zip +``` + +Promote only `fusion/releases//fusion-release.json` as the deployable CI artifact. The +source ZIPs and draft manifest are local build evidence used by `fusion-upload`; manifest apply +never reads them. Instead, apply downloads every exact source version from Nitro and verifies its +normalized content digest. The final manifest contains portable relative archive paths so the full +release directory can still be retained for audit purposes, but those paths are not apply inputs. +It never contains credentials, project paths, working directories, stages, Aspire environments, or +timestamps. + +`aspire publish` remains local-only. It runs `fusion-artifacts` and emits the archives plus draft +manifest, but it does not resolve a Nitro credential or perform an upload. Automation normally uses +`aspire do fusion-upload` directly because the artifact step is already its dependency. + +## Deploy on a separate runner + +Download the CI artifact into any read-only or otherwise stable directory. Pass the exact final +manifest path through the configured parameter and use a separate output directory for the apply: + +```bash +export Parameters__releaseId="$RELEASE_ID" +export Parameters__fusionReleaseManifest="$RUNNER_TEMP/promoted-release/fusion-release.json" +export Parameters__nitroApiKey="$NITRO_API_KEY" + +aspire do fusion-publish \ + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Staging \ + --output-path "$RUNNER_TEMP/aspire-apply/Staging" \ + --non-interactive +``` + +Production uses the identical downloaded manifest: + +```bash +aspire do fusion-publish \ + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Production \ + --output-path "$RUNNER_TEMP/aspire-apply/Production" \ + --non-interactive +``` + +The executor reads exactly `Parameters__fusionReleaseManifest`; it does not scan or reuse +`--output-path` to discover a release. It validates each manifest archive path as a portable +release-relative path but does not open or copy that local archive during apply. Downloaded sources, +apply state, and the composed FAR are written beneath the fresh apply output: + +```text +/ + fusion/ + apply/ + / + fusion-apply.json + fusion-configuration.far + sources/ +``` + +The downloaded release manifest is never regenerated or overwritten. A deployment runner still +needs to evaluate the same pinned AppHost model so Aspire can discover the Fusion declarations and +provider deployment steps. Supply either a buildable AppHost checkout or the same promoted AppHost +binary and locked dependencies with the appropriate Aspire `--no-build` workflow. A compute +provider may still require its own workload source, container context, or promoted image. The +Fusion apply graph itself does not need the source-schema files, the checkout used for schema +export, Git metadata, a schema exporter, or the build runner's local source ZIPs. + +## GitHub Actions promotion and writer serialization + +This example uploads only the final manifest, downloads it into a different runner path, and +serializes each Nitro stage writer: + +```yaml +jobs: + fusion-build: + runs-on: ubuntu-latest + env: + RELEASE_ID: ${{ github.sha }} + Parameters__releaseId: ${{ github.sha }} + Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + - name: Build and upload immutable Fusion sources + run: >- + aspire do fusion-upload + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj + --environment Release + --output-path "$RUNNER_TEMP/fusion-release" + --non-interactive + - uses: actions/upload-artifact@v4 + with: + name: fusion-release-manifest + path: ${{ runner.temp }}/fusion-release/fusion/releases/${{ env.RELEASE_ID }}/fusion-release.json + if-no-files-found: error + + fusion-deploy: + needs: fusion-build + runs-on: ubuntu-latest + strategy: + matrix: + include: + - environment: Staging + writer-key: api.chillicream.com|products-fusion|staging + - environment: Production + writer-key: api.chillicream.com|products-fusion|production + concurrency: + group: fusion-${{ matrix.writer-key }} + cancel-in-progress: false + env: + Parameters__releaseId: ${{ github.sha }} + Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: fusion-release-manifest + path: ${{ runner.temp }}/promoted-release + - name: Publish promoted Fusion release + env: + Parameters__fusionReleaseManifest: ${{ runner.temp }}/promoted-release/fusion-release.json + run: >- + aspire do fusion-publish + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj + --environment "${{ matrix.environment }}" + --output-path "$RUNNER_TEMP/aspire-apply/${{ matrix.environment }}" + --non-interactive +``` + +Each `writer-key` must be a stable encoding of the canonical `(Nitro cloud origin, API ID, stage)` +tuple. `cancel-in-progress: false` queues writers instead of cancelling an in-flight approval. This +package rejects duplicate declarations for the same canonical target and stage within one selected +AppHost environment. It does not provide a distributed lock across workflow runs, repositories, or +other deployment systems. Keep the CI concurrency control, and require server-side Nitro +serialization or an external coordinator when writers can originate outside that one concurrency +domain. + +## Command contract + +| Command | Promoted-manifest behavior | Nitro effect | +| --- | --- | --- | +| `aspire publish` | Exports once and writes source archives plus a draft manifest. | None. | +| `aspire do fusion-upload` | Runs artifacts, reconciles immutable versions into all declared targets, and finalizes `fusion-release.json`. | Source upload only. | +| `aspire do fusion-publish` | Reads the exact manifest parameter, downloads and verifies sources, deploys the sources, composes for the selected environment, waits for readiness, publishes the stage, deploys the gateway, and completes. | Stage publication only, never source upload. | +| `aspire deploy --environment ` | Runs provider deployment and requires the same manifest apply graph to complete. | The same ordered source deployment, stage publication, and gateway deployment. | + +## Pipeline graphs + +Build and upload: + +```text +fusion-artifacts -> fusion-upload + | + +-----------------> Aspire Publish +``` + +Promoted-manifest apply: + +```text +fusion-release-prepare -> fusion-compose + | +source DeployCompute -----------+-> fusion-readiness + | + v + fusion-publish-stage + | + v + gateway DeployCompute + | + v + fusion-publish + | + v + Aspire Deploy +``` + +`fusion-publish` has no transitive dependency on `fusion-artifacts` or `fusion-upload` in manifest +mode. Consequently neither the named publish step nor `aspire deploy` can invoke source export, +archive materialization from a checkout, Git version discovery, or source upload. +The named publish step includes the provider build, push, and deploy graph for every referenced +source and for the gateway. The order is source deployment, readiness, internal Nitro stage +publication, gateway deployment, then the terminal public `fusion-publish` step. `aspire deploy` +is the broader AppHost root and requires that same terminal step. + +Legacy apply, used only when no manifest parameter is configured: + +```text +fusion-artifacts -> fusion-upload +fusion-artifacts -> fusion-readiness <- source DeployCompute +fusion-upload + fusion-readiness -> fusion-publish-stage +fusion-publish-stage -> gateway DeployCompute +gateway DeployCompute -> fusion-publish -> Aspire Deploy +``` + +Do not mix manifest and legacy deployments for the same Aspire environment. + +## Manifest integrity and target binding + +`fusion-release.json` is written atomically after upload verification. It binds: + +- the manifest format version, release ID, and exact Fusion composition tool version; +- composition options and their digest; +- the complete source set digest; +- each source name, immutable version, relative archive path, archive SHA-256, and normalized content + SHA-256; +- every Nitro cloud URL and API ID to which the complete source set was uploaded. + +Apply rejects unsupported formats, invalid or duplicate entries, path traversal, composition or +source-set digest changes, an unexpected release ID, an incompatible composition tool version, and +a target that is not recorded in the manifest. It downloads each exact `name@version` from Nitro +and verifies normalized content before writing its private apply cache. The cache is verified again +before composition. Build and apply should use the same promoted AppHost binary and locked package +set; the manifest's exact composition-tool identity makes a mismatched apply fail before download +or composition. + +The raw archive digest protects the build artifact. The normalized content digest protects the +Nitro identity even if ZIP container bytes differ while schema, settings, and extensions remain +identical. + +The manifest itself is immutable. Retrying the same release ID against an output directory that +already contains its final manifest intentionally reuses that final manifest before inspecting the +current checkout. This makes a lost-response retry stable even if the working tree changed. A new +checkout, source set, composition configuration, tool version, or intended release requires a new +release ID. A different manifest cannot overwrite an existing `fusion-release.json` at the same +release path, and an existing final manifest cannot be extended to a different target set. + +## Publication behavior + +Composition runs before readiness so environment variables in shared source settings are resolved +first. Readiness reads the composed FAR gateway settings and probes the final +`sourceSchemas.*.transports.http.url` values after every referenced source's provider deployment +terminal. Transient DNS, connection, request-timeout, and HTTP 5xx failures are retried with a +bounded delay until the deployment's operation timeout expires. Any response below HTTP 500 is +accepted, preserving the endpoint's ability to use authentication or method-specific status codes +as liveness evidence. A deadline failure reports both the source and endpoint. + +The adapter prefers a `DeployCompute` step associated directly with the resource, then resolves +Aspire 13.4's materialized deployment target and selects its `DeployCompute` step. It applies the +same rule to the gateway. Publication fails closed when any managed source or gateway has no +provable deployment terminal. Infrastructure provisioning alone is not treated as a completed +compute deployment. The gateway deployment depends on successful internal stage publication, and +the public `fusion-publish` terminal depends on the gateway deployment. + +Publication then uses the already composed FAR and the exact source references from the manifest. +It preserves the configured approval, force, operation-timeout, and approval-timeout behavior. +The stage reaches a verified terminal Nitro result or the Aspire step fails. + +Aspire environment selection remains exact and case-sensitive. `fusion-publish` and `aspire deploy` +perform no Nitro stage publication when the selected environment has no matching Fusion +deployment. The dedicated build invocation is intentional exception: when promoted-manifest +declarations exist, `aspire do fusion-upload --environment Release` can use an otherwise unmatched +build environment to create and upload the shared source set to every distinct declared Nitro API +target. Running `aspire deploy --environment Release` does not attach that build-only upload step to +Deploy. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs index ba0449883cd..e44eaa41885 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs @@ -16,6 +16,7 @@ internal static class FusionPipeline internal const string ComposeReleaseStepName = "fusion-compose"; internal const string ReadinessStepName = "fusion-readiness"; internal const string UploadStepName = "fusion-upload"; + internal const string PublishStageStepName = "fusion-publish-stage"; internal const string PublishStepName = "fusion-publish"; public static void Configure( @@ -219,12 +220,20 @@ private static PipelineStep[] CreateStepDefinitions( }, new PipelineStep { - Name = PublishStepName, + Name = PublishStageStepName, Description = "Publish the promoted Fusion configuration to Nitro.", Resource = resource, DependsOnSteps = [ReadinessStepName], - RequiredBySteps = [WellKnownPipelineSteps.Deploy], Action = ExecutePublishAsync + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Complete the promoted Fusion deployment.", + Resource = resource, + DependsOnSteps = [PublishStageStepName], + RequiredBySteps = [WellKnownPipelineSteps.Deploy], + Action = _ => Task.CompletedTask } ]; } @@ -243,11 +252,19 @@ private static PipelineStep[] CreateStepDefinitions( }, new PipelineStep { - Name = PublishStepName, + Name = PublishStageStepName, Description = "Publish a Fusion configuration to Nitro.", Resource = resource, DependsOnSteps = [ReadinessStepName], Action = ExecutePublishAsync + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Complete the Fusion deployment.", + Resource = resource, + DependsOnSteps = [PublishStageStepName], + Action = _ => Task.CompletedTask } ]; } @@ -265,12 +282,20 @@ private static PipelineStep[] CreateStepDefinitions( }, new PipelineStep { - Name = PublishStepName, + Name = PublishStageStepName, Description = "Compose and publish the Fusion configuration to Nitro.", Resource = resource, DependsOnSteps = [UploadStepName, ReadinessStepName], - RequiredBySteps = [WellKnownPipelineSteps.Deploy], Action = ExecutePublishAsync + }, + new PipelineStep + { + Name = PublishStepName, + Description = "Complete the Fusion deployment.", + Resource = resource, + DependsOnSteps = [PublishStageStepName], + RequiredBySteps = [WellKnownPipelineSteps.Deploy], + Action = _ => Task.CompletedTask } ]; } @@ -290,18 +315,22 @@ private static void ConfigureSteps( context.Model); var readiness = context.Steps.Single( step => step.Name == ReadinessStepName); + var stagePublication = context.Steps.Single( + step => step.Name == PublishStageStepName); + var publication = context.Steps.Single( + step => step.Name == PublishStepName); - topology.SourcesWithoutCompute.Clear(); + topology.ResourcesWithoutCompute.Clear(); foreach (var source in sources) { - var computeSteps = context - .GetSteps(source.Resource, WellKnownPipelineTags.DeployCompute) - .ToArray(); + var computeSteps = SelectResourceDeploymentSteps( + context, + source.Resource); if (computeSteps.Length == 0) { - topology.SourcesWithoutCompute.Add(source.Resource.Name); + topology.ResourcesWithoutCompute.Add(source.Resource.Name); continue; } @@ -310,6 +339,67 @@ private static void ConfigureSteps( readiness.DependsOn(computeStep); } } + + var gatewayComputeSteps = SelectResourceDeploymentSteps( + context, + composition); + + if (gatewayComputeSteps.Length == 0) + { + topology.ResourcesWithoutCompute.Add(composition.Name); + return; + } + + WireGatewayDeployment( + stagePublication, + publication, + gatewayComputeSteps); + } + + internal static void WireGatewayDeployment( + PipelineStep stagePublication, + PipelineStep publication, + IEnumerable gatewayComputeSteps) + { + foreach (var gatewayComputeStep in gatewayComputeSteps) + { + gatewayComputeStep.DependsOn(stagePublication); + publication.DependsOn(gatewayComputeStep); + } + } + + internal static PipelineStep[] SelectResourceDeploymentSteps( + PipelineConfigurationContext context, + IResource resource) + { + var deployComputeSteps = context + .GetSteps(resource, WellKnownPipelineTags.DeployCompute) + .ToArray(); + + if (deployComputeSteps.Length > 0) + { + return deployComputeSteps; + } + + var deploymentTarget = resource + .GetDeploymentTargetAnnotation() + ?.DeploymentTarget; + + if (deploymentTarget is not null) + { + deployComputeSteps = context + .GetSteps( + deploymentTarget, + WellKnownPipelineTags.DeployCompute) + .ToArray(); + + if (deployComputeSteps.Length > 0) + { + return deployComputeSteps; + } + } + + return []; } private static Task ExecuteArtifactsAsync(PipelineStepContext context) @@ -319,14 +409,20 @@ private static Task ExecuteReadinessAsync( PipelineStepContext context, FusionPipelineTopology topology) { - if (topology.SourcesWithoutCompute.Count > 0) + EnsureResourceDeploymentOrdering(topology.ResourcesWithoutCompute); + + return GetExecutor(context).VerifyReadinessAsync(context); + } + + internal static void EnsureResourceDeploymentOrdering( + IReadOnlyCollection resourcesWithoutCompute) + { + if (resourcesWithoutCompute.Count > 0) { throw new InvalidOperationException( "Fusion publication cannot prove compute deployment ordering for resources: " - + string.Join(", ", topology.SourcesWithoutCompute.Order())); + + string.Join(", ", resourcesWithoutCompute.Order())); } - - return GetExecutor(context).VerifyReadinessAsync(context); } private static Task ExecuteUploadAsync(PipelineStepContext context) @@ -410,7 +506,7 @@ private sealed class FusionPipelineTopology public bool BuildOnlyManifestProducer { get; set; } - public HashSet SourcesWithoutCompute { get; } = + public HashSet ResourcesWithoutCompute { get; } = new(StringComparer.Ordinal); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs index fb3155c2ed6..b7696458f74 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs @@ -21,6 +21,9 @@ namespace ChilliCream.Nitro.Aspire; internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor { + private static readonly TimeSpan s_readinessRetryDelay = + TimeSpan.FromSeconds(1); + public static FusionPipelineExecutor Instance { get; } = new(); public async Task CreateArtifactsAsync(PipelineStepContext context) @@ -132,15 +135,13 @@ await File.ReadAllTextAsync( var endpoint = GetTransportEndpoint(resolvedSettings); RejectLoopbackEndpoint(endpoint); - using var response = await httpClient.GetAsync( + await WaitForReadinessAsync( + httpClient, + Path.GetFileName(sourceDirectory), endpoint, + deployment.OperationTimeout, + s_readinessRetryDelay, context.CancellationToken); - if ((int)response.StatusCode >= (int)HttpStatusCode.InternalServerError) - { - throw new InvalidOperationException( - $"Fusion source '{Path.GetFileName(sourceDirectory)}' did not pass " - + "its production readiness check."); - } } } } @@ -785,17 +786,92 @@ await archive.TryGetGatewayConfigurationAsync( configuration.Settings)) { RejectLoopbackEndpoint(endpoint); - using var response = await httpClient.GetAsync( + await WaitForReadinessAsync( + httpClient, + name, endpoint, + deployment.OperationTimeout, + s_readinessRetryDelay, context.CancellationToken); + } + } + } + + internal static async Task WaitForReadinessAsync( + HttpClient httpClient, + string sourceName, + Uri endpoint, + TimeSpan timeout, + TimeSpan retryDelay, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(httpClient); + ArgumentException.ThrowIfNullOrWhiteSpace(sourceName); + ArgumentNullException.ThrowIfNull(endpoint); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual( + timeout, + TimeSpan.Zero); + ArgumentOutOfRangeException.ThrowIfLessThan( + retryDelay, + TimeSpan.Zero); + + using var timeoutSource = + CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + timeoutSource.CancelAfter(timeout); + Exception? lastFailure = null; + + while (!timeoutSource.IsCancellationRequested) + { + try + { + using var response = await httpClient.GetAsync( + endpoint, + timeoutSource.Token); if ((int)response.StatusCode - >= (int)HttpStatusCode.InternalServerError) + < (int)HttpStatusCode.InternalServerError) { - throw new InvalidOperationException( - $"Fusion source '{name}' did not pass its production readiness check."); + return; } + + lastFailure = new HttpRequestException( + $"The readiness endpoint returned HTTP {(int)response.StatusCode} " + + $"({response.StatusCode})."); + } + catch (HttpRequestException exception) + { + lastFailure = exception; + } + catch (OperationCanceledException exception) + when (!cancellationToken.IsCancellationRequested) + { + lastFailure = exception; + } + + if (timeoutSource.IsCancellationRequested) + { + break; + } + + try + { + await Task.Delay( + retryDelay, + timeoutSource.Token); + } + catch (OperationCanceledException) + when (!cancellationToken.IsCancellationRequested) + { + break; } } + + cancellationToken.ThrowIfCancellationRequested(); + + throw new InvalidOperationException( + $"Fusion source '{sourceName}' at '{endpoint}' did not pass its " + + $"production readiness check within {timeout}.", + lastFailure); } private static async Task PublishReleaseAsync( diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs index 5ee4142dae6..c01d71a3bf8 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs @@ -12,7 +12,7 @@ public static class NitroResourceBuilderExtensions /// /// Adds a Nitro target. /// - public static IResourceBuilder AddNitro( + public static IResourceBuilder AddNitroTarget( this IDistributedApplicationBuilder builder, string name) { diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md index 360795af791..40d19e023b1 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md @@ -10,7 +10,7 @@ var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); var releaseId = builder.AddParameter("releaseId"); var releaseManifest = builder.AddParameter("fusionReleaseManifest"); -var nitro = builder.AddNitro("nitro") +var nitro = builder.AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products-fusion") .WithApiKey(nitroApiKey); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md index 769454ae558..59b0c5e4123 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md @@ -2,16 +2,16 @@ ## Decision -`aspire deploy` is the canonical Fusion release command. When an AppHost explicitly declares a Fusion deployment for the selected Aspire environment, every matching `aspire deploy` must export, reconcile source-schema uploads, compose, and publish the Fusion configuration before the well-known Deploy step can complete. +The canonical split release commands are `aspire do fusion-upload` for the build job and `aspire do fusion-publish` for an environment deployment job. Upload creates immutable source versions and a portable release manifest. Publish consumes that exact manifest, deploys the source services, composes, verifies source readiness, publishes the Fusion configuration to Nitro, deploys the gateway, and completes its public terminal step. Manifest-mode publish never uploads a source. -`aspire publish` remains artifact-only for Fusion. It produces portable schema inputs, templates, bindings, and provenance, but it does not call Nitro, resolve a Nitro credential, or mutate a Nitro stage. Named `aspire do` steps remain useful for inspection, repair, and controlled reconciliation, but they are supplementary and must not provide a weaker route around compute/readiness dependencies. +`aspire publish` remains artifact-only for Fusion. It produces the portable source archives and draft manifest, but it does not call Nitro, resolve a Nitro credential, or mutate a Nitro stage. The broader `aspire deploy` root is also supported and requires `fusion-publish`, but CI uses the two named commands so the release artifact crosses runners explicitly. The resulting invariant is: ```text Fusion deployment declared for environment E + -aspire deploy --environment E +aspire do fusion-publish --environment E = Nitro stage reaches terminal success, or the Aspire deployment does not succeed ``` @@ -24,26 +24,28 @@ At the time of research, the latest stable Aspire release is **13.4.6**, release The programmatic pipeline APIs used to register and order custom steps remain experimental and emit `ASPIREPIPELINES001`. `aspire do` is the command for running a selected step and its dependencies, but that does not make every API behind custom step construction GA. Keep the experimental integration behind a narrow adapter and explicitly accept the diagnostic in that package. Sources: [deployment pipelines](https://aspire.dev/deployment/pipelines/), [ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/), [`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), [`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and [`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/). -This repository currently pins `Aspire.Hosting.AppHost`, `Aspire.Hosting`, and `Aspire.Hosting.PostgreSQL` to **13.1.2** in [`src/Directory.Packages.props`](../../../../Directory.Packages.props). The required step-discovery and pipeline configuration concepts exist in that pin. Implement and test against 13.1.2 first, then separately test and upgrade to the current stable line. +This repository pins the Aspire hosting packages to **13.4.6** in [`src/Directory.Packages.props`](../../../../Directory.Packages.props). CLI examples use Aspire CLI 13.4.6 and its current `--apphost` option. ## Command contract | Entry point | Fusion behavior | Remote effect | | --- | --- | --- | | `aspire publish` | Evaluate/build as Aspire requires, then emit Fusion SDL, settings templates, endpoint bindings, archive inputs, and provenance beneath the output path. | No Nitro API call, Nitro credential resolution, upload, slot request, or stage mutation. Aspire itself may still create/update local deployment configuration, verify certificates, build, or perform other non-Nitro work. | -| `aspire deploy --environment Production` | Run the provider deployment and every Fusion deployment explicitly mapped to `Production`. It succeeds only after each mapped Nitro publication reaches terminal success. | Mandatory Nitro reconciliation for each matching declaration. | -| `aspire do fusion-upload` | Produce portable Fusion inputs and reconcile immutable source versions. | Upload only. It is not equivalent to deployment completion. | -| `aspire do fusion-publish` | Run the same compute discovery, readiness, upload, composition, and publication dependency graph as the matching deploy. | Safe supplementary reconciliation. It can redeploy or recheck prerequisites as dictated by its graph. | +| `aspire do fusion-upload` | Produce portable Fusion inputs, reconcile immutable source versions, and finalize `fusion-release.json`. | Source upload only. It is not equivalent to deployment completion. | +| `aspire do fusion-publish` | Read the exact promoted manifest, download and verify its source versions, deploy source compute, compose, check readiness, publish the Nitro stage, deploy gateway compute, and complete. | Stage publication only. It never uploads source versions in manifest mode. | +| `aspire deploy --environment Production` | Run the broader provider root, which requires the same `fusion-publish` graph for the matching declaration. | The same stage publication plus any additional AppHost deployment roots. | | `aspire destroy` | Destroy provider resources according to the deployment target. | Never infer deletion of shared Nitro schema history or stage configurations. Nitro retention needs a separate explicit design. | -`aspire deploy` does not consume an arbitrary previously produced `aspire publish` directory as a promoted release. It evaluates the AppHost and executes pipeline dependencies for that invocation. Artifact promotion requires an explicit artifact-path/apply step or an external release tool. +Neither publication command scans an arbitrary `aspire publish` directory for a promoted release. `fusion-upload` writes the final manifest, and `fusion-publish` reads only the absolute path supplied through the configured manifest parameter. ## Environment and stage selection The Fusion deployment declaration must map an Aspire environment to exactly one intended Nitro stage. Do not infer stage from branch, tag, provider, resource name, or `ASPNETCORE_ENVIRONMENT`. Do not deploy staging and production from the same invocation merely because both are declared. ```csharp -var nitro = builder.AddNitro("nitro") +var releaseManifest = builder.AddParameter("fusionReleaseManifest"); + +var nitro = builder.AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products-fusion") .WithApiKey(builder.AddParameter("nitroApiKey", secret: true)); @@ -52,7 +54,7 @@ nitro.AddFusionDeployment("production") .ForEnvironment("Production") .ToStage("production") .WithConfigurationTag(builder.AddParameter("releaseId")) - .WithDefaultSourceVersionFromGitCommit() + .WithFusionReleaseManifest(releaseManifest) .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -60,9 +62,9 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -Adding this resource opts Fusion publication into every `aspire deploy --environment Production`. A separate `.ForEnvironment("Staging").ToStage("staging")` declaration runs only when Staging is selected. Graph construction must fail when multiple Fusion deployments ambiguously claim the same environment/API/stage or when the selected environment has inconsistent mappings. +Adding this resource opts Fusion publication into `aspire do fusion-publish --environment Production` and the broader `aspire deploy --environment Production`. A separate `.ForEnvironment("Staging").ToStage("staging")` declaration runs only when Staging is selected. Graph construction must fail when multiple Fusion deployments ambiguously claim the same environment/API/stage or when the selected environment has inconsistent mappings. -The sketch is illustrative. Match the public API to Aspire 13.1.2 resource idioms and keep required values required. +The sketch uses the implemented Aspire 13.4.6 public surface and keeps required values required. ## Existing `HotChocolate.Fusion.Aspire` state @@ -74,7 +76,9 @@ The repository already has a source-schema and composition graph. Adapt it rathe * File mode can default its asserted name from the Aspire resource, while upload derives the authoritative name from `schema-settings.json`. The manifest must validate that the declared/manifest name exactly matches settings `name`. * Current runtime discovery is suitable for `aspire run`, but normal DCP application orchestration is disabled in publish mode in the inspected Aspire source. Publish cannot assume resource endpoints are running. -Refactor these declarations into a reusable internal model while preserving composition relationships between the gateway/composition resource and its referenced sources. The deploy pipeline then augments those declarations with provider step discovery, deployment endpoint bindings, and readiness evidence. +The integration reuses these declarations and preserves the composition relationships between the +gateway resource and its referenced sources. The deploy pipeline augments that model with provider +step discovery, environment-resolved endpoint bindings, and readiness evidence. ## Can `aspire publish` use Hot Chocolate command-line schema export? @@ -106,7 +110,9 @@ V1 should support checked-in/generated schema files and an explicit per-resource * Precreate an isolated output directory per source resource and invocation. Never share the application working directory or accept a stale file as fallback. * Use `System.Diagnostics.Process` with `ProcessStartInfo.ArgumentList`, not shell concatenation. `WithProcessCommand` is a dashboard/resource command API, not the custom deployment pipeline runner. * Use an environment allowlist. Do not pass Nitro credentials or unrelated AppHost secrets to the child. -* Record configuration, target framework, RID, working directory, launch-profile policy, project/assembly identity, and content/image digest in provenance. `--no-launch-profile` is the deterministic default. +* Record configuration, target framework, RID, working directory, launch-profile policy, project + path and SHA-256, and exported schema SHA-256 in provenance. `--no-launch-profile` is the + deterministic default. Exact deployed assembly or container-image binding remains future work. * Enforce timeout and cancellation, and kill the entire process tree on termination. Capture bounded stdout/stderr and redact sensitive values. * Validate freshness, non-empty SDL, parseable settings, exact `schema-settings.json` name, extensions, and the explicitly selected schema name. * Treat a generated settings URL that resolves to localhost/loopback as a template risk. Reject it for final deployment and materialize the provider-resolved production URL only after readiness. @@ -146,134 +152,91 @@ A Git commit alone is not always a rollout identity. Redeploying the same commit Prefer `ParameterResource` and Aspire configuration, including `Parameters__nitroApiKey` and `Parameters__releaseId`. Support `NITRO_*` only as a documented fallback with deterministic precedence. See [external parameters](https://aspire.dev/fundamentals/external-parameters/). -Current source metadata must remain limited to the existing GitHub and Azure DevOps model. Richer provider/actor metadata is future work. Bind schema provenance to the exact build or container image digest deployed by the provider steps, not only to a mutable working directory. - -## Two-phase artifacts - -Some source-schema settings include deployment-resolved production URLs. A local publish cannot safely guess them. Artifact generation therefore has two phases. +Current source metadata remains limited to the existing GitHub and Azure DevOps model. Richer +provider/actor metadata and binding schema provenance to the exact deployed build or container image +digest remain future work. -### Publish phase +## Build-once artifacts and manifest apply -`aspire publish` emits immutable build inputs under the selected output path: +`aspire publish` produces source archives and a draft release manifest beneath +`/fusion/releases/`. It has no Nitro credential resolution, upload, +composition slot, commit, or stage mutation. `aspire do fusion-upload` runs that artifact step, +reconciles each immutable source version into every declared Nitro API target, verifies existing +versions by normalized content, and atomically writes the final `fusion-release.json`. -```text -/fusion/production/ - nitro-deployment-template.json - sources/ - products/ - schema.graphqls - schema-settings.template.json - extensions/ - provenance.json - inventory/ - schema.graphqls - schema-settings.template.json - extensions/ - provenance.json -``` +The final manifest records the release ID, exact composition tool identity and options, the complete +source-set digest, each source version and content digest, and every Nitro API target to which the +source set was uploaded. It contains no credential, stage, Aspire environment, timestamp, checkout +path, or runner-specific absolute path. -Its Fusion graph ends at artifacts: - -```text -source file or explicit child-process export - | - validate and record provenance - | - SDL/settings template/bindings - | - publish output complete -``` - -There is no Nitro credential resolution, upload, composition slot, commit, or stage mutation on this graph. - -Acquisition supports an existing SDL file and an explicit native export command such as: - -```text -dotnet run --project /absolute/path/Products.Subgraph.csproj --configuration Release --no-launch-profile -- schema export --output /isolated/products/schema.graphqls --schema-name Products -``` - -Endpoint acquisition is unavailable by default in publish mode. An externally managed endpoint can be used only when explicitly declared and authenticated. Apollo `/graphql` requires an explicit Apollo source kind. - -### Deploy materialization phase - -After provider deployment and actual readiness, resolve production endpoint bindings, materialize final `schema-settings.json`, and build the archive with the public `HotChocolate.Fusion.SourceSchema.Packaging.FusionSourceSchemaArchive`. Stage composition and commit must occur after service deployment and actual readiness. The final manifest records the release and provider provenance: - -```json -{ - "formatVersion": 1, - "cloudUrl": "https://api.chillicream.com", - "apiId": "products-fusion", - "environment": "Production", - "stage": "production", - "configurationTag": "build-842-a1b2c3d4", - "stageOwnership": "authoritative", - "sources": [ - { - "name": "products", - "sourceVersion": "a1b2c3d4", - "archive": "materialized/products-a1b2c3d4.zip", - "sha256": "...", - "imageDigest": "sha256:..." - } - ] -} -``` - -The deployment manifest and its validation are new work. There is no existing Nitro CLI manifest/SDL validator to reuse. Validate all paths, hashes, settings names, bindings, and provenance. Write the final manifest atomically after all archives materialize. +The deploy runner receives only that final manifest as the promotion artifact. Manifest apply reads +the exact absolute path supplied by `Parameters__fusionReleaseManifest`, verifies the release and +target bindings, downloads every exact source version from Nitro, verifies normalized content, and +composes the environment-specific FAR. It never reads the build runner's source ZIPs and never +uploads a source version. ## Mandatory deploy graph -The required ordering is: +The implemented promoted-manifest ordering is: ```text -publish-time export/validation -----+ - | -provider source compute deploy | (may run in parallel) - | | -actual production readiness --------+ - | -final settings/archive materialization - | -reconcile source uploads - | -request and claim Nitro slot - | -re-download current stage FAR and selected archives - | -authoritative local composition - | -policy-aware remote validation and FAR commit - | -terminal publication or approval completion - | -WellKnownPipelineSteps.Deploy completes +fusion-release-prepare -> fusion-compose + | +source DeployCompute -----------+-> fusion-readiness + | + v + fusion-publish-stage + | + v + gateway DeployCompute + | + v + fusion-publish + | + v + WellKnownPipelineSteps.Deploy ``` -Export can run in parallel with provider deployment. Readiness plus completed export feeds final settings/archive materialization, which precedes upload and stage composition. Upload may move earlier only when the final archive has no deployment-resolved input and its endpoint/provenance is already deployment-complete. If schema export itself requires deployment-only configuration, move that export after deployment/readiness or fail the resource as unsupported. Never claim a Nitro slot while waiting for compute deployment or readiness. - -The Nitro reconcile step must depend on the lowest provider deployment and readiness steps and be required by the well-known Deploy step. It must never depend on Deploy itself, because that creates a cycle. +`fusion-publish-stage` is the internal action that requests, validates, commits, and observes the +Nitro publication. The public `fusion-publish` step is a terminal completion step. The split is +required for a first release: the gateway must not start before the first FAR exists, but the named +public command must not finish until the gateway deployment terminal succeeds. -`RequiredBySteps = [WellKnownPipelineSteps.Deploy]` guarantees that reconciliation is included before Deploy completes. It does not order sibling prerequisites relative to one another. Register export with `WithPipelineStepFactory`. In `WithPipelineConfiguration`, discover concrete provider steps through `PipelineConfigurationContext.GetSteps(resource, WellKnownPipelineTags.DeployCompute)` where the provider supports it, then create explicit dependencies with `RequiredBy` from compute to readiness, export/readiness to materialization, upload to composition, and Nitro terminal status to root Deploy. These APIs are present in the repository-pinned Aspire 13.1.2 surface. There is no universal after-compute/readiness tag, so a provider adapter or explicit selector is required. +The source provider graph may run in parallel with manifest preparation and composition. Readiness +depends on both the composed FAR and every referenced source deployment terminal. It probes the +resolved production URLs and retries transient DNS, connection, request-timeout, and HTTP 5xx +failures with a bounded delay until the deployment's configured operation timeout. Responses below +HTTP 500 retain their existing accepted semantics. Deadline failures identify the source and +endpoint. -Provider-neutral readiness is not proven. A `DeployCompute` step completing may mean a resource was submitted, not that its production endpoint accepts traffic. Provider adapters must add a supported readiness/check step. Graph construction must fail if an Aspire-managed source lacks supported deploy-step discovery or production readiness. A source explicitly declared as external may participate only with an explicit readiness proof. +`WithPipelineConfiguration` selects `DeployCompute` on the declared resource when a provider uses +that shape. For Aspire 13.4 materialized targets, it follows `DeploymentTargetAnnotation` and +selects `DeployCompute` from the target resource. Infrastructure provisioning is not accepted as a +deployment terminal. Graph execution fails closed if any source or gateway terminal cannot be +proven. -If Nitro publication is intended as an application-wide finalizer, it must also depend on all unrelated release prerequisites whose failure should prevent cutover. Merely being a sibling prerequisite of Deploy does not provide that ordering. +`RequiredBySteps = [WellKnownPipelineSteps.Deploy]` attaches the public terminal to the broader +Deploy root. Explicit dependencies, not sibling membership under Deploy, enforce the order above. +No custom step depends on the Deploy root, which avoids a cycle. ## Current Nitro reconciliation flow -For each matching deploy, the reconciliation step executes even if the desired state is unchanged: - -1. Resolve the selected environment declaration, release ID, stage ownership, force/approval policy, and timeouts. -2. Run publish-time export/local validation in parallel with provider deployment where safe, then complete actual provider readiness. If export needs deployment-only configuration, run it after readiness or fail unsupported. -3. After both export and readiness, resolve final production bindings, materialize source archives, and verify settings name/provenance/image digest. -4. Reconcile each `name@sourceVersion`. Download an existing exact version and compare normalized schema, settings, and extensions. Identical is a verified no-op. Different content under the same identity is a hard collision. Upload only missing versions. -5. Acquire stage serialization for `(cloudUrl, apiId, stage)`. Reconcile any persisted/server-backed request state before requesting a new slot. -6. Request and claim the slot only after compute readiness. Re-download the latest stage FAR and every selected source archive after the slot is acquired so composition uses authoritative current inputs. -7. Compose locally. Apply the selected stage ownership policy, preserving or removing undeclared schemas as defined below, and produce the desired FAR. -8. Compare the desired FAR/intent with an existing publication for the same configuration tag. Identical is terminal success/no-op. Different intent for the same tag is a collision. -9. When approval is disabled, validate the FAR before commit and apply the explicit force policy. When approval is enabled, commit starts processing and validation/approval states are observed through the subscription. -10. Commit and subscribe until terminal success or failure. Approval-gated deployment succeeds only after terminal Nitro success. -11. Complete the Nitro reconciliation prerequisite, allowing `WellKnownPipelineSteps.Deploy` to complete. +For each matching deployment, the apply graph executes even when the desired stage state is +unchanged: + +1. Resolve the exact Aspire environment declaration, release ID, manifest path, stage, force and + approval policy, and timeouts. +2. Verify the promoted manifest, target binding, source set, and composition-tool identity. Download + each exact source from Nitro and verify its normalized digest. +3. Compose the environment-specific FAR while the provider may build and deploy the source + services. +4. Wait for every source deployment terminal, then poll its resolved production endpoint until it + responds below HTTP 500 or the operation timeout expires. +5. Acquire the Nitro slot, apply the configured validation and force policy, commit the already + composed FAR, and observe approval or processing until terminal success. +6. Deploy the gateway only after the internal stage publication succeeds. +7. Complete the public `fusion-publish` terminal only after the gateway deployment succeeds, + allowing the broader Aspire Deploy root to complete. On timeout, cancellation, or lost response, return failed/indeterminate and report the recoverable request ID. Release a claimed slot only when lifecycle-safe. An approval request can outlive a CI process, so a new invocation must resume/reconcile it rather than blindly create another publication. @@ -300,44 +263,47 @@ Choose and document one stage ownership policy: There is no transaction across the service deployment provider, artifact uploads, and Nitro stage publication. Uploads can remain orphaned. Compute can be updated while the old FAR remains active if Nitro validation, approval, or commit fails. Automatic rollback across the compute provider and Nitro is unsafe without a provider-specific, tested rollback protocol. -Source-service changes must remain backward-compatible with the old FAR during this window. Operators need a documented recovery path using the persisted request ID, release manifest, image digests, and previous FAR identity. The deployment result must report partial state precisely instead of claiming rollback. +Source-service changes must remain backward-compatible with the old FAR during this window. +Operators need a documented recovery path using the persisted request ID, release manifest, source +digests, and previous FAR identity. The deployment result must report partial state precisely +instead of claiming rollback. ## Named `aspire do` operations Use target-specific names and actual CLI options: ```bash -aspire publish --output-path ./artifacts/aspire --environment Production --non-interactive -aspire do fusion-upload --output-path ./artifacts/aspire --environment Production --non-interactive -aspire do fusion-publish --output-path ./artifacts/aspire --environment Production --non-interactive -aspire do fusion-publish --list-steps +aspire publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Release --non-interactive +aspire do fusion-upload --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Release --non-interactive +aspire do fusion-publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Production --non-interactive +aspire do fusion-publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --environment Production --list-steps ``` `aspire do` evaluates and builds the AppHost unless `--no-build` is used, and it reruns the selected step's dependencies. Deployment commands default to Production. Inspect scope with `--list-steps`. The safe reconcile command carries the same provider compute/readiness dependencies as Deploy, so it may redeploy or recheck resources. `aspire publish` remains the artifact inspection path, and upload alone does not publish a release. `fusion-publish` requires the supported compute and readiness graph before it claims a slot. -## Packaging and implementation plan +## Implementation layout -`ChilliCream.Nitro.Client` is explicitly non-packable in [`ChilliCream.Nitro.Client.csproj`](ChilliCream.Nitro.Client.csproj). Extract/move the supported client and workflow into a packable assembly or deliberately co-ship supported assemblies. A public `ChilliCream.Nitro.Aspire` package must not depend on an internal non-packable project. +The supported integration is packaged in `ChilliCream.Nitro.Aspire`. It reuses +`FusionSourceSchemaArchive` for immutable source packaging and the Nitro Fusion workflow for +download, normalized verification, composition, publication, approvals, and terminal reporting. +The local pipeline adapter owns environment selection, promoted-manifest validation, provider-step +ordering, readiness polling, and the named `fusion-upload` and `fusion-publish` commands. -| Area | Work | -| --- | --- | -| Existing Fusion Aspire | Adapt its source annotations and composition relationships into a reusable declaration model. | -| Artifact layer | Reuse `FusionSourceSchemaArchive`; add template, binding, provenance, manifest, normalization, and validation code. | -| Nitro workflow | Extract current local FAR composition and `IFusionConfigurationClient` orchestration behind a packable supported API. | -| Aspire pipeline | Add environment-filtered resources, provider adapters, readiness proof, exact step dependencies, Deploy completion prerequisite, and `do` commands. | -| State/recovery | Add server-backed request/tag lookup or durable release-state service, stage serialization, resume, collision checks, and terminal status reporting. | - -### Release-critical phases - -1. Prove pipeline wiring on Aspire 13.1.2: environment filtering, `GetSteps(...DeployCompute)`, exact dependencies, `RequiredBySteps`, `do --list-steps`, and cycle detection. -2. Refactor existing Fusion Aspire declarations and implement two-phase artifacts with provenance bound to deployed build/image digest. -3. Implement provider adapter one, including production endpoint readiness. Fail unsupported graphs closed. -4. Implement mandatory upload reconciliation and normalized duplicate policy. -5. Implement stage serialization, request resume/idempotency, authoritative re-download/local FAR composition, validation/commit, approvals, and terminal reporting. -6. Integrate reconciliation as a prerequisite of WellKnown Deploy for every environment-matched declaration. -7. Exercise partial-state and recovery behavior end to end, then upgrade/test against Aspire 13.4.6. +### Implemented release-critical phases + +1. Aspire 13.4.6 graph wiring covers exact environment selection, direct and materialized-target + `DeployCompute` discovery, explicit dependencies, `RequiredBySteps`, named `do` commands, and + cycle-safe source/publication/gateway ordering. +2. Build produces immutable source archives and a portable manifest. Upload verifies or creates + exact Nitro source identities, and apply re-downloads and verifies them on a separate runner. +3. Production endpoint readiness is bounded by `OperationTimeout`; unsupported provider graphs fail + closed. +4. Nitro publication preserves normalized duplicate policy, force, approvals, timeouts, terminal + reporting, and retry reconciliation. +5. The internal publication step precedes gateway deployment, and the public terminal is a + prerequisite of the Aspire Deploy root. ## Tests and acceptance criteria @@ -348,30 +314,26 @@ aspire do fusion-publish --list-steps | Export lifecycle | Sentinels prove `Program`, configuration, service registration, endpoint mapping, schema hooks/modules, and executor warmups run. Sentinels also prove Kestrel and normal `IHostedService` instances do not start in command mode. | | Export command | Uses argv-safe `ArgumentList`, explicit schema name, isolated/precreated output, bounded redacted output, timeout/cancellation/tree kill, and no Nitro secrets in the environment. | | Export validation | Multi-schema selection is exact; zero exit with no registered schema/no output fails; localhost settings fail final materialization; stale output never falls back; repeated identical input exports deterministically. | -| Export provenance | Configuration, TFM, RID, working directory, launch-profile policy, project/assembly and exact build/image digest match the deployed resource. Unsupported resource types fail closed. | -| Pipeline topology | Export may parallel compute; export plus actual readiness precede final materialization/upload; upload/readiness precede slot; Nitro terminal success precedes Deploy completion. Sibling prerequisites cannot race and no step depends on Deploy. | +| Export provenance | Configuration, TFM, RID, working directory, launch-profile policy, project path and SHA-256, and exported schema SHA-256 are recorded. Exact deployed image binding is future work. | +| Pipeline topology | Source deployment and actual readiness precede internal Nitro publication; Nitro terminal success precedes gateway deployment; gateway completion precedes the public `fusion-publish` terminal and Aspire Deploy completion. No custom step depends on the Deploy root. | | Provider readiness | Supported provider proves a real production endpoint, and unsupported Aspire-managed sources fail graph construction. External source requires explicit proof. | -| Materialization | Production bindings and exact image/build digest produce the final archive; settings name equals declared name. | +| Materialization | The selected composition environment produces the final settings and FAR; settings name equals the declared source name. | | Upload reconciliation | Exact normalized source identity is no-op; mismatch is collision; missing source uploads once under bounded retries. | | Composition | Acquires slot after readiness, re-downloads latest FAR and sources, applies declared stage ownership, composes locally, and validates/commits the FAR. | | Publication identity | Same release tag/identical intent is no-op; same tag/different intent fails; new release is serialized after existing stage work. | | Resume/approval | Lost response or ephemeral runner resumes by request ID/server record. Approval timeout is failed/indeterminate, and Deploy cannot succeed before terminal Nitro success. Stale approval cannot supersede a later rollout. | | Partial state | Nitro failure after compute update reports new compute plus old FAR, leaves recoverable state, and does not claim automatic rollback. | | `aspire do` | Safe reconcile includes compute/readiness dependencies. Export/upload inspection cannot masquerade as completed deployment. | -| Compatibility | Focused suite passes on repository pin 13.1.2 and on 13.4.6 before support is advertised. | +| Compatibility | Focused suite passes on the repository's Aspire 13.4.6 pin, and real `aspire do --list-steps` output proves the materialized-target graph. | Done means every matching `aspire deploy` executes Fusion reconciliation, can verified-no-op on identical desired state, and cannot complete until Nitro reaches terminal success. It also means the pipeline refuses to construct when exact compute/readiness ordering cannot be proven. -## Remaining questions +## Operational boundaries -1. Which provider is the first supported adapter, and what concrete step proves its production endpoint ready rather than merely submitted? -2. Which Nitro server lookup/idempotency API can recover request ID and phase from `(api, stage, release tag, desired digest)` on a new CI runner? -3. Can the server reject or serialize stale approvals so an older request can never become current after a newer rollout? -4. Which fields form normalized source archive equality, and can packaging APIs expose them without duplicate parsing? -5. Is authoritative stage ownership acceptable for the first integration, or is additive multi-owner composition required? -6. Which unrelated application prerequisites must Nitro cutover wait for when it acts as the global finalizer? -7. How should a same-commit rebuild derive source versions and release IDs when schema content, endpoint bindings, or image digests differ? -8. Which resources can safely opt into automatic child-process export, and which initialization hooks need explicit isolation guidance? +CI remains responsible for serializing writers to the same Nitro stage. Source-service changes must +remain compatible with the previously active FAR during the interval between source deployment and +stage publication. The integration fails closed when it cannot prove a managed resource's provider +deployment terminal, and it does not infer deletion of shared Nitro history during Aspire destroy. ## Sources diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs index a8d6a284a60..e051cedcb1a 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs @@ -1,8 +1,10 @@ +using System.Net; using System.Text.Json; using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; using HotChocolate.Fusion.Aspire; +using Microsoft.Extensions.DependencyInjection; namespace ChilliCream.Nitro.Aspire; @@ -15,7 +17,7 @@ public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() { var builder = DistributedApplication.CreateBuilder(); var nitro = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products"); nitro @@ -43,7 +45,7 @@ public void WithFusionReleaseManifest_Should_ConfigureExactParameterAndCompositi var builder = DistributedApplication.CreateBuilder(); var manifest = builder.AddParameter("fusionReleaseManifest"); var deployment = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .AddFusionDeployment("production") .WithFusionReleaseManifest(manifest) .WithCompositionEnvironment("prod"); @@ -61,7 +63,7 @@ public void WithDefaultSourceVersionFromGitCommit_Should_Fail_WhenManifestIsConf { var builder = DistributedApplication.CreateBuilder(); var deployment = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .AddFusionDeployment("production") .WithFusionReleaseManifest( builder.AddParameter("fusionReleaseManifest")); @@ -81,7 +83,7 @@ public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() var exception = Assert.Throws( () => builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl( "https://api.chillicream.com/CaseSensitivePath")); @@ -96,7 +98,7 @@ public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() { var builder = DistributedApplication.CreateBuilder(); builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products") .AddFusionDeployment("production") @@ -117,7 +119,7 @@ public void ShouldUseManifestProducer_Should_PreserveLegacyMode_WhenAnotherEnvir { var builder = DistributedApplication.CreateBuilder(); var nitro = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products"); nitro @@ -152,7 +154,7 @@ public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() { var builder = DistributedApplication.CreateBuilder(); var nitro = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products"); nitro @@ -193,7 +195,8 @@ public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() fusion-artifacts: depends=[]; requiredBy=[publish] fusion-upload: depends=[fusion-artifacts]; requiredBy=[] fusion-readiness: depends=[fusion-artifacts]; requiredBy=[] - fusion-publish: depends=[fusion-upload, fusion-readiness]; requiredBy=[deploy] + fusion-publish-stage: depends=[fusion-upload, fusion-readiness]; requiredBy=[] + fusion-publish: depends=[fusion-publish-stage]; requiredBy=[deploy] """); } @@ -218,7 +221,8 @@ public void CreateStepDefinitions_Should_IsolateManifestApplyFromBuildSteps() fusion-release-prepare: depends=[]; requiredBy=[] fusion-compose: depends=[fusion-release-prepare]; requiredBy=[] fusion-readiness: depends=[fusion-compose]; requiredBy=[] - fusion-publish: depends=[fusion-readiness]; requiredBy=[deploy] + fusion-publish-stage: depends=[fusion-readiness]; requiredBy=[] + fusion-publish: depends=[fusion-publish-stage]; requiredBy=[deploy] """); } @@ -241,6 +245,7 @@ public void CreateStepDefinitions_Should_NotReachExportGitOrUpload_WhenApplyingM .MatchInlineSnapshot( """ fusion-compose + fusion-publish-stage fusion-readiness fusion-release-prepare """); @@ -263,10 +268,157 @@ public void CreateStepDefinitions_Should_NotAttachPublishToDeploy_WhenEnvironmen fusion-artifacts: depends=[]; requiredBy=[publish] fusion-upload: depends=[fusion-artifacts]; requiredBy=[] fusion-readiness: depends=[]; requiredBy=[] - fusion-publish: depends=[fusion-readiness]; requiredBy=[] + fusion-publish-stage: depends=[fusion-readiness]; requiredBy=[] + fusion-publish: depends=[fusion-publish-stage]; requiredBy=[] """); } + [Fact] + public void WireGatewayDeployment_Should_PublishStageBeforeGatewayStarts() + { + var resource = new FusionPipelineResource("fusion-pipeline"); + var stagePublication = CreatePipelineStep( + FusionPipeline.PublishStageStepName, + resource, + "fusion"); + var gatewayDeployment = CreatePipelineStep( + "deploy-gateway", + resource, + WellKnownPipelineTags.DeployCompute); + var publication = CreatePipelineStep( + FusionPipeline.PublishStepName, + resource, + "fusion"); + publication.DependsOn(stagePublication); + + FusionPipeline.WireGatewayDeployment( + stagePublication, + publication, + [gatewayDeployment]); + + string.Join( + Environment.NewLine, + new[] { stagePublication, gatewayDeployment, publication } + .Select(step => + $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]")) + .MatchInlineSnapshot( + """ + fusion-publish-stage: depends=[] + deploy-gateway: depends=[fusion-publish-stage] + fusion-publish: depends=[fusion-publish-stage, deploy-gateway] + """); + } + + [Fact] + public void SelectResourceDeploymentSteps_Should_PreferDirectDeployComputeSteps() + { + var source = new FusionPipelineResource("products"); + var steps = new[] + { + CreatePipelineStep( + "deploy-products", + source, + WellKnownPipelineTags.DeployCompute), + CreatePipelineStep( + "provision-products", + source, + WellKnownPipelineTags.ProvisionInfrastructure) + }; + + string.Join( + Environment.NewLine, + FusionPipeline + .SelectResourceDeploymentSteps( + CreatePipelineConfigurationContext(steps), + source) + .Select(step => step.Name)) + .MatchInlineSnapshot( + """ + deploy-products + """); + } + + [Fact] + public void SelectResourceDeploymentSteps_Should_UseDeploymentTargetDeployComputeSteps() + { + var source = new FusionPipelineResource("products"); + var deploymentTarget = new FusionPipelineResource( + "products-containerapp"); + source.Annotations.Add( + new DeploymentTargetAnnotation(deploymentTarget)); + var steps = new[] + { + CreatePipelineStep( + "build-products", + source, + WellKnownPipelineTags.BuildCompute), + CreatePipelineStep( + "deploy-products", + deploymentTarget, + WellKnownPipelineTags.DeployCompute), + CreatePipelineStep( + "provision-products-containerapp", + deploymentTarget, + WellKnownPipelineTags.ProvisionInfrastructure) + }; + + string.Join( + Environment.NewLine, + FusionPipeline + .SelectResourceDeploymentSteps( + CreatePipelineConfigurationContext(steps), + source) + .Select(step => step.Name)) + .MatchInlineSnapshot( + """ + deploy-products + """); + } + + [Fact] + public void SelectResourceDeploymentSteps_Should_NotUseProvisionInfrastructureAsTerminal() + { + var source = new FusionPipelineResource("products"); + var deploymentTarget = new FusionPipelineResource( + "products-containerapp"); + source.Annotations.Add( + new DeploymentTargetAnnotation(deploymentTarget)); + var steps = new[] + { + CreatePipelineStep( + "build-products", + source, + WellKnownPipelineTags.BuildCompute), + CreatePipelineStep( + "provision-products-containerapp", + deploymentTarget, + WellKnownPipelineTags.ProvisionInfrastructure) + }; + + string.Join( + Environment.NewLine, + FusionPipeline + .SelectResourceDeploymentSteps( + CreatePipelineConfigurationContext(steps), + source) + .Select(step => step.Name)) + .MatchInlineSnapshot( + ""); + } + + [Fact] + public void EnsureResourceDeploymentOrdering_Should_Fail_WhenExternalResourceHasNoDeploymentStep() + { + var exception = Assert.Throws( + () => FusionPipeline.EnsureResourceDeploymentOrdering( + ["external-products"])); + + Assert.Equal( + "Fusion publication cannot prove compute deployment ordering for resources: " + + "external-products", + exception.Message); + } + [Fact] public void ResolveSourceSchemaSettings_Should_UseDifferentEnvironmentOverrides_WhenSourceIsShared() { @@ -620,6 +772,56 @@ public void GetTransportEndpoint_Should_Fail_WhenProductionBindingIsMissing() exception.Message); } + [Fact] + public async Task WaitForReadiness_Should_Retry_WhenEndpointIsTransientlyUnavailable() + { + using var handler = new StubHttpMessageHandler( + (attempt, _, _) => Task.FromResult( + new HttpResponseMessage( + attempt == 1 + ? HttpStatusCode.ServiceUnavailable + : HttpStatusCode.NoContent))); + using var httpClient = new HttpClient(handler); + + await FusionPipelineExecutor.WaitForReadinessAsync( + httpClient, + "products", + new Uri("https://products.example.com/graphql"), + TimeSpan.FromSeconds(1), + TimeSpan.Zero, + CancellationToken.None); + + Assert.Equal(2, handler.Attempts); + } + + [Fact] + public async Task WaitForReadiness_Should_FailWithContext_WhenDeadlineExpires() + { + using var handler = new StubHttpMessageHandler( + async (_, _, cancellationToken) => + { + await Task.Delay( + Timeout.InfiniteTimeSpan, + cancellationToken); + return new HttpResponseMessage(HttpStatusCode.NoContent); + }); + using var httpClient = new HttpClient(handler); + + var exception = await Assert.ThrowsAsync( + () => FusionPipelineExecutor.WaitForReadinessAsync( + httpClient, + "products", + new Uri("https://products.example.com/graphql"), + TimeSpan.FromMilliseconds(50), + TimeSpan.Zero, + CancellationToken.None)); + + Assert.Equal( + "Fusion source 'products' at 'https://products.example.com/graphql' " + + "did not pass its production readiness check within 00:00:00.0500000.", + exception.Message); + } + [Fact] public void ReplaceDirectoryAtomically_Should_RemoveSource_WhenSourceWasRemoved() { @@ -722,6 +924,33 @@ void Visit(string current) } } + private static PipelineStep CreatePipelineStep( + string name, + IResource resource, + string tag) + => new() + { + Name = name, + Description = name, + Resource = resource, + Tags = [tag], + Action = _ => Task.CompletedTask + }; + + private static PipelineConfigurationContext + CreatePipelineConfigurationContext( + IReadOnlyList steps) + { + var builder = DistributedApplication.CreateBuilder(); + + return new PipelineConfigurationContext + { + Services = builder.Services.BuildServiceProvider(), + Steps = steps, + Model = new DistributedApplicationModel(builder.Resources) + }; + } + private static string GetHttpUrl(JsonDocument settings) => settings.RootElement .GetProperty("transports") @@ -786,6 +1015,27 @@ public void Dispose() Directory.Delete(Path, recursive: true); } } + + private sealed class StubHttpMessageHandler( + Func< + int, + HttpRequestMessage, + CancellationToken, + Task> sendAsync) + : HttpMessageHandler + { + private int _attempts; + + public int Attempts => _attempts; + + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + => sendAsync( + Interlocked.Increment(ref _attempts), + request, + cancellationToken); + } } #pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs index 654bb049937..a98cc1135d3 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -198,7 +198,7 @@ private static DistributedApplicationModel CreateModel( .WithReference(products) .WithGraphQLSchemaComposition(); var nitro = builder - .AddNitro("nitro") + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products") .WithApiKey(apiKey); From 18f9c12ae45f3c9498df545a02a56ec31b0715e8 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:26:16 +0200 Subject: [PATCH 04/11] Use tag-based Aspire Fusion deployments --- .../FUSION_PUBLISHING.md | 410 ++---- .../FusionDeploymentResource.cs | 4 - .../FusionPipeline.cs | 190 +-- .../FusionPipelineExecutor.cs | 1113 ++++------------- .../FusionReleaseManifest.cs | 156 --- .../FusionReleaseStore.cs | 475 ------- .../IFusionPipelineExecutor.cs | 4 +- .../NitroResourceBuilderExtensions.cs | 42 - .../src/ChilliCream.Nitro.Aspire/README.md | 27 +- .../FUSION_ASPIRE_PUBLISH_DEPLOY.md | 522 ++++---- .../FusionDeploymentWorkflow.cs | 20 - .../FusionSourceSchemaDownload.cs | 3 +- .../IFusionDeploymentWorkflow.cs | 4 +- .../src/ChilliCream.Nitro.Fusion/README.md | 8 +- .../FusionPipelineTests.cs | 442 +------ .../FusionReleaseAcceptanceTests.cs | 403 +++--- .../FusionDeploymentWorkflowTests.cs | 70 +- 17 files changed, 944 insertions(+), 2949 deletions(-) delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md index 8ac6df632da..2fce04d5193 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md @@ -1,23 +1,20 @@ -# Promoting Fusion releases with Aspire +# Publishing Fusion with Aspire -This package supports a build-once, deploy-many Fusion release. One build job exports the source -schemas, creates immutable source-schema archives, uploads them to Nitro, and emits a portable -release manifest. Environment deployment jobs consume that exact manifest, download and verify the -uploaded archives, compose with the selected `schema-settings.json` environment, wait for deployed -services, and publish the resulting Fusion archive. +Fusion releases use Nitro itself as the handoff between the build job and deployment jobs. The +public commands are: -The promoted-manifest workflow is opt-in. Deployments without -`WithFusionReleaseManifest(...)` continue to use the legacy same-runner workflow. -The examples use Aspire CLI 13.4.6, whose AppHost selector is `--apphost`. +```shell +aspire do fusion-upload --environment +aspire do fusion-publish --environment +``` -## AppHost declaration +There is no release manifest parameter and no artifact upload or download between these commands. +Both invocations must evaluate the same AppHost composition and use the same `tag` value. -Declare the release ID and manifest path as external parameters. The manifest path is required only -by deployment jobs and must resolve to an absolute path. +## AppHost declaration ```csharp -var releaseId = builder.AddParameter("releaseId"); -var releaseManifest = builder.AddParameter("fusionReleaseManifest"); +var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); var nitro = builder.AddNitroTarget("nitro") @@ -25,340 +22,157 @@ var nitro = builder.AddNitroTarget("nitro") .WithApiId("products-fusion") .WithApiKey(nitroApiKey); -nitro.AddFusionDeployment("staging") - .ForEnvironment("Staging") - .ToStage("staging") - .WithCompositionEnvironment("staging") - .WithConfigurationTag(releaseId) - .WithFusionReleaseManifest(releaseManifest); - -nitro.AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithCompositionEnvironment("production") - .WithConfigurationTag(releaseId) - .WithFusionReleaseManifest(releaseManifest) - .WithApproval(waitForApproval: true); -``` - -`ForEnvironment` selects an Aspire deployment. `ToStage` selects the Nitro stage. -`WithCompositionEnvironment` selects the exact case-sensitive key under -`schema-settings.json.environments`. When it is omitted, an existing composition -`EnvironmentName` is used, then the Nitro stage name is the default. - -The source archives are shared across these declarations. For example: - -```json -{ - "name": "products", - "transports": { - "http": { - "url": "{{PRODUCTS_URL}}/graphql" - } - }, - "environments": { - "staging": { - "PRODUCTS_URL": "https://products.staging.example.com" - }, - "production": { - "PRODUCTS_URL": "https://products.example.com" - } - } -} +nitro.AddFusionDeployment("development") + .ForEnvironment("Development") + .ToStage("development") + .WithCompositionEnvironment("development") + .WithConfigurationTag(tag); + +nitro.AddFusionDeployment("test") + .ForEnvironment("Test") + .ToStage("test") + .WithCompositionEnvironment("test") + .WithConfigurationTag(tag); ``` -The release contains this settings document once. Staging and production resolve different gateway -settings while retaining the same source name, version, and content digest. +`ForEnvironment` selects the Aspire invocation. `ToStage` selects the Nitro stage. +`WithCompositionEnvironment` selects the environment block in each source's +`schema-settings.json`. `WithConfigurationTag` supplies both the source version and Fusion +configuration tag. -Promoted releases use the release ID as the source version. The legacy -`WithDefaultSourceVersionFromGitCommit()` option applies only to deployments without a release -manifest; apply never invokes Git to rediscover a promoted version. +The effective source name is `SourceSchemaName` when explicitly declared, otherwise the Aspire +resource name. Effective names must be unique and portable path segments. -## Build and upload +## Upload -The build job runs the named upload step: +The build job checks out the source and runs one selected, real deployment environment: -```bash -export Parameters__releaseId="$RELEASE_ID" +```shell +export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-upload \ - --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ - --environment Release \ - --output-path "$RUNNER_TEMP/fusion-release" \ + --apphost ./src/AppHost/AppHost.csproj \ + --environment Development \ --non-interactive ``` -`fusion-upload` depends on `fusion-artifacts`. This single command therefore: +`fusion-upload` depends on `fusion-artifacts`. For every source in the selected environment's +current AppHost composition it: -1. discovers the composition source schemas; -2. reads checked-in schema files or executes explicitly configured command-line exporters once; -3. creates one immutable source archive per source; -4. computes both the archive SHA-256 and the normalized Fusion content SHA-256; -5. reconciles each source version into every distinct declared Nitro API target; -6. writes the final manifest only after all uploads are verified. - -The final release is: - -```text -/ - fusion/ - releases/ - / - fusion-release.json - fusion-release.draft.json - sources/ - products/ - .zip - reviews/ - .zip -``` +1. acquires and validates the schema and settings; +2. creates the portable source archive; +3. assigns the exact version `name@tag`; +4. rejects loopback endpoints for the selected composition environment; and +5. reconciles that immutable version on the selected Nitro API. -Promote only `fusion/releases//fusion-release.json` as the deployable CI artifact. The -source ZIPs and draft manifest are local build evidence used by `fusion-upload`; manifest apply -never reads them. Instead, apply downloads every exact source version from Nitro and verifies its -normalized content digest. The final manifest contains portable relative archive paths so the full -release directory can still be retained for audit purposes, but those paths are not apply inputs. -It never contains credentials, project paths, working directories, stages, Aspire environments, or -timestamps. +If several deployment environments use the same Nitro cloud URL, API ID, source set, and tag, one +upload serves all of them. Run `fusion-upload` once per distinct Nitro API target otherwise. -`aspire publish` remains local-only. It runs `fusion-artifacts` and emits the archives plus draft -manifest, but it does not resolve a Nitro credential or perform an upload. Automation normally uses -`aspire do fusion-upload` directly because the artifact step is already its dependency. +`aspire publish` remains an artifact-only root. It can create local Fusion source artifacts but it +does not resolve a Nitro credential or mutate Nitro. Automation normally invokes `fusion-upload` +directly because the artifact step is already its dependency. -## Deploy on a separate runner +## Publish -Download the CI artifact into any read-only or otherwise stable directory. Pass the exact final -manifest path through the configured parameter and use a separate output directory for the apply: +Deployment jobs check out the same AppHost revision and use the same tag, but they do not need the +source schema files or a build-job artifact: -```bash -export Parameters__releaseId="$RELEASE_ID" -export Parameters__fusionReleaseManifest="$RUNNER_TEMP/promoted-release/fusion-release.json" +```shell +export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-publish \ - --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ - --environment Staging \ - --output-path "$RUNNER_TEMP/aspire-apply/Staging" \ + --apphost ./src/AppHost/AppHost.csproj \ + --environment Development \ + --output-path "$RUNNER_TEMP/aspire-apply/Development" \ --non-interactive ``` -Production uses the identical downloaded manifest: +Publish infers the complete, sorted source-name set from the current AppHost. Before source compute +is changed it downloads every exact `name@tag` from the selected Nitro API. A missing source fails +the deployment. Download records an atomic apply state binding the tag, Nitro target, complete +source set, archive paths, and canonical content digests. -```bash -aspire do fusion-publish \ - --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ - --environment Production \ - --output-path "$RUNNER_TEMP/aspire-apply/Production" \ - --non-interactive +Composition revalidates every downloaded archive and resolves its settings with the current +AppHost composition options and selected composition environment. Readiness and publication +revalidate the apply state and composed FAR digest. Publish never exports a schema, reads a source +checkout, invokes Git, or calls the source-upload API. + +## Ordering + +The build-side graph is independent of deployment: + +```text +fusion-artifacts -> fusion-upload ``` -The executor reads exactly `Parameters__fusionReleaseManifest`; it does not scan or reuse -`--output-path` to discover a release. It validates each manifest archive path as a portable -release-relative path but does not open or copy that local archive during apply. Downloaded sources, -apply state, and the composed FAR are written beneath the fresh apply output: +The deployment graph is: ```text -/ - fusion/ - apply/ - / - fusion-apply.json - fusion-configuration.far - sources/ +fusion-download -> source DeployCompute -> fusion-readiness + \-> fusion-compose ------/ + +fusion-readiness + -> fusion-publish-stage + -> gateway DeployCompute + -> fusion-publish ``` -The downloaded release manifest is never regenerated or overwritten. A deployment runner still -needs to evaluate the same pinned AppHost model so Aspire can discover the Fusion declarations and -provider deployment steps. Supply either a buildable AppHost checkout or the same promoted AppHost -binary and locked dependencies with the appropriate Aspire `--no-build` workflow. A compute -provider may still require its own workload source, container context, or promoted image. The -Fusion apply graph itself does not need the source-schema files, the checkout used for schema -export, Git metadata, a schema exporter, or the build runner's local source ZIPs. +`fusion-download` is a fail-before-compute preflight. `fusion-publish-stage` is internal. The public +`fusion-publish` step is terminal and completes only after gateway deployment. The broader +`aspire deploy` root requires the same terminal step. + +The Aspire adapter recognizes direct `DeployCompute` steps and deployment-target contributed +`DeployCompute` steps. It fails closed when it cannot prove source and gateway compute ordering. -## GitHub Actions promotion and writer serialization +## CI shape -This example uploads only the final manifest, downloads it into a different runner path, and -serializes each Nitro stage writer: +Use one immutable `RELEASE_TAG` for every job in a rollout: ```yaml +env: + RELEASE_TAG: ${{ github.sha }} + jobs: - fusion-build: - runs-on: ubuntu-latest + fusion-upload: env: - RELEASE_ID: ${{ github.sha }} - Parameters__releaseId: ${{ github.sha }} + Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} steps: - uses: actions/checkout@v4 - - name: Build and upload immutable Fusion sources - run: >- + - run: >- aspire do fusion-upload - --apphost ./MyApp.AppHost/MyApp.AppHost.csproj - --environment Release - --output-path "$RUNNER_TEMP/fusion-release" + --apphost ./src/AppHost/AppHost.csproj + --environment Development --non-interactive - - uses: actions/upload-artifact@v4 - with: - name: fusion-release-manifest - path: ${{ runner.temp }}/fusion-release/fusion/releases/${{ env.RELEASE_ID }}/fusion-release.json - if-no-files-found: error - - fusion-deploy: - needs: fusion-build - runs-on: ubuntu-latest - strategy: - matrix: - include: - - environment: Staging - writer-key: api.chillicream.com|products-fusion|staging - - environment: Production - writer-key: api.chillicream.com|products-fusion|production - concurrency: - group: fusion-${{ matrix.writer-key }} - cancel-in-progress: false + + deploy-development: + needs: fusion-upload env: - Parameters__releaseId: ${{ github.sha }} + Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 - with: - name: fusion-release-manifest - path: ${{ runner.temp }}/promoted-release - - name: Publish promoted Fusion release - env: - Parameters__fusionReleaseManifest: ${{ runner.temp }}/promoted-release/fusion-release.json - run: >- + - run: >- aspire do fusion-publish - --apphost ./MyApp.AppHost/MyApp.AppHost.csproj - --environment "${{ matrix.environment }}" - --output-path "$RUNNER_TEMP/aspire-apply/${{ matrix.environment }}" + --apphost ./src/AppHost/AppHost.csproj + --environment Development --non-interactive ``` -Each `writer-key` must be a stable encoding of the canonical `(Nitro cloud origin, API ID, stage)` -tuple. `cancel-in-progress: false` queues writers instead of cancelling an in-flight approval. This -package rejects duplicate declarations for the same canonical target and stage within one selected -AppHost environment. It does not provide a distributed lock across workflow runs, repositories, or -other deployment systems. Keep the CI concurrency control, and require server-side Nitro -serialization or an external coordinator when writers can originate outside that one concurrency -domain. - -## Command contract - -| Command | Promoted-manifest behavior | Nitro effect | -| --- | --- | --- | -| `aspire publish` | Exports once and writes source archives plus a draft manifest. | None. | -| `aspire do fusion-upload` | Runs artifacts, reconciles immutable versions into all declared targets, and finalizes `fusion-release.json`. | Source upload only. | -| `aspire do fusion-publish` | Reads the exact manifest parameter, downloads and verifies sources, deploys the sources, composes for the selected environment, waits for readiness, publishes the stage, deploys the gateway, and completes. | Stage publication only, never source upload. | -| `aspire deploy --environment ` | Runs provider deployment and requires the same manifest apply graph to complete. | The same ordered source deployment, stage publication, and gateway deployment. | - -## Pipeline graphs - -Build and upload: - -```text -fusion-artifacts -> fusion-upload - | - +-----------------> Aspire Publish -``` - -Promoted-manifest apply: - -```text -fusion-release-prepare -> fusion-compose - | -source DeployCompute -----------+-> fusion-readiness - | - v - fusion-publish-stage - | - v - gateway DeployCompute - | - v - fusion-publish - | - v - Aspire Deploy -``` +There is intentionally no artifact handoff. Use stable concurrency keys per Nitro API for upload +and per Nitro stage for publish. Queue writers instead of cancelling them. -`fusion-publish` has no transitive dependency on `fusion-artifacts` or `fusion-upload` in manifest -mode. Consequently neither the named publish step nor `aspire deploy` can invoke source export, -archive materialization from a checkout, Git version discovery, or source upload. -The named publish step includes the provider build, push, and deploy graph for every referenced -source and for the gateway. The order is source deployment, readiness, internal Nitro stage -publication, gateway deployment, then the terminal public `fusion-publish` step. `aspire deploy` -is the broader AppHost root and requires that same terminal step. +## Failure and retry behavior -Legacy apply, used only when no manifest parameter is configured: - -```text -fusion-artifacts -> fusion-upload -fusion-artifacts -> fusion-readiness <- source DeployCompute -fusion-upload + fusion-readiness -> fusion-publish-stage -fusion-publish-stage -> gateway DeployCompute -gateway DeployCompute -> fusion-publish -> Aspire Deploy -``` +- An existing `name@tag` with identical canonical content is a successful reconcile. +- An existing `name@tag` with different content is an immutable-version collision and fails. +- A missing exact source during publish fails before source compute deployment. +- A changed AppHost source set, tag, target, downloaded archive, composition environment, or FAR + fails apply-state validation. +- A transient source endpoint is polled until the configured operation timeout. +- Approval rejection, timeout, failed commit, or unverified terminal Nitro state fails publication. -Do not mix manifest and legacy deployments for the same Aspire environment. - -## Manifest integrity and target binding - -`fusion-release.json` is written atomically after upload verification. It binds: - -- the manifest format version, release ID, and exact Fusion composition tool version; -- composition options and their digest; -- the complete source set digest; -- each source name, immutable version, relative archive path, archive SHA-256, and normalized content - SHA-256; -- every Nitro cloud URL and API ID to which the complete source set was uploaded. - -Apply rejects unsupported formats, invalid or duplicate entries, path traversal, composition or -source-set digest changes, an unexpected release ID, an incompatible composition tool version, and -a target that is not recorded in the manifest. It downloads each exact `name@version` from Nitro -and verifies normalized content before writing its private apply cache. The cache is verified again -before composition. Build and apply should use the same promoted AppHost binary and locked package -set; the manifest's exact composition-tool identity makes a mismatched apply fail before download -or composition. - -The raw archive digest protects the build artifact. The normalized content digest protects the -Nitro identity even if ZIP container bytes differ while schema, settings, and extensions remain -identical. - -The manifest itself is immutable. Retrying the same release ID against an output directory that -already contains its final manifest intentionally reuses that final manifest before inspecting the -current checkout. This makes a lost-response retry stable even if the working tree changed. A new -checkout, source set, composition configuration, tool version, or intended release requires a new -release ID. A different manifest cannot overwrite an existing `fusion-release.json` at the same -release path, and an existing final manifest cannot be extended to a different target set. - -## Publication behavior - -Composition runs before readiness so environment variables in shared source settings are resolved -first. Readiness reads the composed FAR gateway settings and probes the final -`sourceSchemas.*.transports.http.url` values after every referenced source's provider deployment -terminal. Transient DNS, connection, request-timeout, and HTTP 5xx failures are retried with a -bounded delay until the deployment's operation timeout expires. Any response below HTTP 500 is -accepted, preserving the endpoint's ability to use authentication or method-specific status codes -as liveness evidence. A deadline failure reports both the source and endpoint. - -The adapter prefers a `DeployCompute` step associated directly with the resource, then resolves -Aspire 13.4's materialized deployment target and selects its `DeployCompute` step. It applies the -same rule to the gateway. Publication fails closed when any managed source or gateway has no -provable deployment terminal. Infrastructure provisioning alone is not treated as a completed -compute deployment. The gateway deployment depends on successful internal stage publication, and -the public `fusion-publish` terminal depends on the gateway deployment. - -Publication then uses the already composed FAR and the exact source references from the manifest. -It preserves the configured approval, force, operation-timeout, and approval-timeout behavior. -The stage reaches a verified terminal Nitro result or the Aspire step fails. - -Aspire environment selection remains exact and case-sensitive. `fusion-publish` and `aspire deploy` -perform no Nitro stage publication when the selected environment has no matching Fusion -deployment. The dedicated build invocation is intentional exception: when promoted-manifest -declarations exist, `aspire do fusion-upload --environment Release` can use an otherwise unmatched -build environment to create and upload the shared source set to every distinct declared Nitro API -target. Running `aspire deploy --environment Release` does not attach that build-only upload step to -Deploy. +The tag is the rollout identity. Use a new tag whenever the source content or intended rollout +changes, and reuse the same tag only for retries of that identical rollout. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs index 629be45a00e..849ca7e2ae2 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs @@ -22,10 +22,6 @@ public sealed class FusionDeploymentResource( internal ParameterResource? ConfigurationTagParameter { get; set; } - internal ParameterResource? FusionReleaseManifestParameter { get; set; } - - internal bool UseGitCommitAsSourceVersion { get; set; } - internal bool WaitForApproval { get; set; } internal bool Force { get; set; } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs index e44eaa41885..ea8ebd0fee3 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs @@ -12,8 +12,8 @@ namespace ChilliCream.Nitro.Aspire; internal static class FusionPipeline { internal const string ArtifactsStepName = "fusion-artifacts"; - internal const string PrepareReleaseStepName = "fusion-release-prepare"; - internal const string ComposeReleaseStepName = "fusion-compose"; + internal const string DownloadStepName = "fusion-download"; + internal const string ComposeStepName = "fusion-compose"; internal const string ReadinessStepName = "fusion-readiness"; internal const string UploadStepName = "fusion-upload"; internal const string PublishStageStepName = "fusion-publish-stage"; @@ -67,45 +67,6 @@ internal static IReadOnlyList SelectDeployments( return deployments; } - internal static IReadOnlyList - GetManifestDeployments(DistributedApplicationModel model) - { - var deployments = model.Resources - .OfType() - .Where(deployment => - deployment.FusionReleaseManifestParameter is not null) - .ToArray(); - - foreach (var deployment in deployments) - { - ValidateDeclaration(deployment); - } - - return deployments; - } - - internal static bool ShouldUseManifestProducer( - DistributedApplicationModel model, - string environmentName) - { - var selected = SelectDeployments(model, environmentName); - if (selected.Count > 0) - { - var manifestCount = selected.Count(deployment => - deployment.FusionReleaseManifestParameter is not null); - if (manifestCount > 0 && manifestCount != selected.Count) - { - throw new InvalidOperationException( - $"Aspire environment '{environmentName}' mixes " - + "promoted-manifest and legacy Fusion deployments."); - } - - return manifestCount > 0; - } - - return GetManifestDeployments(model).Count > 0; - } - internal static IResourceWithEndpoints GetCompositionResource( DistributedApplicationModel model) { @@ -131,40 +92,14 @@ private static IEnumerable CreateSteps( context.PipelineContext.Model, environment); - topology.EnvironmentName = environment; topology.HasDeployments = deployments.Count > 0; - topology.BuildOnlyManifestProducer = deployments.Count == 0 - && GetManifestDeployments( - context.PipelineContext.Model).Count > 0; - topology.UseManifestApply = deployments.Count > 0 - && deployments.All( - deployment => - deployment.FusionReleaseManifestParameter is not null); - - if (deployments.Any( - deployment => - deployment.FusionReleaseManifestParameter is not null) - && !topology.UseManifestApply) - { - throw new InvalidOperationException( - $"Aspire environment '{environment}' mixes promoted-manifest " - + "and legacy Fusion deployments."); - } return CreateStepDefinitions(context.Resource, topology); } internal static PipelineStep[] CreateStepDefinitionsForTest( - IResource resource, - bool useManifestApply = false, - bool buildOnlyManifestProducer = false) - => CreateStepDefinitions( - resource, - new FusionPipelineTopology - { - UseManifestApply = useManifestApply, - BuildOnlyManifestProducer = buildOnlyManifestProducer - }); + IResource resource) + => CreateStepDefinitions(resource, new FusionPipelineTopology()); private static PipelineStep[] CreateStepDefinitions( IResource resource, @@ -190,102 +125,38 @@ private static PipelineStep[] CreateStepDefinitions( } }; - if (topology.UseManifestApply) - { - return - [ - .. buildSteps, - new PipelineStep - { - Name = PrepareReleaseStepName, - Description = "Download and verify a promoted Fusion release.", - Resource = resource, - Action = ExecutePrepareReleaseAsync - }, - new PipelineStep - { - Name = ComposeReleaseStepName, - Description = "Compose the promoted Fusion release for this environment.", - Resource = resource, - DependsOnSteps = [PrepareReleaseStepName], - Action = ExecuteComposeReleaseAsync - }, - new PipelineStep - { - Name = ReadinessStepName, - Description = "Verify deployed Fusion source services are ready.", - Resource = resource, - DependsOnSteps = [ComposeReleaseStepName], - Action = stepContext => ExecuteReadinessAsync(stepContext, topology) - }, - new PipelineStep - { - Name = PublishStageStepName, - Description = "Publish the promoted Fusion configuration to Nitro.", - Resource = resource, - DependsOnSteps = [ReadinessStepName], - Action = ExecutePublishAsync - }, - new PipelineStep - { - Name = PublishStepName, - Description = "Complete the promoted Fusion deployment.", - Resource = resource, - DependsOnSteps = [PublishStageStepName], - RequiredBySteps = [WellKnownPipelineSteps.Deploy], - Action = _ => Task.CompletedTask - } - ]; - } - - if (topology.BuildOnlyManifestProducer) - { - return - [ - .. buildSteps, - new PipelineStep - { - Name = ReadinessStepName, - Description = "Verify deployed Fusion source services are ready.", - Resource = resource, - Action = stepContext => ExecuteReadinessAsync(stepContext, topology) - }, - new PipelineStep - { - Name = PublishStageStepName, - Description = "Publish a Fusion configuration to Nitro.", - Resource = resource, - DependsOnSteps = [ReadinessStepName], - Action = ExecutePublishAsync - }, - new PipelineStep - { - Name = PublishStepName, - Description = "Complete the Fusion deployment.", - Resource = resource, - DependsOnSteps = [PublishStageStepName], - Action = _ => Task.CompletedTask - } - ]; - } - return [ .. buildSteps, new PipelineStep + { + Name = DownloadStepName, + Description = "Download exact Fusion source schema versions from Nitro.", + Resource = resource, + Action = ExecuteDownloadAsync + }, + new PipelineStep + { + Name = ComposeStepName, + Description = "Compose the Fusion configuration for this environment.", + Resource = resource, + DependsOnSteps = [DownloadStepName], + Action = ExecuteComposeAsync + }, + new PipelineStep { Name = ReadinessStepName, Description = "Verify deployed Fusion source services are ready.", Resource = resource, - DependsOnSteps = [ArtifactsStepName], + DependsOnSteps = [ComposeStepName], Action = stepContext => ExecuteReadinessAsync(stepContext, topology) }, new PipelineStep { Name = PublishStageStepName, - Description = "Compose and publish the Fusion configuration to Nitro.", + Description = "Publish the Fusion configuration to Nitro.", Resource = resource, - DependsOnSteps = [UploadStepName, ReadinessStepName], + DependsOnSteps = [ReadinessStepName], Action = ExecutePublishAsync }, new PipelineStep @@ -313,6 +184,8 @@ private static void ConfigureSteps( var sources = GraphQLResourceModel.GetReferencedSourceSchemas( composition, context.Model); + var download = context.Steps.Single( + step => step.Name == DownloadStepName); var readiness = context.Steps.Single( step => step.Name == ReadinessStepName); var stagePublication = context.Steps.Single( @@ -336,6 +209,7 @@ private static void ConfigureSteps( foreach (var computeStep in computeSteps) { + computeStep.DependsOn(download); readiness.DependsOn(computeStep); } } @@ -428,11 +302,11 @@ internal static void EnsureResourceDeploymentOrdering( private static Task ExecuteUploadAsync(PipelineStepContext context) => GetExecutor(context).UploadAsync(context); - private static Task ExecutePrepareReleaseAsync(PipelineStepContext context) - => GetExecutor(context).PrepareReleaseAsync(context); + private static Task ExecuteDownloadAsync(PipelineStepContext context) + => GetExecutor(context).DownloadAsync(context); - private static Task ExecuteComposeReleaseAsync(PipelineStepContext context) - => GetExecutor(context).ComposeReleaseAsync(context); + private static Task ExecuteComposeAsync(PipelineStepContext context) + => GetExecutor(context).ComposeAsync(context); private static Task ExecutePublishAsync(PipelineStepContext context) => GetExecutor(context).PublishAsync(context); @@ -498,14 +372,8 @@ private static void ValidateDeclaration( private sealed class FusionPipelineTopology { - public string? EnvironmentName { get; set; } - public bool HasDeployments { get; set; } - public bool UseManifestApply { get; set; } - - public bool BuildOnlyManifestProducer { get; set; } - public HashSet ResourcesWithoutCompute { get; } = new(StringComparer.Ordinal); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs index b7696458f74..f4f37b97c72 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Net; using System.Security.Cryptography; using System.Text.Json; @@ -23,6 +22,11 @@ internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor { private static readonly TimeSpan s_readinessRetryDelay = TimeSpan.FromSeconds(1); + private static readonly JsonSerializerOptions s_jsonOptions = new( + JsonSerializerDefaults.Web) + { + WriteIndented = true + }; public static FusionPipelineExecutor Instance { get; } = new(); @@ -31,16 +35,6 @@ public async Task CreateArtifactsAsync(PipelineStepContext context) var environment = context.Services .GetRequiredService() .EnvironmentName; - if (FusionPipeline.ShouldUseManifestProducer( - context.Model, - environment)) - { - await CreateReleaseArtifactsAsync( - FusionPipeline.GetManifestDeployments(context.Model), - context); - return; - } - var deployments = FusionPipeline.SelectDeployments( context.Model, environment); @@ -61,6 +55,8 @@ await CreateReleaseArtifactsAsync( $"Fusion composition resource '{composition.Name}' has no declared source schemas."); } + _ = GetSourceNames(context.Model); + var output = context.Services .GetRequiredService() .GetOutputDirectory(); @@ -89,16 +85,10 @@ public async Task VerifyReadinessAsync(PipelineStepContext context) return; } - if (deployments.All( - deployment => - deployment.FusionReleaseManifestParameter is not null)) - { - await VerifyReleaseReadinessAsync( - deployments, - context); - return; - } - + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var composition = GraphQLResourceModel.GetComposition( + compositionResource); var output = context.Services .GetRequiredService() .GetOutputDirectory(); @@ -106,38 +96,48 @@ await VerifyReleaseReadinessAsync( { Timeout = TimeSpan.FromSeconds(10) }; - var compositionResource = FusionPipeline.GetCompositionResource( - context.Model); - var composition = GraphQLResourceModel.GetComposition( - compositionResource); - foreach (var deployment in deployments) { - var compositionEnvironment = ResolveCompositionEnvironment( + var applyDirectory = GetApplyDirectory(output, deployment); + var state = await ReadApplyStateAsync( + applyDirectory, + context.CancellationToken); + await ValidateApplyStateAsync( + state, + deployment, + context, + context.CancellationToken); + ValidateCompositionState( + state, deployment, composition.Settings); - var deploymentDirectory = GetDeploymentDirectory(output, deployment); + var farPath = ResolveApplyPath( + applyDirectory, + state.FusionArchivePath + ?? throw new InvalidOperationException( + "The downloaded Fusion sources have not been composed.")); + await VerifyFileDigestAsync( + farPath, + state.FusionArchiveSha256 + ?? throw new InvalidOperationException( + "The composed Fusion archive has no digest."), + "composed Fusion archive", + context.CancellationToken); + using var archive = FusionArchive.Open(farPath); + using var configuration = + await archive.TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + context.CancellationToken) + ?? throw new InvalidDataException( + "The composed Fusion archive contains no gateway configuration."); - foreach (var sourceDirectory in Directory.EnumerateDirectories( - Path.Combine(deploymentDirectory, "sources"))) + foreach (var (name, endpoint) in GetTransportEndpoints( + configuration.Settings)) { - var settingsPath = Path.Combine( - sourceDirectory, - "schema-settings.template.json"); - using var settings = JsonDocument.Parse( - await File.ReadAllTextAsync( - settingsPath, - context.CancellationToken)); - using var resolvedSettings = - AspireCompositionHelper.ResolveSourceSchemaSettings( - settings, - compositionEnvironment); - var endpoint = GetTransportEndpoint(resolvedSettings); RejectLoopbackEndpoint(endpoint); - await WaitForReadinessAsync( httpClient, - Path.GetFileName(sourceDirectory), + name, endpoint, deployment.OperationTimeout, s_readinessRetryDelay, @@ -148,19 +148,6 @@ await WaitForReadinessAsync( public async Task UploadAsync(PipelineStepContext context) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - if (FusionPipeline.ShouldUseManifestProducer( - context.Model, - environment)) - { - await UploadReleaseAsync( - FusionPipeline.GetManifestDeployments(context.Model), - context); - return; - } - var artifacts = await MaterializeArchivesAsync(context); if (artifacts.Count == 0) { @@ -190,9 +177,14 @@ await workflow.ReconcileSourceSchemaAsync( } } - public async Task PrepareReleaseAsync(PipelineStepContext context) + public async Task DownloadAsync(PipelineStepContext context) { - var deployments = GetSelectedManifestDeployments(context); + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); if (deployments.Count == 0) { return; @@ -203,37 +195,13 @@ public async Task PrepareReleaseAsync(PipelineStepContext context) var output = context.Services .GetRequiredService() .GetOutputDirectory(); - var composition = FusionPipeline.GetCompositionResource( - context.Model); - var providerSourceNames = GraphQLResourceModel - .GetReferencedSourceSchemas(composition, context.Model) - .Select(source => - source.Declaration.SourceSchemaName - ?? source.Resource.Name) - .ToArray(); + var sourceNames = GetSourceNames(context.Model); foreach (var deployment in deployments) { - var manifestPath = await ResolveManifestPathAsync( + var tag = await ResolveConfigurationTagAsync( deployment, context.CancellationToken); - var manifest = await FusionReleaseStore.ReadFinalAsync( - manifestPath, - context.CancellationToken); - FusionReleaseCompatibility.ValidateCompositionToolVersion( - manifest); - var manifestSha256 = await ComputeFileDigestAsync( - manifestPath, - context.CancellationToken); - ValidateManifestSourceNames( - manifest, - providerSourceNames); - await ValidateReleaseIdAsync( - deployment, - manifest, - context.CancellationToken); - GetReleaseTarget(manifest, deployment); - var target = await ResolveTargetAsync( deployment, context, @@ -247,26 +215,38 @@ await ValidateReleaseIdAsync( try { - var sources = new List( - manifest.Sources.Count); + var sources = new List(sourceNames.Count); - foreach (var source in manifest.Sources) + foreach (var sourceName in sourceNames) { var download = await workflow.DownloadSourceSchemaAsync( target, new FusionSourceSchemaVersion( - source.Name, - source.Version), - source.ContentSha256, + sourceName, + tag), context.CancellationToken) ?? throw new InvalidOperationException( - $"Promoted Fusion source '{source.Name}' version " - + $"'{source.Version}' does not exist on target " + $"Fusion source '{sourceName}' version " + + $"'{tag}' does not exist on target " + $"'{deployment.Nitro.ApiId}'."); + if (!string.Equals( + download.Name, + sourceName, + StringComparison.Ordinal) + || !string.Equals( + download.Version, + tag, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Nitro returned Fusion source '{download.Name}@{download.Version}' " + + $"when '{sourceName}@{tag}' was requested."); + } + var relativePath = Path.Combine( "sources", - source.Name, - $"{source.Version}.zip"); + sourceName, + $"{tag}.zip"); var archivePath = Path.Combine( temporaryDirectory, relativePath); @@ -274,12 +254,27 @@ await ValidateReleaseIdAsync( Path.GetDirectoryName(archivePath)!); await File.WriteAllBytesAsync( archivePath, - download.Archive, + download.Archive.ToArray(), context.CancellationToken); + var contentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + archivePath, + sourceName, + context.CancellationToken); + if (!string.Equals( + contentSha256, + download.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Downloaded Fusion source '{sourceName}@{tag}' content " + + "does not match its canonical digest."); + } + sources.Add( - new FusionReleaseApplySource( - source.Name, - source.Version, + new FusionApplySource( + sourceName, + tag, relativePath.Replace( Path.DirectorySeparatorChar, '/'), @@ -290,10 +285,8 @@ await WriteJsonAtomicallyAsync( Path.Combine( temporaryDirectory, "fusion-apply.json"), - new FusionReleaseApplyState( - manifestPath, - manifestSha256, - manifest.ReleaseId, + new FusionApplyState( + tag, deployment.Nitro.CloudUrl!, deployment.Nitro.ApiId!, CompositionEnvironment: null, @@ -313,9 +306,14 @@ await WriteJsonAtomicallyAsync( } } - public async Task ComposeReleaseAsync(PipelineStepContext context) + public async Task ComposeAsync(PipelineStepContext context) { - var deployments = GetSelectedManifestDeployments(context); + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); if (deployments.Count == 0) { return; @@ -335,15 +333,11 @@ public async Task ComposeReleaseAsync(PipelineStepContext context) var state = await ReadApplyStateAsync( applyDirectory, context.CancellationToken); - await VerifyFileDigestAsync( - state.ManifestPath, - state.ManifestSha256, - "promoted Fusion release manifest", - context.CancellationToken); - var manifest = await FusionReleaseStore.ReadFinalAsync( - state.ManifestPath, + await ValidateApplyStateAsync( + state, + deployment, + context, context.CancellationToken); - ValidateApplyState(state, manifest, deployment); var compositionEnvironment = ResolveCompositionEnvironment( deployment, @@ -351,8 +345,6 @@ await VerifyFileDigestAsync( var farPath = Path.Combine( applyDirectory, "fusion-configuration.far"); - var settings = manifest.Composition.Settings - .ToCompositionSettings(compositionEnvironment); foreach (var source in state.Sources) { @@ -391,7 +383,7 @@ await FusionSourceSchemaContent.ComputeSha256Async( source.ArchivePath))) .ToArray(), compositionEnvironment, - settings, + currentComposition.Settings, logger, context.CancellationToken)) { @@ -424,332 +416,20 @@ public async Task PublishAsync(PipelineStepContext context) var deployments = FusionPipeline.SelectDeployments( context.Model, environment); - if (deployments.Count > 0 - && deployments.All( - deployment => - deployment.FusionReleaseManifestParameter is not null)) - { - await PublishReleaseAsync(deployments, context); - return; - } - - var artifacts = await MaterializeArchivesAsync(context); - if (artifacts.Count == 0) - { - return; - } - - var workflow = context.Services.GetRequiredService(); - var compositionResource = FusionPipeline.GetCompositionResource(context.Model); - var composition = GraphQLResourceModel.GetComposition(compositionResource); - - foreach (var group in artifacts.GroupBy(artifact => artifact.Deployment)) - { - var deployment = group.Key; - var target = await ResolveTargetAsync( - deployment, - context, - context.CancellationToken); - var releaseId = await ResolveConfigurationTagAsync( - deployment, - context.CancellationToken); - var farPath = Path.Combine( - Path.GetDirectoryName(group.First().ArchivePath)!, - $"{releaseId}.far"); - var compositionSettings = composition.Settings; - compositionSettings.EnvironmentName = - ResolveCompositionEnvironment( - deployment, - compositionSettings); - - await ComposeAsync( - farPath, - group.ToArray(), - compositionSettings, - context, - context.CancellationToken); - - await workflow.PublishAsync( - new FusionPublicationRequest( - target, - deployment.StageName!, - releaseId, - group - .Select(artifact => - new FusionSourceSchemaVersion( - artifact.Name, - artifact.Version)) - .ToArray(), - deployment.WaitForApproval, - deployment.Force, - deployment.OperationTimeout, - deployment.ApprovalTimeout), - farPath, - context.CancellationToken); - - await WriteDeploymentManifestAsync( - deployment, - releaseId, - group.ToArray(), - context, - context.CancellationToken); - } - } - - private static async Task CreateReleaseArtifactsAsync( - IReadOnlyList deployments, - PipelineStepContext context) - { - var releaseId = await ResolveSharedReleaseIdAsync( - deployments, - context.CancellationToken); - var compositionResource = FusionPipeline.GetCompositionResource( - context.Model); - var composition = GraphQLResourceModel.GetComposition( - compositionResource); - var sources = GraphQLResourceModel.GetReferencedSourceSchemas( - compositionResource, - context.Model); - - if (sources.Count == 0) - { - throw new InvalidOperationException( - $"Fusion composition resource '{compositionResource.Name}' " - + "has no declared source schemas."); - } - - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); - var releaseDirectory = GetReleaseDirectory(output, releaseId); - var finalManifestPath = Path.Combine( - releaseDirectory, - "fusion-release.json"); - if (File.Exists(finalManifestPath)) + if (deployments.Count == 0) { - var existing = await FusionReleaseStore.ReadFinalAsync( - Path.GetFullPath(finalManifestPath), - context.CancellationToken); - if (!string.Equals( - existing.ReleaseId, - releaseId, - StringComparison.Ordinal)) - { - throw new InvalidOperationException( - $"Existing Fusion release '{existing.ReleaseId}' does not " - + $"match requested release '{releaseId}'."); - } - return; } - var releaseParent = Path.GetDirectoryName(releaseDirectory)!; - Directory.CreateDirectory(releaseParent); - var temporaryDirectory = Path.Combine( - releaseParent, - $".{releaseId}.{Guid.NewGuid():N}.tmp"); - - try - { - var inputsDirectory = Path.Combine( - temporaryDirectory, - ".inputs"); - Directory.CreateDirectory(inputsDirectory); - var releaseSources = new List( - sources.Count); - - foreach (var source in sources) - { - var name = await CreateSourceArtifactsAsync( - source, - inputsDirectory, - context.CancellationToken); - var sourceDirectory = Path.Combine( - inputsDirectory, - name); - var archiveRelativePath = Path.Combine( - "sources", - name, - $"{releaseId}.zip") - .Replace(Path.DirectorySeparatorChar, '/'); - var archivePath = Path.Combine( - temporaryDirectory, - archiveRelativePath.Replace( - '/', - Path.DirectorySeparatorChar)); - Directory.CreateDirectory( - Path.GetDirectoryName(archivePath)!); - using var settings = JsonDocument.Parse( - await File.ReadAllTextAsync( - Path.Combine( - sourceDirectory, - "schema-settings.template.json"), - context.CancellationToken)); - ValidateSettingsName(name, settings); - await CreateArchiveAsync( - archivePath, - await File.ReadAllBytesAsync( - Path.Combine(sourceDirectory, "schema.graphqls"), - context.CancellationToken), - settings, - GetExtensionsPath(sourceDirectory), - context.CancellationToken); - - releaseSources.Add( - new FusionReleaseSource( - name, - releaseId, - archiveRelativePath, - await ComputeFileDigestAsync( - archivePath, - context.CancellationToken), - await FusionSourceSchemaContent.ComputeSha256Async( - archivePath, - name, - context.CancellationToken))); - } - - Directory.Delete(inputsDirectory, recursive: true); - releaseSources.Sort( - (left, right) => StringComparer.Ordinal.Compare( - left.Name, - right.Name)); - var compositionSettings = - FusionReleaseCompositionSettings.From( - composition.Settings); - var manifest = new FusionReleaseManifest( - FusionReleaseManifest.CurrentFormatVersion, - releaseId, - FusionReleaseCompatibility.CompositionToolVersion, - FusionReleaseDigests.ComputeSourceSetSha256( - releaseSources), - new FusionReleaseComposition( - FusionReleaseDigests.ComputeCompositionSha256( - compositionSettings), - compositionSettings), - releaseSources, - Targets: []); - - await FusionReleaseStore.WriteDraftAsync( - temporaryDirectory, - manifest, - context.CancellationToken); - ReplaceDirectoryAtomically( - temporaryDirectory, - releaseDirectory); - } - finally - { - DeleteDirectoryBestEffort(temporaryDirectory); - } - } - - private static async Task UploadReleaseAsync( - IReadOnlyList deployments, - PipelineStepContext context) - { - var releaseId = await ResolveSharedReleaseIdAsync( - deployments, - context.CancellationToken); var output = context.Services .GetRequiredService() .GetOutputDirectory(); - var releaseDirectory = GetReleaseDirectory(output, releaseId); - var finalManifestPath = Path.Combine( - releaseDirectory, - "fusion-release.json"); - var draft = File.Exists(finalManifestPath) - ? await FusionReleaseStore.ReadFinalAsync( - Path.GetFullPath(finalManifestPath), - context.CancellationToken) - : await FusionReleaseStore.ReadDraftAsync( - releaseDirectory, - context.CancellationToken); - ValidateReleaseManifestId(draft, releaseId); var workflow = context.Services .GetRequiredService(); - var targets = new List(); - var distinctDeployments = deployments - .DistinctBy( - deployment => ( - deployment.Nitro.CloudUrl!.TrimEnd('/').ToUpperInvariant(), - deployment.Nitro.ApiId), - EqualityComparer<(string, string?)>.Default) - .OrderBy( - deployment => deployment.Nitro.CloudUrl, - StringComparer.OrdinalIgnoreCase) - .ThenBy( - deployment => deployment.Nitro.ApiId, - StringComparer.Ordinal) - .ToArray(); - - if (File.Exists(finalManifestPath) - && !TargetsMatch(draft.Targets, distinctDeployments)) - { - throw new InvalidOperationException( - $"Existing Fusion release '{releaseId}' target set does not " - + "match the declared Nitro targets."); - } - - foreach (var deployment in distinctDeployments) - { - var target = await ResolveTargetAsync( - deployment, - context, - context.CancellationToken); - - foreach (var source in draft.Sources) - { - var archivePath = FusionReleaseStore.ResolveArchivePath( - Path.Combine( - releaseDirectory, - "fusion-release.draft.json"), - source); - await FusionReleaseStore.VerifyArchiveAsync( - archivePath, - source, - context.CancellationToken); - await workflow.ReconcileSourceSchemaAsync( - target, - new FusionSourceSchemaUpload( - source.Name, - source.Version, - archivePath, - source.ArchiveSha256), - context.CancellationToken); - } - - targets.Add( - new FusionReleaseTarget( - deployment.Nitro.CloudUrl!, - deployment.Nitro.ApiId!, - draft.SourceSetSha256, - draft.Sources.Select( - source => - new FusionReleaseSourceReference( - source.Name, - source.Version, - source.ContentSha256)) - .ToArray())); - } - - await FusionReleaseStore.WriteFinalAsync( - releaseDirectory, - draft with { Targets = targets }, - context.CancellationToken); - } - - private static async Task VerifyReleaseReadinessAsync( - IReadOnlyList deployments, - PipelineStepContext context) - { - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); - using var httpClient = new HttpClient - { - Timeout = TimeSpan.FromSeconds(10) - }; + var compositionResource = FusionPipeline.GetCompositionResource( + context.Model); + var composition = GraphQLResourceModel.GetComposition( + compositionResource); foreach (var deployment in deployments) { @@ -757,43 +437,50 @@ private static async Task VerifyReleaseReadinessAsync( var state = await ReadApplyStateAsync( applyDirectory, context.CancellationToken); - await VerifyFileDigestAsync( - state.ManifestPath, - state.ManifestSha256, - "promoted Fusion release manifest", + await ValidateApplyStateAsync( + state, + deployment, + context, + context.CancellationToken); + var target = await ResolveTargetAsync( + deployment, + context, context.CancellationToken); var farPath = ResolveApplyPath( applyDirectory, state.FusionArchivePath ?? throw new InvalidOperationException( - "The promoted Fusion release has not been composed.")); + "The downloaded Fusion sources have not been composed.")); + ValidateCompositionState( + state, + deployment, + composition.Settings); + await VerifyFileDigestAsync( farPath, state.FusionArchiveSha256 ?? throw new InvalidOperationException( - "The promoted Fusion release has no composed archive digest."), + "The composed Fusion archive has no digest."), "composed Fusion archive", context.CancellationToken); - using var archive = FusionArchive.Open(farPath); - using var configuration = - await archive.TryGetGatewayConfigurationAsync( - WellKnownVersions.LatestGatewayFormatVersion, - context.CancellationToken) - ?? throw new InvalidDataException( - "The composed Fusion archive contains no gateway configuration."); - foreach (var (name, endpoint) in GetTransportEndpoints( - configuration.Settings)) - { - RejectLoopbackEndpoint(endpoint); - await WaitForReadinessAsync( - httpClient, - name, - endpoint, + await workflow.PublishAsync( + new FusionPublicationRequest( + target, + deployment.StageName!, + state.Tag, + state.Sources + .Select(source => + new FusionSourceSchemaVersion( + source.Name, + source.Version)) + .ToArray(), + deployment.WaitForApproval, + deployment.Force, deployment.OperationTimeout, - s_readinessRetryDelay, - context.CancellationToken); - } + deployment.ApprovalTimeout), + farPath, + context.CancellationToken); } } @@ -874,85 +561,6 @@ await Task.Delay( lastFailure); } - private static async Task PublishReleaseAsync( - IReadOnlyList deployments, - PipelineStepContext context) - { - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); - var workflow = context.Services - .GetRequiredService(); - var compositionResource = FusionPipeline.GetCompositionResource( - context.Model); - var currentComposition = GraphQLResourceModel.GetComposition( - compositionResource); - - foreach (var deployment in deployments) - { - var applyDirectory = GetApplyDirectory(output, deployment); - var state = await ReadApplyStateAsync( - applyDirectory, - context.CancellationToken); - await VerifyFileDigestAsync( - state.ManifestPath, - state.ManifestSha256, - "promoted Fusion release manifest", - context.CancellationToken); - var manifest = await FusionReleaseStore.ReadFinalAsync( - state.ManifestPath, - context.CancellationToken); - ValidateApplyState(state, manifest, deployment); - var target = await ResolveTargetAsync( - deployment, - context, - context.CancellationToken); - var farPath = ResolveApplyPath( - applyDirectory, - state.FusionArchivePath - ?? throw new InvalidOperationException( - "The promoted Fusion release has not been composed.")); - var expectedEnvironment = ResolveCompositionEnvironment( - deployment, - currentComposition.Settings); - if (!string.Equals( - state.CompositionEnvironment, - expectedEnvironment, - StringComparison.Ordinal)) - { - throw new InvalidDataException( - "The composed Fusion archive environment does not match " - + $"deployment '{deployment.Name}'."); - } - - await VerifyFileDigestAsync( - farPath, - state.FusionArchiveSha256 - ?? throw new InvalidOperationException( - "The promoted Fusion release has no composed archive digest."), - "composed Fusion archive", - context.CancellationToken); - - await workflow.PublishAsync( - new FusionPublicationRequest( - target, - deployment.StageName!, - manifest.ReleaseId, - manifest.Sources.Select( - source => - new FusionSourceSchemaVersion( - source.Name, - source.Version)) - .ToArray(), - deployment.WaitForApproval, - deployment.Force, - deployment.OperationTimeout, - deployment.ApprovalTimeout), - farPath, - context.CancellationToken); - } - } - internal async Task> MaterializeArchivesAsync( PipelineStepContext context) { @@ -982,7 +590,7 @@ internal async Task> MaterializeArchivesAsyn var compositionEnvironment = ResolveCompositionEnvironment( deployment, composition.Settings); - var releaseId = await ResolveConfigurationTagAsync( + var tag = await ResolveConfigurationTagAsync( deployment, context.CancellationToken); var deploymentDirectory = GetDeploymentDirectory(output, deployment); @@ -992,14 +600,11 @@ internal async Task> MaterializeArchivesAsyn Directory.CreateDirectory(materializedDirectory); foreach (var sourceDirectory in Directory.EnumerateDirectories( - Path.Combine(deploymentDirectory, "sources"))) + Path.Combine(deploymentDirectory, "sources")) + .OrderBy(Path.GetFileName, StringComparer.Ordinal)) { var name = Path.GetFileName(sourceDirectory); - var sourceVersion = deployment.UseGitCommitAsSourceVersion - ? await ReadGitCommitAsync( - sourceDirectory, - context.CancellationToken) - : releaseId; + var sourceVersion = tag; ValidatePathSegment(sourceVersion, "source version"); var schema = await File.ReadAllBytesAsync( Path.Combine(sourceDirectory, "schema.graphqls"), @@ -1084,7 +689,7 @@ await CreateSourceArtifactsAsync( ConfigurationTag: deployment.ConfigurationTag ?? $"{{{{{deployment.ConfigurationTagParameter!.Name}}}}}", StageOwnership: "authoritative", - Sources: sourceNames.Order().ToArray()); + Sources: sourceNames.Order(StringComparer.Ordinal).ToArray()); await WriteJsonAtomicallyAsync( Path.Combine(temporaryDirectory, "nitro-deployment-template.json"), @@ -1337,103 +942,6 @@ await File.ReadAllBytesAsync( } } - private static async Task ComposeAsync( - string farPath, - IReadOnlyList artifacts, - GraphQLCompositionSettings settings, - PipelineStepContext context, - CancellationToken cancellationToken) - { - if (File.Exists(farPath)) - { - File.Delete(farPath); - } - - var logger = context.Services - .GetRequiredService>(); - if (!await AspireCompositionHelper.TryComposeArchivesAsync( - farPath, - artifacts.Select( - artifact => new SourceSchemaArchiveInfo( - artifact.Name, - artifact.ArchivePath)) - .ToArray(), - settings, - logger, - cancellationToken)) - { - throw new InvalidOperationException( - "Fusion configuration composition failed."); - } - } - - private static IReadOnlyList - GetSelectedManifestDeployments(PipelineStepContext context) - { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( - context.Model, - environment); - - if (deployments.Any( - deployment => - deployment.FusionReleaseManifestParameter is null)) - { - throw new InvalidOperationException( - $"Aspire environment '{environment}' does not exclusively use " - + "promoted Fusion release manifests."); - } - - return deployments; - } - - private static async Task ResolveSharedReleaseIdAsync( - IReadOnlyList deployments, - CancellationToken cancellationToken) - { - var releaseIds = new HashSet(StringComparer.Ordinal); - foreach (var deployment in deployments) - { - releaseIds.Add( - await ResolveConfigurationTagAsync( - deployment, - cancellationToken)); - } - - if (releaseIds.Count is not 1) - { - throw new InvalidOperationException( - "All promoted-manifest Fusion deployments must use the same release ID."); - } - - return releaseIds.Single(); - } - - private static async Task ResolveManifestPathAsync( - FusionDeploymentResource deployment, - CancellationToken cancellationToken) - { - var parameter = deployment.FusionReleaseManifestParameter - ?? throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' has no release manifest parameter."); - var value = await parameter.GetValueAsync(cancellationToken); - if (string.IsNullOrWhiteSpace(value)) - { - throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' release manifest path resolved to an empty value."); - } - - if (!Path.IsPathFullyQualified(value)) - { - throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' release manifest path must be absolute."); - } - - return Path.GetFullPath(value); - } - internal static string ResolveCompositionEnvironment( FusionDeploymentResource deployment, GraphQLCompositionSettings settings) @@ -1443,6 +951,25 @@ internal static string ResolveCompositionEnvironment( ?? throw new InvalidOperationException( $"Fusion deployment '{deployment.Name}' has no composition environment."); + private static void ValidateCompositionState( + FusionApplyState state, + FusionDeploymentResource deployment, + GraphQLCompositionSettings settings) + { + var expectedEnvironment = ResolveCompositionEnvironment( + deployment, + settings); + if (!string.Equals( + state.CompositionEnvironment, + expectedEnvironment, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + "The composed Fusion archive environment does not match " + + $"deployment '{deployment.Name}'."); + } + } + internal static JsonDocument ResolveSourceSchemaSettings( JsonDocument settings, string environmentName) @@ -1450,119 +977,61 @@ internal static JsonDocument ResolveSourceSchemaSettings( settings, environmentName); - internal static void ValidateManifestSourceNames( - FusionReleaseManifest manifest, - IReadOnlyList providerSourceNames) + internal static IReadOnlyList GetSourceNames( + DistributedApplicationModel model) { - var duplicateProvider = providerSourceNames + var composition = FusionPipeline.GetCompositionResource(model); + var sourceNames = GraphQLResourceModel + .GetReferencedSourceSchemas(composition, model) + .Select(source => + source.Declaration.SourceSchemaName + ?? source.Resource.Name) + .ToArray(); + if (sourceNames.Length == 0) + { + throw new InvalidOperationException( + $"Fusion composition resource '{composition.Name}' has no declared source schemas."); + } + + var duplicateProvider = sourceNames .GroupBy(name => name, StringComparer.Ordinal) .FirstOrDefault(group => group.Count() > 1); if (duplicateProvider is not null) { throw new InvalidOperationException( - "Multiple provider resources map to promoted Fusion source " + "Multiple provider resources map to Fusion source " + $"'{duplicateProvider.Key}'."); } - var manifestNames = manifest.Sources - .Select(source => source.Name) - .ToHashSet(StringComparer.Ordinal); - var providerNames = providerSourceNames - .ToHashSet(StringComparer.Ordinal); - if (manifestNames.SetEquals(providerNames)) + foreach (var sourceName in sourceNames) { - return; + ValidatePathSegment(sourceName, "source schema name"); } - var missingProviders = manifestNames - .Except(providerNames, StringComparer.Ordinal) - .Order(StringComparer.Ordinal); - var unexpectedProviders = providerNames - .Except(manifestNames, StringComparer.Ordinal) - .Order(StringComparer.Ordinal); - throw new InvalidOperationException( - "The promoted Fusion source set does not match the AppHost provider " - + $"resources. Missing providers: [{string.Join(", ", missingProviders)}]. " - + $"Unexpected providers: [{string.Join(", ", unexpectedProviders)}]."); - } - - internal static void ValidateReleaseManifestId( - FusionReleaseManifest manifest, - string expectedReleaseId) - { - if (!string.Equals( - manifest.ReleaseId, - expectedReleaseId, - StringComparison.Ordinal)) - { - throw new InvalidDataException( - $"Fusion release manifest ID '{manifest.ReleaseId}' does not " - + $"match expected release '{expectedReleaseId}'."); - } + Array.Sort(sourceNames, StringComparer.Ordinal); + return sourceNames; } - internal static FusionReleaseTarget GetReleaseTarget( - FusionReleaseManifest manifest, - FusionDeploymentResource deployment) - => manifest.Targets.SingleOrDefault(target => - string.Equals( - target.CloudUrl.TrimEnd('/'), - deployment.Nitro.CloudUrl!.TrimEnd('/'), - StringComparison.OrdinalIgnoreCase) - && string.Equals( - target.ApiId, - deployment.Nitro.ApiId, - StringComparison.Ordinal)) - ?? throw new InvalidOperationException( - $"Fusion release '{manifest.ReleaseId}' was not uploaded to " - + $"Nitro API '{deployment.Nitro.ApiId}' at " - + $"'{deployment.Nitro.CloudUrl}'."); - - private static bool TargetsMatch( - IReadOnlyList targets, - IReadOnlyList deployments) - => targets.Count == deployments.Count - && deployments.All(deployment => - targets.Any(target => - string.Equals( - target.CloudUrl.TrimEnd('/'), - deployment.Nitro.CloudUrl!.TrimEnd('/'), - StringComparison.OrdinalIgnoreCase) - && string.Equals( - target.ApiId, - deployment.Nitro.ApiId, - StringComparison.Ordinal))); - - private static async Task ValidateReleaseIdAsync( + private static async Task ValidateApplyStateAsync( + FusionApplyState state, FusionDeploymentResource deployment, - FusionReleaseManifest manifest, + PipelineStepContext context, CancellationToken cancellationToken) { - var configuredReleaseId = await ResolveConfigurationTagAsync( + var tag = await ResolveConfigurationTagAsync( deployment, cancellationToken); + var sourceNames = GetSourceNames(context.Model); if (!string.Equals( - configuredReleaseId, - manifest.ReleaseId, + state.Tag, + tag, StringComparison.Ordinal)) { - throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' selected release " - + $"'{configuredReleaseId}', but the promoted manifest contains " - + $"release '{manifest.ReleaseId}'."); + throw new InvalidDataException( + $"Prepared Fusion tag '{state.Tag}' does not match configured tag '{tag}'."); } - } - private static void ValidateApplyState( - FusionReleaseApplyState state, - FusionReleaseManifest manifest, - FusionDeploymentResource deployment) - { if (!string.Equals( - state.ReleaseId, - manifest.ReleaseId, - StringComparison.Ordinal) - || !string.Equals( state.CloudUrl.TrimEnd('/'), deployment.Nitro.CloudUrl!.TrimEnd('/'), StringComparison.OrdinalIgnoreCase) @@ -1570,38 +1039,50 @@ private static void ValidateApplyState( state.ApiId, deployment.Nitro.ApiId, StringComparison.Ordinal) - || state.Sources.Count != manifest.Sources.Count) + || state.Sources.Count != sourceNames.Count + || !state.Sources.Select(source => source.Name) + .SequenceEqual(sourceNames, StringComparer.Ordinal)) { throw new InvalidDataException( - "Prepared Fusion release state for deployment " - + $"'{deployment.Name}' does not match the promoted manifest."); + $"Prepared Fusion state does not match deployment '{deployment.Name}'."); } - GetReleaseTarget(manifest, deployment); - foreach (var source in manifest.Sources) + var applyDirectory = GetApplyDirectory( + context.Services + .GetRequiredService() + .GetOutputDirectory(), + deployment); + foreach (var source in state.Sources) { - var prepared = state.Sources.SingleOrDefault(candidate => - string.Equals( - candidate.Name, - source.Name, - StringComparison.Ordinal)); - if (prepared is null - || !string.Equals( - prepared.Version, + if (!string.Equals( source.Version, - StringComparison.Ordinal) - || !string.Equals( - prepared.ContentSha256, + tag, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Prepared Fusion source '{source.Name}' does not use tag '{tag}'."); + } + + var archivePath = ResolveApplyPath( + applyDirectory, + source.ArchivePath); + var contentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + archivePath, + source.Name, + cancellationToken); + if (!string.Equals( + contentSha256, source.ContentSha256, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException( - $"Prepared Fusion source '{source.Name}' does not match the promoted manifest."); + $"Prepared Fusion source '{source.Name}' content changed after download."); } } } - private static async Task ReadApplyStateAsync( + private static async Task ReadApplyStateAsync( string applyDirectory, CancellationToken cancellationToken) { @@ -1611,17 +1092,32 @@ private static async Task ReadApplyStateAsync( if (!File.Exists(path)) { throw new FileNotFoundException( - "The promoted Fusion release has not been prepared.", + "Fusion sources have not been downloaded.", path); } await using var stream = File.OpenRead(path); - return await JsonSerializer.DeserializeAsync( + var state = await JsonSerializer.DeserializeAsync( stream, - FusionReleaseStore.SerializerOptions, + s_jsonOptions, cancellationToken) ?? throw new InvalidDataException( - "The promoted Fusion apply state is empty."); + "The Fusion apply state is empty."); + if (string.IsNullOrWhiteSpace(state.Tag) + || string.IsNullOrWhiteSpace(state.CloudUrl) + || string.IsNullOrWhiteSpace(state.ApiId) + || state.Sources is null + || state.Sources.Any(source => + string.IsNullOrWhiteSpace(source.Name) + || string.IsNullOrWhiteSpace(source.Version) + || string.IsNullOrWhiteSpace(source.ArchivePath) + || string.IsNullOrWhiteSpace(source.ContentSha256))) + { + throw new InvalidDataException( + "The Fusion apply state is missing required content."); + } + + return state; } private static string ResolveApplyPath( @@ -1631,7 +1127,7 @@ private static string ResolveApplyPath( if (Path.IsPathFullyQualified(relativePath)) { throw new InvalidDataException( - "A promoted Fusion apply path must be relative."); + "A Fusion apply path must be relative."); } var fullApplyDirectory = Path.GetFullPath(applyDirectory); @@ -1643,7 +1139,7 @@ private static string ResolveApplyPath( if (!path.StartsWith(prefix, StringComparison.Ordinal)) { throw new InvalidDataException( - "A promoted Fusion apply path escapes the apply output directory."); + "A Fusion apply path escapes the apply output directory."); } return path; @@ -1708,42 +1204,6 @@ private static async Task ResolveTargetAsync( apiKey); } - private static async Task WriteDeploymentManifestAsync( - FusionDeploymentResource deployment, - string releaseId, - IReadOnlyList artifacts, - PipelineStepContext context, - CancellationToken cancellationToken) - { - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); - var manifest = new FusionDeploymentManifest( - FormatVersion: 1, - CloudUrl: deployment.Nitro.CloudUrl!, - ApiId: deployment.Nitro.ApiId!, - Environment: deployment.EnvironmentName!, - Stage: deployment.StageName!, - ConfigurationTag: releaseId, - StageOwnership: "authoritative", - Sources: artifacts.Select( - artifact => new FusionDeploymentManifestSource( - artifact.Name, - artifact.Version, - Path.GetRelativePath( - GetDeploymentDirectory(output, deployment), - artifact.ArchivePath), - artifact.Sha256)) - .ToArray()); - - await WriteJsonAtomicallyAsync( - Path.Combine( - GetDeploymentDirectory(output, deployment), - "nitro-deployment.json"), - manifest, - cancellationToken); - } - internal static Uri GetTransportEndpoint(JsonDocument settings) { var root = settings.RootElement; @@ -1816,44 +1276,6 @@ private static async Task ResolveConfigurationTagAsync( return value; } - private static async Task ReadGitCommitAsync( - string sourceDirectory, - CancellationToken cancellationToken) - { - using var provenance = JsonDocument.Parse( - await File.ReadAllTextAsync( - Path.Combine(sourceDirectory, "provenance.json"), - cancellationToken)); - var workingDirectory = provenance.RootElement - .GetProperty("workingDirectory") - .GetString()!; - var startInfo = new ProcessStartInfo - { - FileName = "git", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - startInfo.ArgumentList.Add("-C"); - startInfo.ArgumentList.Add(workingDirectory); - startInfo.ArgumentList.Add("rev-parse"); - startInfo.ArgumentList.Add("HEAD"); - - using var process = Process.Start(startInfo) - ?? throw new InvalidOperationException("Could not start Git."); - var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); - await process.WaitForExitAsync(cancellationToken); - - if (process.ExitCode != 0 || string.IsNullOrWhiteSpace(output)) - { - throw new InvalidOperationException( - "Could not resolve the Git commit for a Fusion source version."); - } - - return output.Trim(); - } - internal static void ValidatePathSegment( string value, string description) @@ -1911,11 +1333,7 @@ private static async Task WriteJsonAtomicallyAsync( await JsonSerializer.SerializeAsync( stream, value, - new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }, + s_jsonOptions, cancellationToken); } @@ -1935,11 +1353,6 @@ private static string GetDeploymentDirectory( FusionDeploymentResource deployment) => Path.Combine(output, "fusion", deployment.Name); - private static string GetReleaseDirectory( - string output, - string releaseId) - => Path.Combine(output, "fusion", "releases", releaseId); - private static string GetApplyDirectory( string output, FusionDeploymentResource deployment) @@ -2019,34 +1432,16 @@ internal sealed record FusionSourceProvenance( bool LaunchProfile, string WorkingDirectory); -internal sealed record FusionDeploymentManifest( - int FormatVersion, - string CloudUrl, - string ApiId, - string Environment, - string Stage, - string ConfigurationTag, - string StageOwnership, - IReadOnlyList Sources); - -internal sealed record FusionDeploymentManifestSource( - string Name, - string SourceVersion, - string Archive, - string Sha256); - -internal sealed record FusionReleaseApplyState( - string ManifestPath, - string ManifestSha256, - string ReleaseId, +internal sealed record FusionApplyState( + string Tag, string CloudUrl, string ApiId, string? CompositionEnvironment, string? FusionArchivePath, string? FusionArchiveSha256, - IReadOnlyList Sources); + IReadOnlyList Sources); -internal sealed record FusionReleaseApplySource( +internal sealed record FusionApplySource( string Name, string Version, string ArchivePath, diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs deleted file mode 100644 index 70792d25401..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseManifest.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using HotChocolate.Fusion; -using HotChocolate.Fusion.Aspire; -using HotChocolate.Fusion.Options; - -namespace ChilliCream.Nitro.Aspire; - -internal sealed record FusionReleaseManifest( - int FormatVersion, - string ReleaseId, - string CompositionToolVersion, - string SourceSetSha256, - FusionReleaseComposition Composition, - IReadOnlyList Sources, - IReadOnlyList Targets) -{ - public const int CurrentFormatVersion = 1; -} - -internal static class FusionReleaseCompatibility -{ - public static string CompositionToolVersion { get; } = - GetCompositionToolVersion(); - - public static void ValidateCompositionToolVersion( - FusionReleaseManifest manifest) - { - if (!string.Equals( - manifest.CompositionToolVersion, - CompositionToolVersion, - StringComparison.Ordinal)) - { - throw new InvalidDataException( - $"Fusion release '{manifest.ReleaseId}' was created with " - + $"composition tool version '{manifest.CompositionToolVersion}', " - + $"but apply is running version '{CompositionToolVersion}'."); - } - } - - private static string GetCompositionToolVersion() - { - var assembly = typeof(GraphQLCompositionSettings).Assembly; - return assembly - .GetCustomAttribute() - ?.InformationalVersion - ?? assembly.GetName().Version?.ToString() - ?? throw new InvalidOperationException( - "The Fusion composition tool version could not be determined."); - } -} - -internal sealed record FusionReleaseComposition( - string SettingsSha256, - FusionReleaseCompositionSettings Settings); - -internal sealed record FusionReleaseCompositionSettings( - DirectiveMergeBehavior? CacheControlMergeBehavior, - bool? EnableGlobalObjectIdentification, - NodeResolution? NodeResolution, - DirectiveMergeBehavior? TagMergeBehavior, - bool? IncludeSatisfiabilityPaths, - bool? AllowNonResolvableInterfaceObjects, - ShareableFieldRuntimeTypeRouting? ShareableFieldRuntimeTypeRouting, - IReadOnlyList ExcludeByTag) -{ - public static FusionReleaseCompositionSettings From( - GraphQLCompositionSettings settings) - => new( - settings.CacheControlMergeBehavior, - settings.EnableGlobalObjectIdentification, - settings.NodeResolution, - settings.TagMergeBehavior, - settings.IncludeSatisfiabilityPaths, - settings.AllowNonResolvableInterfaceObjects, - settings.ShareableFieldRuntimeTypeRouting, - settings.ExcludeByTag? - .Order(StringComparer.Ordinal) - .ToArray() - ?? []); - - public GraphQLCompositionSettings ToCompositionSettings( - string environmentName) - => new() - { - CacheControlMergeBehavior = CacheControlMergeBehavior, - EnableGlobalObjectIdentification = EnableGlobalObjectIdentification, - NodeResolution = NodeResolution, - TagMergeBehavior = TagMergeBehavior, - IncludeSatisfiabilityPaths = IncludeSatisfiabilityPaths, - AllowNonResolvableInterfaceObjects = AllowNonResolvableInterfaceObjects, - ShareableFieldRuntimeTypeRouting = ShareableFieldRuntimeTypeRouting, - ExcludeByTag = ExcludeByTag.ToHashSet(StringComparer.Ordinal), - EnvironmentName = environmentName - }; -} - -internal sealed record FusionReleaseSource( - string Name, - string Version, - string ArchivePath, - string ArchiveSha256, - string ContentSha256); - -internal sealed record FusionReleaseTarget( - string CloudUrl, - string ApiId, - string SourceSetSha256, - IReadOnlyList Sources); - -internal sealed record FusionReleaseSourceReference( - string Name, - string Version, - string ContentSha256); - -internal static class FusionReleaseDigests -{ - public static string ComputeCompositionSha256( - FusionReleaseCompositionSettings settings) - => ComputeSha256(JsonSerializer.SerializeToUtf8Bytes( - settings, - FusionReleaseStore.SerializerOptions)); - - public static string ComputeSourceSetSha256( - IEnumerable sources) - { - using var stream = new MemoryStream(); - - foreach (var source in sources.OrderBy( - source => source.Name, - StringComparer.Ordinal)) - { - WriteFramed(stream, source.Name); - WriteFramed(stream, source.Version); - WriteFramed(stream, source.ContentSha256); - } - - return ComputeSha256(stream.GetBuffer().AsSpan(0, (int)stream.Length)); - } - - private static void WriteFramed(Stream stream, string value) - { - var bytes = Encoding.UTF8.GetBytes(value); - Span length = stackalloc byte[sizeof(int)]; - System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian( - length, - bytes.Length); - stream.Write(length); - stream.Write(bytes); - } - - private static string ComputeSha256(ReadOnlySpan value) - => Convert.ToHexStringLower(SHA256.HashData(value)); -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs deleted file mode 100644 index 74cfef79ca9..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionReleaseStore.cs +++ /dev/null @@ -1,475 +0,0 @@ -using System.Security.Cryptography; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace ChilliCream.Nitro.Aspire; - -internal static class FusionReleaseStore -{ - internal static JsonSerializerOptions SerializerOptions { get; } = - CreateSerializerOptions(); - - public static async Task ReadFinalAsync( - string manifestPath, - CancellationToken cancellationToken) - { - if (!Path.IsPathFullyQualified(manifestPath)) - { - throw new InvalidOperationException( - "The Fusion release manifest parameter must resolve to an absolute path."); - } - - if (!File.Exists(manifestPath)) - { - throw new FileNotFoundException( - "The promoted Fusion release manifest was not found.", - manifestPath); - } - - try - { - await using var stream = File.OpenRead(manifestPath); - var manifest = - await JsonSerializer.DeserializeAsync( - stream, - SerializerOptions, - cancellationToken) - ?? throw new InvalidDataException( - "The Fusion release manifest is empty."); - - Validate(manifest, requireTargets: true); - return manifest; - } - catch (JsonException exception) - { - throw new InvalidDataException( - "The Fusion release manifest contains invalid JSON.", - exception); - } - } - - public static async Task ReadDraftAsync( - string releaseDirectory, - CancellationToken cancellationToken) - { - var path = Path.Combine( - releaseDirectory, - "fusion-release.draft.json"); - if (!File.Exists(path)) - { - throw new FileNotFoundException( - "The Fusion release draft manifest was not found.", - path); - } - - try - { - await using var stream = File.OpenRead(path); - var manifest = - await JsonSerializer.DeserializeAsync( - stream, - SerializerOptions, - cancellationToken) - ?? throw new InvalidDataException( - "The Fusion release draft manifest is empty."); - Validate(manifest, requireTargets: false); - return manifest; - } - catch (JsonException exception) - { - throw new InvalidDataException( - "The Fusion release draft manifest contains invalid JSON.", - exception); - } - } - - public static Task WriteDraftAsync( - string releaseDirectory, - FusionReleaseManifest manifest, - CancellationToken cancellationToken) - { - Validate(manifest, requireTargets: false); - return WriteAtomicallyAsync( - Path.Combine(releaseDirectory, "fusion-release.draft.json"), - manifest, - overwrite: true, - cancellationToken); - } - - public static Task WriteFinalAsync( - string releaseDirectory, - FusionReleaseManifest manifest, - CancellationToken cancellationToken) - { - Validate(manifest, requireTargets: true); - return WriteAtomicallyAsync( - Path.Combine(releaseDirectory, "fusion-release.json"), - manifest, - overwrite: false, - cancellationToken); - } - - public static string ResolveArchivePath( - string manifestPath, - FusionReleaseSource source) - { - if (Path.IsPathFullyQualified(source.ArchivePath)) - { - throw new InvalidDataException( - $"Fusion source '{source.Name}' archive path must be relative."); - } - - var manifestDirectory = Path.GetDirectoryName( - Path.GetFullPath(manifestPath))!; - var archivePath = Path.GetFullPath( - source.ArchivePath, - manifestDirectory); - var expectedPrefix = manifestDirectory.EndsWith( - Path.DirectorySeparatorChar) - ? manifestDirectory - : manifestDirectory + Path.DirectorySeparatorChar; - - if (!archivePath.StartsWith( - expectedPrefix, - StringComparison.Ordinal)) - { - throw new InvalidDataException( - $"Fusion source '{source.Name}' archive path escapes the release directory."); - } - - return archivePath; - } - - public static async Task VerifyArchiveAsync( - string archivePath, - FusionReleaseSource source, - CancellationToken cancellationToken) - { - if (!File.Exists(archivePath)) - { - throw new FileNotFoundException( - $"Fusion source '{source.Name}' archive was not found.", - archivePath); - } - - await using var stream = File.OpenRead(archivePath); - var digest = Convert.ToHexStringLower( - await SHA256.HashDataAsync(stream, cancellationToken)); - - if (!string.Equals( - digest, - source.ArchiveSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - $"Fusion source '{source.Name}' archive SHA-256 does not match the release manifest."); - } - } - - public static void Validate( - FusionReleaseManifest manifest, - bool requireTargets) - { - ArgumentNullException.ThrowIfNull(manifest); - - if (manifest.FormatVersion is not FusionReleaseManifest.CurrentFormatVersion) - { - throw new InvalidDataException( - $"Unsupported Fusion release manifest format version '{manifest.FormatVersion}'."); - } - - if (manifest.Composition is null - || manifest.Composition.Settings is null - || manifest.Composition.Settings.ExcludeByTag is null - || manifest.Sources is null - || manifest.Targets is null) - { - throw new InvalidDataException( - "The Fusion release manifest is missing required content."); - } - - ValidatePathSegment(manifest.ReleaseId, "release ID"); - if (string.IsNullOrWhiteSpace(manifest.CompositionToolVersion)) - { - throw new InvalidDataException( - "The Fusion release manifest is missing the composition tool version."); - } - - ValidateSha256(manifest.SourceSetSha256, "source set"); - ValidateSha256( - manifest.Composition.SettingsSha256, - "composition settings"); - - var expectedCompositionDigest = - FusionReleaseDigests.ComputeCompositionSha256( - manifest.Composition.Settings); - if (!string.Equals( - expectedCompositionDigest, - manifest.Composition.SettingsSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - "The Fusion release composition settings digest is invalid."); - } - - if (manifest.Sources.Count == 0) - { - throw new InvalidDataException( - "The Fusion release manifest must contain at least one source."); - } - - if (manifest.Sources.Any(source => - source is null - || string.IsNullOrWhiteSpace(source.Name) - || string.IsNullOrWhiteSpace(source.Version) - || string.IsNullOrWhiteSpace(source.ArchivePath) - || string.IsNullOrWhiteSpace(source.ArchiveSha256) - || string.IsNullOrWhiteSpace(source.ContentSha256))) - { - throw new InvalidDataException( - "The Fusion release manifest contains an invalid source."); - } - - var duplicateSource = manifest.Sources - .GroupBy(source => source.Name, StringComparer.Ordinal) - .FirstOrDefault(group => group.Count() > 1); - if (duplicateSource is not null) - { - throw new InvalidDataException( - $"Fusion release source '{duplicateSource.Key}' is duplicated."); - } - - foreach (var source in manifest.Sources) - { - ValidatePathSegment(source.Name, "source name"); - ValidatePathSegment(source.Version, "source version"); - ValidateSha256( - source.ArchiveSha256, - $"source '{source.Name}' archive"); - ValidateSha256( - source.ContentSha256, - $"source '{source.Name}' content"); - - if (source.ArchivePath.Contains('\\') - || Path.IsPathFullyQualified(source.ArchivePath) - || source.ArchivePath.Split( - [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], - StringSplitOptions.RemoveEmptyEntries) - .Any(segment => segment is "." or "..")) - { - throw new InvalidDataException( - $"Fusion source '{source.Name}' archive path must remain inside the release."); - } - } - - var expectedSourceSetDigest = - FusionReleaseDigests.ComputeSourceSetSha256(manifest.Sources); - if (!string.Equals( - expectedSourceSetDigest, - manifest.SourceSetSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - "The Fusion release source set digest is invalid."); - } - - if (requireTargets && manifest.Targets.Count == 0) - { - throw new InvalidDataException( - "The final Fusion release manifest must contain an uploaded target."); - } - - if (manifest.Targets.Any(target => - target is null - || string.IsNullOrWhiteSpace(target.CloudUrl) - || string.IsNullOrWhiteSpace(target.ApiId) - || string.IsNullOrWhiteSpace(target.SourceSetSha256) - || target.Sources is null - || target.Sources.Any(source => - source is null - || string.IsNullOrWhiteSpace(source.Name) - || string.IsNullOrWhiteSpace(source.Version) - || string.IsNullOrWhiteSpace(source.ContentSha256)))) - { - throw new InvalidDataException( - "The Fusion release manifest contains an invalid target."); - } - - var duplicateTarget = manifest.Targets - .GroupBy( - target => (target.CloudUrl, target.ApiId), - FusionReleaseTargetKeyComparer.Instance) - .FirstOrDefault(group => group.Count() > 1); - if (duplicateTarget is not null) - { - throw new InvalidDataException( - $"Fusion release target '{duplicateTarget.Key.ApiId}' is duplicated."); - } - - foreach (var target in manifest.Targets) - { - if (target is null - || target.Sources is null - || target.Sources.Any(source => source is null)) - { - throw new InvalidDataException( - "The Fusion release manifest contains an invalid target."); - } - - if (!Uri.TryCreate( - target.CloudUrl, - UriKind.Absolute, - out var cloudUri) - || cloudUri.Scheme is not "https" - || !string.IsNullOrEmpty(cloudUri.UserInfo) - || cloudUri.AbsolutePath is not "/" - || !string.IsNullOrEmpty(cloudUri.Query) - || !string.IsNullOrEmpty(cloudUri.Fragment)) - { - throw new InvalidDataException( - $"Fusion release target '{target.ApiId}' cloud URL must be " - + "an absolute HTTPS origin."); - } - - if (string.IsNullOrWhiteSpace(target.ApiId)) - { - throw new InvalidDataException( - "A Fusion release target must specify an API ID."); - } - - if (!string.Equals( - target.SourceSetSha256, - manifest.SourceSetSha256, - StringComparison.OrdinalIgnoreCase) - || target.Sources.Count != manifest.Sources.Count) - { - throw new InvalidDataException( - $"Fusion release target '{target.ApiId}' does not contain the release source set."); - } - - foreach (var source in manifest.Sources) - { - var reference = target.Sources.SingleOrDefault(candidate => - string.Equals(candidate.Name, source.Name, StringComparison.Ordinal)); - if (reference is null - || !string.Equals( - reference.Version, - source.Version, - StringComparison.Ordinal) - || !string.Equals( - reference.ContentSha256, - source.ContentSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - $"Fusion release target '{target.ApiId}' source set does not match the release."); - } - } - } - } - - private static async Task WriteAtomicallyAsync( - string path, - FusionReleaseManifest manifest, - bool overwrite, - CancellationToken cancellationToken) - { - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - var bytes = JsonSerializer.SerializeToUtf8Bytes( - manifest, - SerializerOptions); - - if (!overwrite && File.Exists(path)) - { - var current = await File.ReadAllBytesAsync( - path, - cancellationToken); - if (current.AsSpan().SequenceEqual(bytes)) - { - return; - } - - throw new InvalidOperationException( - $"Fusion release manifest '{path}' already exists with different content."); - } - - var temporaryPath = - path + "." + Guid.NewGuid().ToString("N") + ".tmp"; - try - { - await File.WriteAllBytesAsync( - temporaryPath, - bytes, - cancellationToken); - File.Move(temporaryPath, path, overwrite); - } - finally - { - if (File.Exists(temporaryPath)) - { - File.Delete(temporaryPath); - } - } - } - - private static void ValidatePathSegment( - string value, - string description) - { - if (string.IsNullOrWhiteSpace(value) - || value is "." or ".." - || value.Any(character => - !char.IsAsciiLetterOrDigit(character) - && character is not '.' and not '_' and not '-')) - { - throw new InvalidDataException( - $"Fusion {description} '{value}' is not a portable path segment."); - } - } - - private static void ValidateSha256( - string value, - string description) - { - if (string.IsNullOrEmpty(value) - || value.Length is not 64 - || !value.All(Uri.IsHexDigit)) - { - throw new InvalidDataException( - $"Fusion {description} SHA-256 must contain 64 hexadecimal characters."); - } - } - - private static JsonSerializerOptions CreateSerializerOptions() - { - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = false, - WriteIndented = true - }; - options.Converters.Add(new JsonStringEnumConverter()); - return options; - } - - private sealed class FusionReleaseTargetKeyComparer - : IEqualityComparer<(string CloudUrl, string ApiId)> - { - public static FusionReleaseTargetKeyComparer Instance { get; } = new(); - - public bool Equals( - (string CloudUrl, string ApiId) x, - (string CloudUrl, string ApiId) y) - => string.Equals( - x.CloudUrl.TrimEnd('/'), - y.CloudUrl.TrimEnd('/'), - StringComparison.OrdinalIgnoreCase) - && string.Equals(x.ApiId, y.ApiId, StringComparison.Ordinal); - - public int GetHashCode((string CloudUrl, string ApiId) obj) - => HashCode.Combine( - StringComparer.OrdinalIgnoreCase.GetHashCode( - obj.CloudUrl.TrimEnd('/')), - StringComparer.Ordinal.GetHashCode(obj.ApiId)); - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs index d0a9ca7e4dc..9197e653100 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs @@ -12,9 +12,9 @@ internal interface IFusionPipelineExecutor Task UploadAsync(PipelineStepContext context); - Task PrepareReleaseAsync(PipelineStepContext context); + Task DownloadAsync(PipelineStepContext context); - Task ComposeReleaseAsync(PipelineStepContext context); + Task ComposeAsync(PipelineStepContext context); Task PublishAsync(PipelineStepContext context); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs index c01d71a3bf8..050fa8bf163 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs @@ -146,29 +146,6 @@ public static IResourceBuilder WithCompositionEnvironm return builder; } - /// - /// Uses a promoted Fusion release manifest for this deployment. - /// - /// - /// The parameter must resolve to the absolute path of fusion-release.json. - /// - public static IResourceBuilder WithFusionReleaseManifest( - this IResourceBuilder builder, - IResourceBuilder manifestPath) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(manifestPath); - - if (builder.Resource.UseGitCommitAsSourceVersion) - { - throw new InvalidOperationException( - "A promoted Fusion release cannot rediscover source versions from Git."); - } - - builder.Resource.FusionReleaseManifestParameter = manifestPath.Resource; - return builder; - } - /// /// Sets the immutable release tag. /// @@ -199,25 +176,6 @@ public static IResourceBuilder WithConfigurationTag( return builder; } - /// - /// Uses the current Git commit as the default source schema version. - /// - public static IResourceBuilder - WithDefaultSourceVersionFromGitCommit( - this IResourceBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - if (builder.Resource.FusionReleaseManifestParameter is not null) - { - throw new InvalidOperationException( - "A promoted Fusion release cannot rediscover source versions from Git."); - } - - builder.Resource.UseGitCommitAsSourceVersion = true; - return builder; - } - /// /// Configures whether Nitro waits for approval. /// diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md index 40d19e023b1..82aef3ef76d 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md @@ -2,13 +2,12 @@ Publishes Hot Chocolate Fusion configurations through the .NET Aspire deployment pipeline. -See [Publishing Fusion with Aspire](FUSION_PUBLISHING.md) for the command contract, artifact -layout, failure behavior, and CI/CD examples. +See [Publishing Fusion with Aspire](FUSION_PUBLISHING.md) for the command contract, failure +behavior, ordering, and CI/CD example. ```csharp +var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); -var releaseId = builder.AddParameter("releaseId"); -var releaseManifest = builder.AddParameter("fusionReleaseManifest"); var nitro = builder.AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") @@ -19,8 +18,7 @@ nitro.AddFusionDeployment("production") .ForEnvironment("Production") .ToStage("production") .WithCompositionEnvironment("production") - .WithConfigurationTag(releaseId) - .WithFusionReleaseManifest(releaseManifest) + .WithConfigurationTag(tag) .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -28,11 +26,12 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -The Fusion `aspire publish` step creates environment-neutral source archives without resolving a -Nitro credential or calling Nitro. Use `aspire do fusion-upload` in the build job to upload the -immutable source versions and finalize a portable release manifest. Deployment jobs pass that exact -manifest through `Parameters__fusionReleaseManifest` and run `aspire do fusion-publish`. Only the -final `fusion-release.json` needs to cross runners; apply downloads exact verified sources from -Nitro and rejects a different Fusion composition-tool version. A matching `aspire deploy` reaches -the same download, verification, environment-specific composition, readiness, and publication -graph. +Run `aspire do fusion-upload --environment Production` after building the source schemas. It +exports the AppHost-declared source set and reconciles every source as the immutable version +`name@tag` on the selected Nitro target. + +Run `aspire do fusion-publish --environment Production` on the deployment runner. It infers the +same complete source set from the AppHost, downloads each exact `name@tag` from Nitro, composes with +the selected environment settings, deploys and checks the source services, publishes the Nitro +stage, and then deploys the gateway. It does not read source schemas, use Git, or upload source +versions. No manifest or CI artifact is passed between upload and publish. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md index 59b0c5e4123..fe97727ff04 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md @@ -1,60 +1,61 @@ -# Fusion and .NET Aspire publishing and deployment +# Fusion publish and deploy with Aspire ## Decision -The canonical split release commands are `aspire do fusion-upload` for the build job and `aspire do fusion-publish` for an environment deployment job. Upload creates immutable source versions and a portable release manifest. Publish consumes that exact manifest, deploys the source services, composes, verifies source readiness, publishes the Fusion configuration to Nitro, deploys the gateway, and completes its public terminal step. Manifest-mode publish never uploads a source. +The canonical release commands are: -`aspire publish` remains artifact-only for Fusion. It produces the portable source archives and draft manifest, but it does not call Nitro, resolve a Nitro credential, or mutate a Nitro stage. The broader `aspire deploy` root is also supported and requires `fusion-publish`, but CI uses the two named commands so the release artifact crosses runners explicitly. - -The resulting invariant is: - -```text -Fusion deployment declared for environment E - + -aspire do fusion-publish --environment E - = -Nitro stage reaches terminal success, or the Aspire deployment does not succeed +```shell +aspire do fusion-upload --environment +aspire do fusion-publish --environment ``` -This is an explicit opt-in at AppHost design time, not an implicit inference. An AppHost without a matching Fusion deployment has no Nitro side effect. - -## Aspire version and API maturity - -At the time of research, the latest stable Aspire release is **13.4.6**, released June 20, 2026. See [Aspire 13.4.6](https://github.com/microsoft/aspire/releases/tag/v13.4.6). Aspire 13.4 made `aspire publish` and `aspire deploy` generally available. See [what's new in Aspire 13.4](https://aspire.dev/whats-new/aspire-13-4/). +`fusion-upload` exports and uploads immutable source schemas. `fusion-publish` downloads and +verifies the exact uploaded source versions, composes for the selected environment, deploys source +compute, checks readiness, publishes the Fusion configuration to Nitro, deploys the gateway, and +completes its terminal step. -The programmatic pipeline APIs used to register and order custom steps remain experimental and emit `ASPIREPIPELINES001`. `aspire do` is the command for running a selected step and its dependencies, but that does not make every API behind custom step construction GA. Keep the experimental integration behind a narrow adapter and explicitly accept the diagnostic in that package. Sources: [deployment pipelines](https://aspire.dev/deployment/pipelines/), [ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/), [`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), [`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and [`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/). +Nitro is the handoff between the two commands. There is no release-manifest parameter, CI artifact +upload/download, source archive handoff, or dependency from `fusion-publish` to `fusion-upload`. +Both invocations evaluate the same AppHost revision and receive the same `tag` parameter. -This repository pins the Aspire hosting packages to **13.4.6** in [`src/Directory.Packages.props`](../../../../Directory.Packages.props). CLI examples use Aspire CLI 13.4.6 and its current `--apphost` option. +`aspire publish` remains artifact-only for Fusion. It may create local source inputs but does not +resolve a Nitro credential or mutate Nitro. The broader `aspire deploy` root is supported and +requires the same public `fusion-publish` terminal. -## Command contract +## Aspire integration boundary -| Entry point | Fusion behavior | Remote effect | -| --- | --- | --- | -| `aspire publish` | Evaluate/build as Aspire requires, then emit Fusion SDL, settings templates, endpoint bindings, archive inputs, and provenance beneath the output path. | No Nitro API call, Nitro credential resolution, upload, slot request, or stage mutation. Aspire itself may still create/update local deployment configuration, verify certificates, build, or perform other non-Nitro work. | -| `aspire do fusion-upload` | Produce portable Fusion inputs, reconcile immutable source versions, and finalize `fusion-release.json`. | Source upload only. It is not equivalent to deployment completion. | -| `aspire do fusion-publish` | Read the exact promoted manifest, download and verify its source versions, deploy source compute, compose, check readiness, publish the Nitro stage, deploy gateway compute, and complete. | Stage publication only. It never uploads source versions in manifest mode. | -| `aspire deploy --environment Production` | Run the broader provider root, which requires the same `fusion-publish` graph for the matching declaration. | The same stage publication plus any additional AppHost deployment roots. | -| `aspire destroy` | Destroy provider resources according to the deployment target. | Never infer deletion of shared Nitro schema history or stage configurations. Nitro retention needs a separate explicit design. | +The implementation targets the repository's Aspire 13.4 integration. Aspire 13.4 made +`aspire publish` and `aspire deploy` generally available, while the programmatic custom-pipeline +APIs remain experimental and emit `ASPIREPIPELINES001`. Keep those APIs behind the small local +pipeline adapter. Sources: [deployment pipelines](https://aspire.dev/deployment/pipelines/), +[ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/), +[`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), +[`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and +[`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/). -Neither publication command scans an arbitrary `aspire publish` directory for a promoted release. `fusion-upload` writes the final manifest, and `fusion-publish` reads only the absolute path supplied through the configured manifest parameter. +The adapter recognizes provider steps tagged `DeployCompute`, including steps contributed through +an Aspire `DeploymentTarget`. It does not treat provisioning as successful compute deployment. If +the adapter cannot identify deploy-compute steps for a source or gateway resource, publication +fails closed instead of claiming safe ordering. -## Environment and stage selection - -The Fusion deployment declaration must map an Aspire environment to exactly one intended Nitro stage. Do not infer stage from branch, tag, provider, resource name, or `ASPNETCORE_ENVIRONMENT`. Do not deploy staging and production from the same invocation merely because both are declared. +## AppHost surface ```csharp -var releaseManifest = builder.AddParameter("fusionReleaseManifest"); +var tag = builder.AddParameter("tag"); +var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); -var nitro = builder.AddNitroTarget("nitro") +var nitro = builder + .AddNitroTarget("nitro") .WithCloudUrl("https://api.chillicream.com") .WithApiId("products-fusion") - .WithApiKey(builder.AddParameter("nitroApiKey", secret: true)); + .WithApiKey(nitroApiKey); -nitro.AddFusionDeployment("production") +nitro + .AddFusionDeployment("production") .ForEnvironment("Production") .ToStage("production") - .WithConfigurationTag(builder.AddParameter("releaseId")) - .WithFusionReleaseManifest(releaseManifest) + .WithCompositionEnvironment("production") + .WithConfigurationTag(tag) .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -62,287 +63,254 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -Adding this resource opts Fusion publication into `aspire do fusion-publish --environment Production` and the broader `aspire deploy --environment Production`. A separate `.ForEnvironment("Staging").ToStage("staging")` declaration runs only when Staging is selected. Graph construction must fail when multiple Fusion deployments ambiguously claim the same environment/API/stage or when the selected environment has inconsistent mappings. - -The sketch uses the implemented Aspire 13.4.6 public surface and keeps required values required. - -## Existing `HotChocolate.Fusion.Aspire` state - -The repository already has a source-schema and composition graph. Adapt it rather than creating a second Nitro-only graph. +`ForEnvironment` selects the Aspire invocation. `ToStage` selects the Nitro stage. +`WithCompositionEnvironment` selects the source-settings environment. `WithConfigurationTag` +supplies the immutable source version and final configuration tag. -* [`GraphQLResourceBuilderExtensions.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs) exposes `WithGraphQLSchemaFile`, `WithGraphQLSchemaEndpoint`, and `WithGraphQLSchemaComposition`. -* [`SchemaComposition.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs) discovers referenced resources, creates internal `SourceSchemaInfo` records, and subscribes to `AfterResourcesCreatedEvent` through internal annotations and private discovery helpers. -* Native endpoint acquisition uses `/graphql/schema.graphql`. `/graphql` is only for an explicitly identified Apollo Federation `_service.sdl` flow. -* File mode can default its asserted name from the Aspire resource, while upload derives the authoritative name from `schema-settings.json`. The manifest must validate that the declared/manifest name exactly matches settings `name`. -* Current runtime discovery is suitable for `aspire run`, but normal DCP application orchestration is disabled in publish mode in the inspected Aspire source. Publish cannot assume resource endpoints are running. +Multiple deployment declarations can map the same AppHost composition to different Aspire +environments or Nitro stages. Ambiguous duplicate environment/API/stage mappings are rejected. -The integration reuses these declarations and preserves the composition relationships between the -gateway resource and its referenced sources. The deploy pipeline augments that model with provider -step discovery, environment-resolved endpoint bindings, and readiness evidence. +## Source declaration and acquisition -## Can `aspire publish` use Hot Chocolate command-line schema export? +Fusion sources come from the resources referenced by the single AppHost composition resource. +Supported acquisition modes are: -Yes, as an explicit short-lived child process. Aspire publish mode does not start normal AppHost/DCP resources, but the custom publish pipeline action can start the referenced GraphQL project itself to run `HotChocolate.AspNetCore.CommandLine`. +1. checked-in `schema.graphqls` and `schema-settings.json` files; and +2. explicit command-line schema export. -This export is not a pure or side-effect-free metadata operation. The child process executes the application's `Program`, configuration loading, service registration, `Build`, and endpoint mapping. Resolving the request executor performs full Hot Chocolate schema initialization, including type modules, schema hooks, warmup tasks, and any user code reached by those paths. Kestrel and ordinary `IHostedService` instances do not start because command mode invokes the CLI and skips `host.Run`/`host.RunAsync`. See [`WebApplicationExtensions.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/WebApplicationExtensions.cs) and [`ExportCommand.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/Command/ExportCommand.cs). +Runtime HTTP introspection is not accepted during publication. File-based input is preferred for +deterministic, auditable CI. -The correct registration symbol is `ExportSchemaOnStartup`, not `ExportSchemaFileOnStartup`. It registers a schema executor warmup task, so it can export again while the CLI export command initializes the executor. See [`HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs). Projects used by the integration should either avoid the startup exporter in this mode or tolerate and isolate both writes. +The effective source name is the explicitly declared source-schema name when present, otherwise +the Aspire resource name. The implementation validates that effective names are unique, portable, +and exactly match the name in source settings. Duplicate names fail before any path can be +overwritten or any publication can become ambiguous. -The application entry point must return the command exit code: +Command-line export must validate the expected schema and settings files, not only exit code zero. +It runs without a launch profile, records the project/configuration/framework/runtime inputs, and +rejects missing or empty output. -```csharp -return await app.RunWithGraphQLCommandsAsync(args); -``` +## Identity -Use an argument-safe process invocation equivalent to: +One rollout uses one immutable `tag`. Upload assigns that value to every source in the declared +set, producing exact identities such as: ```text -dotnet run --project /absolute/path/Products.Subgraph.csproj --configuration Release --no-launch-profile -- schema export --output /isolated/products/schema.graphqls --schema-name Products +products@build-842-a1b2c3d4 +reviews@build-842-a1b2c3d4 ``` -`schema` must be the first application argument after `--`, otherwise the application follows its normal server path. Always select the schema name explicitly. `ExportCommand` can print `No schemas registered.` and return successfully without creating output, so exit code zero is insufficient. The integration must validate the expected schema and settings artifacts. +Publish uses the same value as the Fusion configuration tag. A Git commit is useful provenance but +is not the public rollout identity. A rebuild, endpoint change, composition change, or different +desired rollout requires a new tag. Retries of the identical rollout reuse the same tag. -V1 should support checked-in/generated schema files and an explicit per-resource export command. Automatic `dotnet run` inference is opt-in because application startup and schema initialization can have arbitrary user side effects. Longer term, export from the exact published assembly or container that will be deployed, and bind schema provenance to its build/image digest. +Recommended configuration precedence is explicit AppHost values and parameter resources first, +then documented compatibility configuration. Secrets come from CI or a secret provider and are +never written to output or logs. -### Child-process requirements +| Concern | Recommended input | +| --- | --- | +| Cloud URL | Explicit `.WithCloudUrl(...)` HTTPS origin | +| API ID | Explicit `.WithApiId(...)` | +| API key | Secret `ParameterResource` | +| Aspire environment | Explicit `.ForEnvironment(...)` | +| Nitro stage | Explicit `.ToStage(...)` | +| Rollout/source/configuration tag | `builder.AddParameter("tag")` | -* Resolve a project with `ProjectResource.GetProjectMetadata().ProjectPath`. Reject resource types without a supported file/export declaration rather than guessing. -* Precreate an isolated output directory per source resource and invocation. Never share the application working directory or accept a stale file as fallback. -* Use `System.Diagnostics.Process` with `ProcessStartInfo.ArgumentList`, not shell concatenation. `WithProcessCommand` is a dashboard/resource command API, not the custom deployment pipeline runner. -* Use an environment allowlist. Do not pass Nitro credentials or unrelated AppHost secrets to the child. -* Record configuration, target framework, RID, working directory, launch-profile policy, project - path and SHA-256, and exported schema SHA-256 in provenance. `--no-launch-profile` is the - deterministic default. Exact deployed assembly or container-image binding remains future work. -* Enforce timeout and cancellation, and kill the entire process tree on termination. Capture bounded stdout/stderr and redact sensitive values. -* Validate freshness, non-empty SDL, parseable settings, exact `schema-settings.json` name, extensions, and the explicitly selected schema name. -* Treat a generated settings URL that resolves to localhost/loopback as a template risk. Reject it for final deployment and materialize the provider-resolved production URL only after readiness. -* Verify deterministic repeated exports for the same declared build inputs. Differences are a hard failure unless the differing provenance explains a new release input. +## `fusion-upload` -Register the action with `WithPipelineStepFactory`. Use `WithPipelineConfiguration`, `PipelineConfigurationContext.GetSteps`, and `RequiredBy` to connect it to the release graph. There is no universal provider-neutral after-compute/readiness tag. Every supported provider needs an explicit step selector/readiness adapter. +The upload command selects the deployment declaration for the current Aspire environment. It: -## Identity and configuration +1. resolves the current AppHost composition and complete effective source-name set; +2. exports or reads every source and validates schema, settings, extensions, and endpoint binding; +3. materializes every archive as `name@tag`; +4. computes its raw archive digest and canonical Fusion content digest; and +5. reconciles the immutable version on the selected Nitro API. -The recommended common path uses one immutable release ID as the configuration tag and as the default upload version for bare source names. The native model still keeps each source version explicit so an advanced rollout can select `products@version-a` and `inventory@version-b`. +An existing version with identical canonical content is success. An existing version with +different content is an immutable-version collision and fails. Partial uploads may remain orphaned +if a later source fails, but no Nitro stage is changed by upload. -| Setting | Scope | Example | Policy | -| --- | --- | --- | --- | -| Cloud URL | Nitro target | `https://api.chillicream.com` | Explicit target/config value, then documented `NITRO_CLOUD_URL` compatibility fallback. HTTPS unless an explicit development exception exists. | -| API ID | Nitro target | `products-fusion` | Explicit identifier, not a display name. | -| API key | Credential | secret `ParameterResource` | Inject through CI/secret provider. Never store in artifacts or logs. | -| Aspire environment | Deployment selector | `Production` | Required explicit `.ForEnvironment(...)` mapping. | -| Stage | Nitro deployment | `production` | Required explicit mapping. Never inferred. | -| Release/configuration tag | Deployment invocation | `build-842-a1b2c3d4` | Unique for the rollout and stable across its retries. Inject once per CI release invocation. | -| Source version | Per source | `a1b2c3d4` or content hash | Immutable and derived from the exact deployed schema/build. Defaults to release tag only for bare-name compatibility. | -| Approval | Deployment | `true` | Explicit policy. Deployment succeeds only at terminal Nitro success. | -| Force | Deployment | `false` | Never infer and never default to true. | -| Timeouts | Deployment | operation and approval limits | Explicit and conservative. Timeout returns failed/indeterminate plus the recoverable request ID. | -| Stage ownership | Deployment | authoritative or additive | Explicit single-writer policy. | +Use a real selected environment for the build job: -A Git commit alone is not always a rollout identity. Redeploying the same commit with different infrastructure, a dirty worktree, rebuilt image, changed endpoint binding, or altered settings can produce different desired state. CI should inject a unique release ID per release invocation and reuse it for retries of that same rollout. Do not derive the current release from stale Aspire deployment cache state. +```shell +export Parameters__tag="$RELEASE_TAG" +export Parameters__nitroApiKey="$NITRO_API_KEY" -### Existing Nitro CLI input mapping +aspire do fusion-upload \ + --apphost ./src/AppHost/AppHost.csproj \ + --environment Development \ + --non-interactive +``` -| Existing input | Aspire-native input | Compatibility meaning | -| --- | --- | --- | -| `NITRO_API_ID` | `Nitro:ApiId` / `Parameters__nitroApiId` | API identity. | -| `NITRO_STAGE` | deployment `.ToStage(...)` / `Parameters__nitroStage` | Explicit environment-to-stage mapping. | -| `NITRO_TAG` | `configurationTag`, plus default source version for bare names | Publish uses it as configuration tag/default selected version; upload uses it as source version. Aspire splits the concepts internally. | -| `NITRO_API_KEY` | secret `nitroApiKey` / `Parameters__nitroApiKey` | Never write to artifacts/logs. Aspire deployment cache can persist resolved secrets in plaintext as described below. | -| `NITRO_CLOUD_URL` | `Nitro:CloudUrl` / `Parameters__nitroCloudUrl` | Compatibility override for the API base URL. | +When Development and Test share the same Nitro cloud URL, API ID, source set, and tag, that upload +serves both. Run the command once per distinct Nitro API target otherwise. -Prefer `ParameterResource` and Aspire configuration, including `Parameters__nitroApiKey` and `Parameters__releaseId`. Support `NITRO_*` only as a documented fallback with deterministic precedence. See [external parameters](https://aspire.dev/fundamentals/external-parameters/). +## `fusion-publish` -Current source metadata remains limited to the existing GitHub and Azure DevOps model. Richer -provider/actor metadata and binding schema provenance to the exact deployed build or container image -digest remain future work. +Publish does not need a build-job artifact and does not read source schema files. It uses the +current AppHost only as the authority for: -## Build-once artifacts and manifest apply +- exact effective source names; +- target cloud URL and API ID; +- selected stage and composition environment; and +- current composition settings. -`aspire publish` produces source archives and a draft release manifest beneath -`/fusion/releases/`. It has no Nitro credential resolution, upload, -composition slot, commit, or stage mutation. `aspire do fusion-upload` runs that artifact step, -reconciles each immutable source version into every declared Nitro API target, verifies existing -versions by normalized content, and atomically writes the final `fusion-release.json`. +The command performs these release-critical phases: -The final manifest records the release ID, exact composition tool identity and options, the complete -source-set digest, each source version and content digest, and every Nitro API target to which the -source set was uploaded. It contains no credential, stage, Aspire environment, timestamp, checkout -path, or runner-specific absolute path. +1. select the current environment declaration and resolve `tag`; +2. infer and sort the complete effective source-name set; +3. download each exact `name@tag` from the selected Nitro API; +4. atomically record apply state that binds tag, target, complete source set, archive paths, and + canonical content digests; +5. compose a FAR from only those downloaded archives using current AppHost composition settings + and the selected composition environment; +6. revalidate source digests, composition environment, and FAR digest; +7. deploy source compute and poll the composed production endpoints until ready; +8. publish the FAR and exact source references to the selected Nitro stage; +9. wait for approval when configured and verify the terminal Nitro result; +10. deploy gateway compute; and +11. complete the public terminal step. -The deploy runner receives only that final manifest as the promotion artifact. Manifest apply reads -the exact absolute path supplied by `Parameters__fusionReleaseManifest`, verifies the release and -target bindings, downloads every exact source version from Nitro, verifies normalized content, and -composes the environment-specific FAR. It never reads the build runner's source ZIPs and never -uploads a source version. +A missing exact source fails during download before source compute changes. Publish never calls the +source reconciliation API, runs schema export, reads a source checkout, or invokes Git. -## Mandatory deploy graph +## Pipeline graph and first release -The implemented promoted-manifest ordering is: +Build-side work is independent: ```text -fusion-release-prepare -> fusion-compose - | -source DeployCompute -----------+-> fusion-readiness - | - v - fusion-publish-stage - | - v - gateway DeployCompute - | - v - fusion-publish - | - v - WellKnownPipelineSteps.Deploy +fusion-artifacts -> fusion-upload ``` -`fusion-publish-stage` is the internal action that requests, validates, commits, and observes the -Nitro publication. The public `fusion-publish` step is a terminal completion step. The split is -required for a first release: the gateway must not start before the first FAR exists, but the named -public command must not finish until the gateway deployment terminal succeeds. - -The source provider graph may run in parallel with manifest preparation and composition. Readiness -depends on both the composed FAR and every referenced source deployment terminal. It probes the -resolved production URLs and retries transient DNS, connection, request-timeout, and HTTP 5xx -failures with a bounded delay until the deployment's configured operation timeout. Responses below -HTTP 500 retain their existing accepted semantics. Deadline failures identify the source and -endpoint. - -`WithPipelineConfiguration` selects `DeployCompute` on the declared resource when a provider uses -that shape. For Aspire 13.4 materialized targets, it follows `DeploymentTargetAnnotation` and -selects `DeployCompute` from the target resource. Infrastructure provisioning is not accepted as a -deployment terminal. Graph execution fails closed if any source or gateway terminal cannot be -proven. - -`RequiredBySteps = [WellKnownPipelineSteps.Deploy]` attaches the public terminal to the broader -Deploy root. Explicit dependencies, not sibling membership under Deploy, enforce the order above. -No custom step depends on the Deploy root, which avoids a cycle. - -## Current Nitro reconciliation flow - -For each matching deployment, the apply graph executes even when the desired stage state is -unchanged: +Deployment uses this graph: -1. Resolve the exact Aspire environment declaration, release ID, manifest path, stage, force and - approval policy, and timeouts. -2. Verify the promoted manifest, target binding, source set, and composition-tool identity. Download - each exact source from Nitro and verify its normalized digest. -3. Compose the environment-specific FAR while the provider may build and deploy the source - services. -4. Wait for every source deployment terminal, then poll its resolved production endpoint until it - responds below HTTP 500 or the operation timeout expires. -5. Acquire the Nitro slot, apply the configured validation and force policy, commit the already - composed FAR, and observe approval or processing until terminal success. -6. Deploy the gateway only after the internal stage publication succeeds. -7. Complete the public `fusion-publish` terminal only after the gateway deployment succeeds, - allowing the broader Aspire Deploy root to complete. - -On timeout, cancellation, or lost response, return failed/indeterminate and report the recoverable request ID. Release a claimed slot only when lifecycle-safe. An approval request can outlive a CI process, so a new invocation must resume/reconcile it rather than blindly create another publication. - -## Idempotency, serialization, and approvals - -Every-deploy reconciliation makes idempotency mandatory: - -* Same source name/version plus normalized-identical content is success/no-op. Same identity plus different content is a collision. -* Same release/configuration tag plus identical desired FAR and selected source intent is success/no-op. Same tag plus different intent is a collision. -* Persist `requestId`, phase, desired FAR digest, release ID, and source selection. Local cache alone is insufficient because CI runners are ephemeral and responses can be lost. Require server-backed lookup/idempotency or an equivalent durable release record before claiming robust retry behavior. -* Serialize publishers by `(cloudUrl, apiId, stage)`. A stale approval must never supersede a newer rollout. Enforce stage queue/order on the server or fail when it cannot be proven. -* Retry only bounded, known-transient transport/server failures with exponential backoff and jitter. Never retry authorization, invalid schema/settings, stage/API not found, force-policy rejection, or identity collisions. - -Aspire stores deployment state under `~/.aspire/deployments`, including resolved parameter values and secrets in plaintext. Never share, publish, or commit this cache. Inject credentials and the release ID from CI, and use clear-cache behavior when state is stale or compromised. The cache cannot be the authority for Nitro idempotency. See [deployment state caching](https://aspire.dev/deployment/deployment-state-caching/). - -## Stage ownership and partial state - -Choose and document one stage ownership policy: - -| Policy | Behavior | Constraint | -| --- | --- | --- | -| Authoritative declared set | The resulting FAR contains exactly the AppHost-declared source set for this deployment. Undeclared schemas are removed. | Recommended for a single release owner. Requires complete declarations. | -| Additive preserve-undeclared | Replace declared sources and preserve undeclared sources from the latest stage FAR. | Required for shared ownership, but prone to drift. Still requires one serialized writer/coordinator per stage. | - -There is no transaction across the service deployment provider, artifact uploads, and Nitro stage publication. Uploads can remain orphaned. Compute can be updated while the old FAR remains active if Nitro validation, approval, or commit fails. Automatic rollback across the compute provider and Nitro is unsafe without a provider-specific, tested rollback protocol. - -Source-service changes must remain backward-compatible with the old FAR during this window. -Operators need a documented recovery path using the persisted request ID, release manifest, source -digests, and previous FAR identity. The deployment result must report partial state precisely -instead of claiming rollback. - -## Named `aspire do` operations - -Use target-specific names and actual CLI options: +```text +fusion-download -> source DeployCompute -> fusion-readiness + \-> fusion-compose ------/ -```bash -aspire publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Release --non-interactive -aspire do fusion-upload --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Release --non-interactive -aspire do fusion-publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/aspire --environment Production --non-interactive -aspire do fusion-publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj --environment Production --list-steps +fusion-readiness + -> fusion-publish-stage + -> gateway DeployCompute + -> fusion-publish ``` -`aspire do` evaluates and builds the AppHost unless `--no-build` is used, and it reruns the selected step's dependencies. Deployment commands default to Production. Inspect scope with `--list-steps`. The safe reconcile command carries the same provider compute/readiness dependencies as Deploy, so it may redeploy or recheck resources. - -`aspire publish` remains the artifact inspection path, and upload alone does not publish a release. `fusion-publish` requires the supported compute and readiness graph before it claims a slot. - -## Implementation layout - -The supported integration is packaged in `ChilliCream.Nitro.Aspire`. It reuses -`FusionSourceSchemaArchive` for immutable source packaging and the Nitro Fusion workflow for -download, normalized verification, composition, publication, approvals, and terminal reporting. -The local pipeline adapter owns environment selection, promoted-manifest validation, provider-step -ordering, readiness polling, and the named `fusion-upload` and `fusion-publish` commands. +Every source deploy-compute step depends on `fusion-download`, so the exact Nitro source set is a +fail-before-compute preflight. Readiness depends on both composition and all source deploy-compute +steps. The internal `fusion-publish-stage` step runs only after readiness. Gateway deployment +depends on stage publication, which is required for a first release because no gateway can start +from Nitro before the first FAR exists. The public `fusion-publish` terminal depends on gateway +deployment and is required by the broader Deploy root. + +The ordering is intentionally source deploy, readiness, internal Nitro publication, gateway +deploy, terminal public publication. Upload is never in the transitive dependency set of the +public publish command. + +## Apply-state integrity + +Download writes into a temporary sibling directory and atomically replaces the deployment apply +directory only after all exact sources succeed. State paths must be relative and remain beneath the +apply directory. + +Compose, readiness, and publish validate: + +- configured tag equals recorded tag; +- normalized cloud URL and API ID equal the selected target; +- recorded source names exactly equal the current sorted AppHost set; +- every source version equals the tag; +- every archive still has its recorded canonical content digest; +- the composition environment still matches the current declaration; and +- the FAR still has its recorded raw digest. + +This state is local execution integrity, not a promotion artifact. A new deployment runner +recreates it by downloading exact source versions from Nitro. + +## Readiness, approval, and retries + +Readiness comes from the composed gateway settings, not an arbitrary Aspire liveness endpoint. +Loopback URLs are rejected for deployment. A response below HTTP 500 is considered reachable; +transport failures and server errors are retried until the configured operation timeout. + +Publication is successful only after Nitro reports a verified terminal result. Approval rejection, +approval timeout, failed commit, polling timeout, or an unverified terminal state fails the step. + +Retry rules: + +- same `name@tag` and same canonical content: upload no-op success; +- same `name@tag` and different content: collision failure; +- missing `name@tag`: publish failure before compute; +- changed AppHost source set, target, tag, downloaded content, environment, or FAR: integrity + failure; and +- transient readiness/Nitro polling failure: retry the same rollout with the same tag after the + cause is resolved. + +Serialize writers per Nitro API during upload and per Nitro stage during publication. Queue writers +instead of cancelling them. Repository-local concurrency is insufficient when another repository +or deployment system can write the same target. + +## CI shape + +All jobs check out the same revision and receive the same `RELEASE_TAG`: + +```yaml +env: + RELEASE_TAG: ${{ github.sha }} + +jobs: + upload: + env: + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + - run: >- + aspire do fusion-upload + --apphost ./src/AppHost/AppHost.csproj + --environment Development + --non-interactive + + deploy-development: + needs: upload + env: + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + - run: >- + aspire do fusion-publish + --apphost ./src/AppHost/AppHost.csproj + --environment Development + --non-interactive + + deploy-test: + needs: deploy-development + env: + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + - run: >- + aspire do fusion-publish + --apphost ./src/AppHost/AppHost.csproj + --environment Test + --non-interactive +``` -### Implemented release-critical phases +There is deliberately no artifact upload/download step and no public `releaseId` or manifest +parameter. -1. Aspire 13.4.6 graph wiring covers exact environment selection, direct and materialized-target - `DeployCompute` discovery, explicit dependencies, `RequiredBySteps`, named `do` commands, and - cycle-safe source/publication/gateway ordering. -2. Build produces immutable source archives and a portable manifest. Upload verifies or creates - exact Nitro source identities, and apply re-downloads and verifies them on a separate runner. -3. Production endpoint readiness is bounded by `OperationTimeout`; unsupported provider graphs fail - closed. -4. Nitro publication preserves normalized duplicate policy, force, approvals, timeouts, terminal - reporting, and retry reconciliation. -5. The internal publication step precedes gateway deployment, and the public terminal is a - prerequisite of the Aspire Deploy root. +## Verification matrix -## Tests and acceptance criteria +The implementation is complete only when focused tests and a real materialized AppHost prove: -| Layer | Required evidence | +| Scenario | Required result | | --- | --- | -| Environment selection | Production deploy runs only Production Fusion deployment; Staging runs only Staging; ambiguous mappings fail graph construction. | -| Publish | Produces templates/provenance and has no Nitro calls, credential resolution, upload, slot, or stage mutation. | -| Export lifecycle | Sentinels prove `Program`, configuration, service registration, endpoint mapping, schema hooks/modules, and executor warmups run. Sentinels also prove Kestrel and normal `IHostedService` instances do not start in command mode. | -| Export command | Uses argv-safe `ArgumentList`, explicit schema name, isolated/precreated output, bounded redacted output, timeout/cancellation/tree kill, and no Nitro secrets in the environment. | -| Export validation | Multi-schema selection is exact; zero exit with no registered schema/no output fails; localhost settings fail final materialization; stale output never falls back; repeated identical input exports deterministically. | -| Export provenance | Configuration, TFM, RID, working directory, launch-profile policy, project path and SHA-256, and exported schema SHA-256 are recorded. Exact deployed image binding is future work. | -| Pipeline topology | Source deployment and actual readiness precede internal Nitro publication; Nitro terminal success precedes gateway deployment; gateway completion precedes the public `fusion-publish` terminal and Aspire Deploy completion. No custom step depends on the Deploy root. | -| Provider readiness | Supported provider proves a real production endpoint, and unsupported Aspire-managed sources fail graph construction. External source requires explicit proof. | -| Materialization | The selected composition environment produces the final settings and FAR; settings name equals the declared source name. | -| Upload reconciliation | Exact normalized source identity is no-op; mismatch is collision; missing source uploads once under bounded retries. | -| Composition | Acquires slot after readiness, re-downloads latest FAR and sources, applies declared stage ownership, composes locally, and validates/commits the FAR. | -| Publication identity | Same release tag/identical intent is no-op; same tag/different intent fails; new release is serialized after existing stage work. | -| Resume/approval | Lost response or ephemeral runner resumes by request ID/server record. Approval timeout is failed/indeterminate, and Deploy cannot succeed before terminal Nitro success. Stale approval cannot supersede a later rollout. | -| Partial state | Nitro failure after compute update reports new compute plus old FAR, leaves recoverable state, and does not claim automatic rollback. | -| `aspire do` | Safe reconcile includes compute/readiness dependencies. Export/upload inspection cannot masquerade as completed deployment. | -| Compatibility | Focused suite passes on the repository's Aspire 13.4.6 pin, and real `aspire do --list-steps` output proves the materialized-target graph. | - -Done means every matching `aspire deploy` executes Fusion reconciliation, can verified-no-op on identical desired state, and cannot complete until Nitro reaches terminal success. It also means the pipeline refuses to construct when exact compute/readiness ordering cannot be proven. - -## Operational boundaries - -CI remains responsible for serializing writers to the same Nitro stage. Source-service changes must -remain compatible with the previously active FAR during the interval between source deployment and -stage publication. The integration fails closed when it cannot prove a managed resource's provider -deployment terminal, and it does not infer deletion of shared Nitro history during Aspire destroy. - -## Sources - -* [What's new in Aspire 13.4](https://aspire.dev/whats-new/aspire-13-4/) -* [Aspire 13.4.6 release](https://github.com/microsoft/aspire/releases/tag/v13.4.6) -* [Deploy with Aspire](https://aspire.dev/deployment/deploy-with-aspire/) -* [Aspire deployment pipelines](https://aspire.dev/deployment/pipelines/) -* [`aspire publish`](https://aspire.dev/reference/cli/commands/aspire-publish/), [`aspire deploy`](https://aspire.dev/reference/cli/commands/aspire-deploy/), and [`aspire do`](https://aspire.dev/reference/cli/commands/aspire-do/) -* [Aspire external parameters](https://aspire.dev/fundamentals/external-parameters/) -* [ASPIREPIPELINES001](https://aspire.dev/diagnostics/aspirepipelines001/) -* [Aspire deployment state caching](https://aspire.dev/deployment/deployment-state-caching/) -* Local code: [`GraphQLResourceBuilderExtensions.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/GraphQLResourceBuilderExtensions.cs), [`SchemaComposition.cs`](../../../../HotChocolate/Fusion/src/Fusion.Aspire/SchemaComposition.cs), [`WebApplicationExtensions.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/WebApplicationExtensions.cs), [`ExportCommand.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore.CommandLine/Command/ExportCommand.cs), [`HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs`](../../../../HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs), [`FusionConfiguration/IFusionConfigurationClient.cs`](FusionConfiguration/IFusionConfigurationClient.cs), [`FusionConfiguration/FusionConfigurationClient.cs`](FusionConfiguration/FusionConfigurationClient.cs), [`ChilliCream.Nitro.Client.csproj`](ChilliCream.Nitro.Client.csproj), and [`src/Directory.Packages.props`](../../../../Directory.Packages.props). +| Environment selection | Only the matching declaration is used; ambiguous mappings fail. | +| Complete source set | Duplicate effective names and missing exact downloads fail. | +| Cross-runner publish | Publish succeeds with AppHost metadata and Nitro downloads but no schema files, Git metadata, or upload artifact. | +| Environment composition | The same `name@tag` archives compose different Development/Test endpoints. | +| Integrity | Tag, target, source-set, archive, environment, and FAR drift fail. | +| Provider ordering | Source deploy waits for download; readiness waits for source compute; gateway waits for Nitro publication. | +| First release | Nitro stage publication precedes gateway deployment. | +| Command surface | Real `aspire do --list-steps` exposes `fusion-upload` and terminal `fusion-publish`. | +| Compatibility | Build and focused tests pass for the repository's supported target frameworks. | diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs index 225d23a7ed2..a92c499d576 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs @@ -10,12 +10,10 @@ internal sealed class FusionDeploymentWorkflow( public async Task DownloadSourceSchemaAsync( FusionTarget target, FusionSourceSchemaVersion source, - string expectedContentSha256, CancellationToken cancellationToken) { ValidateTarget(target); ValidateSourceVersion(source); - ValidateContentSha256(expectedContentSha256); await using var transport = await transportFactory.OpenAsync( target, @@ -37,16 +35,6 @@ internal sealed class FusionDeploymentWorkflow( cancellationToken); var contentSha256 = content.ComputeSha256(cancellationToken); - if (!string.Equals( - contentSha256, - expectedContentSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new FusionIdentityCollisionException( - $"Source schema '{source.Name}' version '{source.Version}' " - + "was downloaded with a different canonical content SHA-256."); - } - return new FusionSourceSchemaDownload( source.Name, source.Version, @@ -571,14 +559,6 @@ private static void ValidateSourceVersion(FusionSourceSchemaVersion source) ArgumentException.ThrowIfNullOrWhiteSpace(source.Version); } - private static void ValidateContentSha256(string contentSha256) - { - ArgumentException.ThrowIfNullOrWhiteSpace(contentSha256); - ValidateSha256( - contentSha256, - "The source schema canonical content SHA-256"); - } - private static void ValidateSha256(string sha256, string description) { if (sha256.Length is not 64 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs index 306cd8b0ade..2d5b14abe72 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs @@ -1,7 +1,8 @@ namespace ChilliCream.Nitro.Fusion; /// -/// An exact immutable source schema archive downloaded from Nitro. +/// An exact immutable source schema archive downloaded from Nitro together with its canonical +/// content identity. /// public sealed record FusionSourceSchemaDownload( string Name, diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs index dfe5481e20b..cc27966bca6 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs @@ -6,12 +6,12 @@ namespace ChilliCream.Nitro.Fusion; public interface IFusionDeploymentWorkflow { /// - /// Downloads an exact immutable source schema version and verifies its canonical content. + /// Downloads an exact immutable source schema version and computes its canonical content + /// identity. /// Task DownloadSourceSchemaAsync( FusionTarget target, FusionSourceSchemaVersion source, - string expectedContentSha256, CancellationToken cancellationToken); /// diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md index cd489e3195b..d09ae0a5ef5 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md @@ -13,7 +13,7 @@ var workflow = services.BuildServiceProvider() The public API uses deployment DTOs only. Nitro's generated GraphQL management types remain internal to this package. -`FusionSourceSchemaContent.ComputeSha256Async` computes the normalized content identity recorded -in a release manifest. `IFusionDeploymentWorkflow.DownloadSourceSchemaAsync` downloads an exact -name and version, validates the archive settings, and verifies that identity before returning the -archive bytes. +`FusionSourceSchemaContent.ComputeSha256Async` computes a normalized content identity. +`IFusionDeploymentWorkflow.DownloadSourceSchemaAsync` downloads an exact name and version, +validates the archive and settings name, then returns the archive bytes with their canonical +content identity. diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs index e051cedcb1a..a99f8edb955 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs @@ -39,43 +39,6 @@ public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() Assert.Equal(["production"], deployments.Select(x => x.Name)); } - [Fact] - public void WithFusionReleaseManifest_Should_ConfigureExactParameterAndCompositionEnvironment() - { - var builder = DistributedApplication.CreateBuilder(); - var manifest = builder.AddParameter("fusionReleaseManifest"); - var deployment = builder - .AddNitroTarget("nitro") - .AddFusionDeployment("production") - .WithFusionReleaseManifest(manifest) - .WithCompositionEnvironment("prod"); - - Assert.Same( - manifest.Resource, - deployment.Resource.FusionReleaseManifestParameter); - Assert.Equal( - "prod", - deployment.Resource.CompositionEnvironmentName); - } - - [Fact] - public void WithDefaultSourceVersionFromGitCommit_Should_Fail_WhenManifestIsConfigured() - { - var builder = DistributedApplication.CreateBuilder(); - var deployment = builder - .AddNitroTarget("nitro") - .AddFusionDeployment("production") - .WithFusionReleaseManifest( - builder.AddParameter("fusionReleaseManifest")); - - var exception = Assert.Throws( - deployment.WithDefaultSourceVersionFromGitCommit); - - Assert.Equal( - "A promoted Fusion release cannot rediscover source versions from Git.", - exception.Message); - } - [Fact] public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() { @@ -115,38 +78,41 @@ public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() } [Fact] - public void ShouldUseManifestProducer_Should_PreserveLegacyMode_WhenAnotherEnvironmentUsesManifest() + public void GetSourceNames_Should_Fail_WhenEffectiveNamesAreDuplicated() { + using var testDirectory = new TestDirectory(); + var productsProject = Path.Combine( + testDirectory.Path, + "Products.csproj"); + var reviewsProject = Path.Combine( + testDirectory.Path, + "Reviews.csproj"); + var gatewayProject = Path.Combine( + testDirectory.Path, + "Gateway.csproj"); + File.WriteAllText(productsProject, ""); + File.WriteAllText(reviewsProject, ""); + File.WriteAllText(gatewayProject, ""); var builder = DistributedApplication.CreateBuilder(); - var nitro = builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products"); - nitro - .AddFusionDeployment("development") - .ForEnvironment("Development") - .ToStage("development") - .WithConfigurationTag("release-1"); - nitro - .AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithConfigurationTag("release-1") - .WithFusionReleaseManifest( - builder.AddParameter("fusionReleaseManifest")); + var products = builder + .AddProject("products", productsProject) + .WithGraphQLSchemaFile(sourceSchemaName: "shared"); + var reviews = builder + .AddProject("reviews", reviewsProject) + .WithGraphQLSchemaFile(sourceSchemaName: "shared"); + builder + .AddProject("gateway", gatewayProject) + .WithReference(products) + .WithReference(reviews) + .WithGraphQLSchemaComposition(); var model = new DistributedApplicationModel(builder.Resources); - string.Join( - Environment.NewLine, - $"Development: {FusionPipeline.ShouldUseManifestProducer(model, "Development")}", - $"Production: {FusionPipeline.ShouldUseManifestProducer(model, "Production")}", - $"Release: {FusionPipeline.ShouldUseManifestProducer(model, "Release")}") - .MatchInlineSnapshot( - """ - Development: False - Production: True - Release: True - """); + var exception = Assert.Throws( + () => FusionPipelineExecutor.GetSourceNames(model)); + + Assert.Equal( + "Multiple provider resources map to Fusion source 'shared'.", + exception.Message); } [Fact] @@ -194,32 +160,8 @@ public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() """ fusion-artifacts: depends=[]; requiredBy=[publish] fusion-upload: depends=[fusion-artifacts]; requiredBy=[] - fusion-readiness: depends=[fusion-artifacts]; requiredBy=[] - fusion-publish-stage: depends=[fusion-upload, fusion-readiness]; requiredBy=[] - fusion-publish: depends=[fusion-publish-stage]; requiredBy=[deploy] - """); - } - - [Fact] - public void CreateStepDefinitions_Should_IsolateManifestApplyFromBuildSteps() - { - var resource = new FusionPipelineResource("fusion-pipeline"); - - var steps = FusionPipeline.CreateStepDefinitionsForTest( - resource, - useManifestApply: true); - - string.Join( - Environment.NewLine, - steps.Select(step => - $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]; " - + $"requiredBy=[{string.Join(", ", step.RequiredBySteps)}]")) - .MatchInlineSnapshot( - """ - fusion-artifacts: depends=[]; requiredBy=[publish] - fusion-upload: depends=[fusion-artifacts]; requiredBy=[] - fusion-release-prepare: depends=[]; requiredBy=[] - fusion-compose: depends=[fusion-release-prepare]; requiredBy=[] + fusion-download: depends=[]; requiredBy=[] + fusion-compose: depends=[fusion-download]; requiredBy=[] fusion-readiness: depends=[fusion-compose]; requiredBy=[] fusion-publish-stage: depends=[fusion-readiness]; requiredBy=[] fusion-publish: depends=[fusion-publish-stage]; requiredBy=[deploy] @@ -227,11 +169,11 @@ public void CreateStepDefinitions_Should_IsolateManifestApplyFromBuildSteps() } [Fact] - public void CreateStepDefinitions_Should_NotReachExportGitOrUpload_WhenApplyingManifest() + public void CreateStepDefinitions_Should_IsolatePublishFromUploadSteps() { - var steps = FusionPipeline.CreateStepDefinitionsForTest( - new FusionPipelineResource("fusion-pipeline"), - useManifestApply: true); + var resource = new FusionPipelineResource("fusion-pipeline"); + + var steps = FusionPipeline.CreateStepDefinitionsForTest(resource); var stepsByName = steps.ToDictionary( step => step.Name, StringComparer.Ordinal); @@ -245,31 +187,9 @@ public void CreateStepDefinitions_Should_NotReachExportGitOrUpload_WhenApplyingM .MatchInlineSnapshot( """ fusion-compose + fusion-download fusion-publish-stage fusion-readiness - fusion-release-prepare - """); - } - - [Fact] - public void CreateStepDefinitions_Should_NotAttachPublishToDeploy_WhenEnvironmentIsBuildOnly() - { - var steps = FusionPipeline.CreateStepDefinitionsForTest( - new FusionPipelineResource("fusion-pipeline"), - buildOnlyManifestProducer: true); - - string.Join( - Environment.NewLine, - steps.Select(step => - $"{step.Name}: depends=[{string.Join(", ", step.DependsOnSteps)}]; " - + $"requiredBy=[{string.Join(", ", step.RequiredBySteps)}]")) - .MatchInlineSnapshot( - """ - fusion-artifacts: depends=[]; requiredBy=[publish] - fusion-upload: depends=[fusion-artifacts]; requiredBy=[] - fusion-readiness: depends=[]; requiredBy=[] - fusion-publish-stage: depends=[fusion-readiness]; requiredBy=[] - fusion-publish: depends=[fusion-publish-stage]; requiredBy=[] """); } @@ -482,64 +402,6 @@ public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() Assert.Equal("production", environment); } - [Fact] - public async Task WriteFinalAsync_Should_PreserveExistingManifest_WhenContentChanges() - { - using var testDirectory = new TestDirectory(); - var manifest = CreateReleaseManifest(); - await FusionReleaseStore.WriteFinalAsync( - testDirectory.Path, - manifest, - TestContext.Current.CancellationToken); - var manifestPath = Path.Combine( - testDirectory.Path, - "fusion-release.json"); - var original = await File.ReadAllBytesAsync( - manifestPath, - TestContext.Current.CancellationToken); - - var exception = await Assert.ThrowsAsync( - () => FusionReleaseStore.WriteFinalAsync( - testDirectory.Path, - manifest with { ReleaseId = "release-2" }, - TestContext.Current.CancellationToken)); - - Assert.Equal( - $"Fusion release manifest '{manifestPath}' already exists with different content.", - exception.Message); - Assert.Equal( - original, - await File.ReadAllBytesAsync( - manifestPath, - TestContext.Current.CancellationToken)); - } - - [Fact] - public async Task VerifyArchiveAsync_Should_Fail_WhenArtifactIntegrityDoesNotMatch() - { - using var testDirectory = new TestDirectory(); - var manifest = CreateReleaseManifest(); - var source = Assert.Single(manifest.Sources); - var archivePath = Path.Combine( - testDirectory.Path, - source.ArchivePath); - Directory.CreateDirectory(Path.GetDirectoryName(archivePath)!); - await File.WriteAllTextAsync( - archivePath, - "tampered", - TestContext.Current.CancellationToken); - - var exception = await Assert.ThrowsAsync( - () => FusionReleaseStore.VerifyArchiveAsync( - archivePath, - source, - TestContext.Current.CancellationToken)); - - Assert.Equal( - "Fusion source 'products' archive SHA-256 does not match the release manifest.", - exception.Message); - } - [Fact] public async Task VerifyFileDigestAsync_Should_Fail_WhenComposedArchiveChanges() { @@ -564,195 +426,6 @@ await File.WriteAllTextAsync( exception.Message); } - [Fact] - public async Task ReadFinalAsync_Should_Fail_WhenManifestPathIsRelative() - { - var exception = await Assert.ThrowsAsync( - () => FusionReleaseStore.ReadFinalAsync( - "fusion-release.json", - TestContext.Current.CancellationToken)); - - Assert.Equal( - "The Fusion release manifest parameter must resolve to an absolute path.", - exception.Message); - } - - [Fact] - public void ValidateCompositionToolVersion_Should_Fail_WhenVersionDoesNotMatch() - { - var manifest = CreateReleaseManifest() with - { - CompositionToolVersion = "incompatible" - }; - - var exception = Assert.Throws( - () => FusionReleaseCompatibility.ValidateCompositionToolVersion( - manifest)); - - Assert.Equal( - "Fusion release 'release-1' was created with composition tool " - + "version 'incompatible', but apply is running version " - + $"'{FusionReleaseCompatibility.CompositionToolVersion}'.", - exception.Message); - } - - [Fact] - public async Task ReadFinalAsync_Should_ReturnInvalidData_WhenRequiredJsonIsNull() - { - using var testDirectory = new TestDirectory(); - var manifestPath = Path.Combine( - testDirectory.Path, - "fusion-release.json"); - await File.WriteAllTextAsync( - manifestPath, - """ - { - "formatVersion": 1, - "releaseId": "release-1", - "sourceSetSha256": null, - "composition": null, - "sources": null, - "targets": null - } - """, - TestContext.Current.CancellationToken); - - var exception = await Assert.ThrowsAsync( - () => FusionReleaseStore.ReadFinalAsync( - manifestPath, - TestContext.Current.CancellationToken)); - - Assert.Equal( - "The Fusion release manifest is missing required content.", - exception.Message); - } - - [Theory] - [InlineData( - "\"name\": \"products\"", - "\"name\": null", - "The Fusion release manifest contains an invalid source.")] - [InlineData( - "\"cloudUrl\": \"https://api.chillicream.com\"", - "\"cloudUrl\": null", - "The Fusion release manifest contains an invalid target.")] - [InlineData( - "\"apiId\": \"products\"", - "\"apiId\": null", - "The Fusion release manifest contains an invalid target.")] - public async Task ReadFinalAsync_Should_ReturnInvalidData_WhenNestedStringIsNull( - string oldValue, - string newValue, - string expectedMessage) - { - using var testDirectory = new TestDirectory(); - var manifestPath = Path.Combine( - testDirectory.Path, - "fusion-release.json"); - var json = JsonSerializer.Serialize( - CreateReleaseManifest(), - FusionReleaseStore.SerializerOptions) - .Replace( - oldValue, - newValue, - StringComparison.Ordinal); - await File.WriteAllTextAsync( - manifestPath, - json, - TestContext.Current.CancellationToken); - - var exception = await Assert.ThrowsAsync( - () => FusionReleaseStore.ReadFinalAsync( - manifestPath, - TestContext.Current.CancellationToken)); - - Assert.Equal(expectedMessage, exception.Message); - } - - [Fact] - public void Validate_Should_Fail_WhenArchivePathEscapesRelease() - { - var manifest = CreateReleaseManifest(); - var source = Assert.Single(manifest.Sources); - var invalidSource = source with - { - ArchivePath = "../secret.zip" - }; - var invalidSources = new[] { invalidSource }; - var sourceSetSha256 = - FusionReleaseDigests.ComputeSourceSetSha256(invalidSources); - - var exception = Assert.Throws( - () => FusionReleaseStore.Validate( - manifest with - { - SourceSetSha256 = sourceSetSha256, - Sources = invalidSources, - Targets = - [ - manifest.Targets[0] with - { - SourceSetSha256 = sourceSetSha256 - } - ] - }, - requireTargets: true)); - - Assert.Equal( - "Fusion source 'products' archive path must remain inside the release.", - exception.Message); - } - - [Fact] - public void GetReleaseTarget_Should_Fail_WhenManifestWasNotUploadedToSelectedApi() - { - var deployment = new FusionDeploymentResource( - "production", - new NitroResource("nitro") - { - CloudUrl = "https://api.chillicream.com", - ApiId = "other-api" - }); - - var exception = Assert.Throws( - () => FusionPipelineExecutor.GetReleaseTarget( - CreateReleaseManifest(), - deployment)); - - Assert.Equal( - "Fusion release 'release-1' was not uploaded to Nitro API " - + "'other-api' at 'https://api.chillicream.com'.", - exception.Message); - } - - [Fact] - public void ValidateManifestSourceNames_Should_Fail_WhenProviderGraphDrifts() - { - var exception = Assert.Throws( - () => FusionPipelineExecutor.ValidateManifestSourceNames( - CreateReleaseManifest(), - ["reviews"])); - - Assert.Equal( - "The promoted Fusion source set does not match the AppHost provider " - + "resources. Missing providers: [products]. Unexpected providers: [reviews].", - exception.Message); - } - - [Fact] - public void ValidateReleaseManifestId_Should_Fail_WhenDraftIsInWrongReleaseDirectory() - { - var exception = Assert.Throws( - () => FusionPipelineExecutor.ValidateReleaseManifestId( - CreateReleaseManifest(), - "release-2")); - - Assert.Equal( - "Fusion release manifest ID 'release-1' does not match expected " - + "release 'release-2'.", - exception.Message); - } - [Fact] public void GetTransportEndpoint_Should_Fail_WhenProductionBindingIsMissing() { @@ -958,45 +631,6 @@ private static string GetHttpUrl(JsonDocument settings) .GetProperty("url") .GetString()!; - private static FusionReleaseManifest CreateReleaseManifest() - { - var compositionSettings = - FusionReleaseCompositionSettings.From( - new GraphQLCompositionSettings()); - var source = new FusionReleaseSource( - "products", - "release-1", - "sources/products/release-1.zip", - new string('a', 64), - new string('b', 64)); - var sources = new[] { source }; - var sourceSetSha256 = - FusionReleaseDigests.ComputeSourceSetSha256(sources); - - return new FusionReleaseManifest( - FusionReleaseManifest.CurrentFormatVersion, - "release-1", - FusionReleaseCompatibility.CompositionToolVersion, - sourceSetSha256, - new FusionReleaseComposition( - FusionReleaseDigests.ComputeCompositionSha256( - compositionSettings), - compositionSettings), - sources, - [ - new FusionReleaseTarget( - "https://api.chillicream.com", - "products", - sourceSetSha256, - [ - new FusionReleaseSourceReference( - source.Name, - source.Version, - source.ContentSha256) - ]) - ]); - } - private sealed class TestDirectory : IDisposable { public TestDirectory() diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs index a98cc1135d3..2ecddea7454 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -20,7 +20,7 @@ namespace ChilliCream.Nitro.Aspire; public sealed class FusionReleaseAcceptanceTests { [Fact] - public async Task Release_Should_BuildOnceAndApplyFromOnlyManifestAcrossRunners() + public async Task Release_Should_UploadOnceAndPublishFromNitroAcrossRunners() { using var testDirectory = new TestDirectory(); var sourceCheckout = Path.Combine( @@ -33,93 +33,54 @@ public async Task Release_Should_BuildOnceAndApplyFromOnlyManifestAcrossRunners( var runnerC = Path.Combine(testDirectory.Path, "unrelated-runner-c"); Directory.CreateDirectory(sourceCheckout); - var sourceProjectPath = Path.Combine( + var productsProjectPath = await CreateSourceCheckoutAsync( sourceCheckout, - "Products.csproj"); - var gatewayProjectPath = Path.Combine( + "Products", + "products", + "Product"); + var reviewsProjectPath = await CreateSourceCheckoutAsync( sourceCheckout, + "Reviews", + "reviews", + "Review"); + var gatewayDirectory = Path.Combine(sourceCheckout, "Gateway"); + Directory.CreateDirectory(gatewayDirectory); + var gatewayProjectPath = Path.Combine( + gatewayDirectory, "Gateway.csproj"); - await File.WriteAllTextAsync( - sourceProjectPath, - "", - TestContext.Current.CancellationToken); await File.WriteAllTextAsync( gatewayProjectPath, "", TestContext.Current.CancellationToken); - await File.WriteAllTextAsync( - Path.Combine(sourceCheckout, "schema.graphqls"), - """ - type Query { - product: Product - } - - type Product { - id: ID! - name: String! - } - """, - TestContext.Current.CancellationToken); - await File.WriteAllTextAsync( - Path.Combine(sourceCheckout, "schema-settings.json"), - """ - { - "name": "products", - "transports": { - "http": { - "url": "{{PRODUCTS_URL}}/graphql" - } - }, - "environments": { - "staging": { - "PRODUCTS_URL": "https://products.staging.example.com" - }, - "production": { - "PRODUCTS_URL": "https://products.example.com" - } - } - } - """, - TestContext.Current.CancellationToken); var workflow = new RecordingFusionDeploymentWorkflow(); var executor = FusionPipelineExecutor.Instance; - var buildManifestPath = Path.Combine( - runnerAOutput, - "fusion", - "releases", - "release-1", - "fusion-release.json"); var buildModel = CreateModel( - sourceProjectPath, - gatewayProjectPath, - buildManifestPath); + productsProjectPath, + reviewsProjectPath, + gatewayProjectPath); var buildContext = CreateContext( buildModel, - "Release", + "Development", runnerAOutput, workflow); await executor.CreateArtifactsAsync(buildContext); await executor.UploadAsync(buildContext); - var reconcile = Assert.Single(workflow.Reconciliations); - Assert.Equal("products", reconcile.Name); - Assert.Equal("release-1", reconcile.Version); - Assert.True(File.Exists(buildManifestPath)); - var manifest = await FusionReleaseStore.ReadFinalAsync( - buildManifestPath, - TestContext.Current.CancellationToken); - Assert.Equal( - FusionReleaseCompatibility.CompositionToolVersion, - manifest.CompositionToolVersion); - - var runnerBManifestPath = CopyOnlyFinalManifest( - buildManifestPath, - runnerB); - var runnerCManifestPath = CopyOnlyFinalManifest( - buildManifestPath, - runnerC); + Assert.Collection( + workflow.Reconciliations.OrderBy(source => source.Name), + products => + { + Assert.Equal("products", products.Name); + Assert.Equal("release-1", products.Version); + }, + reviews => + { + Assert.Equal("reviews", reviews.Name); + Assert.Equal("release-1", reviews.Version); + }); + var runnerBProjects = await CreateAppHostProjectStubsAsync(runnerB); var runnerCProjects = await CreateAppHostProjectStubsAsync(runnerC); Directory.Delete(sourceCheckout, recursive: true); @@ -127,75 +88,133 @@ await File.WriteAllTextAsync( var stagingContext = CreateContext( CreateModel( - runnerBProjects.SourceProjectPath, - runnerBProjects.GatewayProjectPath, - runnerBManifestPath), - "Staging", + runnerBProjects.ProductsProjectPath, + runnerBProjects.ReviewsProjectPath, + runnerBProjects.GatewayProjectPath), + "Development", Path.Combine(runnerB, "apply-output"), workflow); - await executor.PrepareReleaseAsync(stagingContext); - await executor.ComposeReleaseAsync(stagingContext); + await executor.DownloadAsync(stagingContext); + await executor.ComposeAsync(stagingContext); await executor.PublishAsync(stagingContext); var productionContext = CreateContext( CreateModel( - runnerCProjects.SourceProjectPath, - runnerCProjects.GatewayProjectPath, - runnerCManifestPath), - "Production", + runnerCProjects.ProductsProjectPath, + runnerCProjects.ReviewsProjectPath, + runnerCProjects.GatewayProjectPath), + "Test", Path.Combine(runnerC, "different-apply-output"), workflow); - await executor.PrepareReleaseAsync(productionContext); - await executor.ComposeReleaseAsync(productionContext); + await executor.DownloadAsync(productionContext); + await executor.ComposeAsync(productionContext); await executor.PublishAsync(productionContext); - Assert.Single(workflow.Reconciliations); - Assert.Equal(2, workflow.Downloads.Count); + Assert.Equal(2, workflow.Reconciliations.Count); + Assert.Equal( + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + workflow.Downloads.OrderBy(source => source.Name)); Assert.Collection( workflow.Publications.OrderBy( publication => publication.Stage, StringComparer.Ordinal), - production => + development => { - Assert.Equal("production", production.Stage); + Assert.Equal("development", development.Stage); Assert.Equal( - "https://products.example.com/graphql", - production.SourceUrl); + new Dictionary + { + ["products"] = "https://products.development.example.com/graphql", + ["reviews"] = "https://reviews.development.example.com/graphql" + }, + development.SourceUrls); Assert.Equal( - [new FusionSourceSchemaVersion("products", "release-1")], - production.Sources); + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + development.Sources); }, - staging => + test => { - Assert.Equal("staging", staging.Stage); + Assert.Equal("test", test.Stage); Assert.Equal( - "https://products.staging.example.com/graphql", - staging.SourceUrl); + new Dictionary + { + ["products"] = "https://products.test.example.com/graphql", + ["reviews"] = "https://reviews.test.example.com/graphql" + }, + test.SourceUrls); Assert.Equal( - [new FusionSourceSchemaVersion("products", "release-1")], - staging.Sources); + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + test.Sources); }); } + [Fact] + public async Task Download_Should_FailWithoutApplyState_WhenExactSourceIsMissing() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var output = Path.Combine(testDirectory.Path, "apply-output"); + var workflow = new RecordingFusionDeploymentWorkflow(); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + output, + workflow); + + var exception = await Assert.ThrowsAsync( + () => FusionPipelineExecutor.Instance.DownloadAsync(context)); + + Assert.Equal( + "Fusion source 'products' version 'release-1' does not exist on target 'products'.", + exception.Message); + Assert.False( + File.Exists( + Path.Combine( + output, + "fusion", + "apply", + "development", + "fusion-apply.json"))); + Assert.Empty(workflow.Reconciliations); + Assert.Empty(workflow.Publications); + } + private static DistributedApplicationModel CreateModel( - string sourceProjectPath, - string gatewayProjectPath, - string manifestPath) + string productsProjectPath, + string reviewsProjectPath, + string gatewayProjectPath) { var builder = DistributedApplication.CreateBuilder(); - var manifest = builder.AddParameter( - "fusionReleaseManifest", - manifestPath); + var tag = builder.AddParameter("tag", "release-1"); var apiKey = builder.AddParameter( "nitroApiKey", "test-api-key", secret: true); var products = builder - .AddProject("products", sourceProjectPath) + .AddProject("products", productsProjectPath) + .WithGraphQLSchemaFile(); + var reviews = builder + .AddProject("reviews", reviewsProjectPath) .WithGraphQLSchemaFile(); builder .AddProject("gateway", gatewayProjectPath) .WithReference(products) + .WithReference(reviews) .WithGraphQLSchemaComposition(); var nitro = builder .AddNitroTarget("nitro") @@ -203,19 +222,17 @@ private static DistributedApplicationModel CreateModel( .WithApiId("products") .WithApiKey(apiKey); nitro - .AddFusionDeployment("staging") - .ForEnvironment("Staging") - .ToStage("staging") - .WithCompositionEnvironment("staging") - .WithConfigurationTag("release-1") - .WithFusionReleaseManifest(manifest); + .AddFusionDeployment("development") + .ForEnvironment("Development") + .ToStage("development") + .WithCompositionEnvironment("development") + .WithConfigurationTag(tag); nitro - .AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithCompositionEnvironment("production") - .WithConfigurationTag("release-1") - .WithFusionReleaseManifest(manifest); + .AddFusionDeployment("test") + .ForEnvironment("Test") + .ToStage("test") + .WithCompositionEnvironment("test") + .WithConfigurationTag(tag); return new DistributedApplicationModel(builder.Resources); } @@ -260,41 +277,90 @@ private static PipelineStepContext CreateContext( }; } - private static string CopyOnlyFinalManifest( - string sourceManifestPath, - string runnerPath) + private static async Task CreateSourceCheckoutAsync( + string checkout, + string projectName, + string sourceName, + string typeName) { - var promotedDirectory = Path.Combine(runnerPath, "promoted"); - Directory.CreateDirectory(promotedDirectory); - var destination = Path.Combine( - promotedDirectory, - "fusion-release.json"); - File.Copy(sourceManifestPath, destination); + var sourceDirectory = Path.Combine(checkout, projectName); + Directory.CreateDirectory(sourceDirectory); + var projectPath = Path.Combine( + sourceDirectory, + $"{projectName}.csproj"); + await File.WriteAllTextAsync( + projectPath, + "", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + Path.Combine(sourceDirectory, "schema.graphqls"), + $$""" + type Query { + {{sourceName}}: {{typeName}} + } - Assert.Equal( - [destination], - Directory.GetFiles( - promotedDirectory, - "*", - SearchOption.AllDirectories)); - return destination; + type {{typeName}} { + id: ID! + } + """, + TestContext.Current.CancellationToken); + var settings = """ + { + "name": "SOURCE_NAME", + "transports": { + "http": { + "url": "{{SOURCE_URL}}/graphql" + } + }, + "environments": { + "development": { + "SOURCE_URL": "https://SOURCE_NAME.development.example.com" + }, + "test": { + "SOURCE_URL": "https://SOURCE_NAME.test.example.com" + } + } + } + """.Replace( + "SOURCE_NAME", + sourceName, + StringComparison.Ordinal); + await File.WriteAllTextAsync( + Path.Combine(sourceDirectory, "schema-settings.json"), + settings, + TestContext.Current.CancellationToken); + return projectPath; } private static async Task<( - string SourceProjectPath, + string ProductsProjectPath, + string ReviewsProjectPath, string GatewayProjectPath)> CreateAppHostProjectStubsAsync( string runnerPath) { var appHostDirectory = Path.Combine(runnerPath, "apphost-model"); Directory.CreateDirectory(appHostDirectory); - var sourceProjectPath = Path.Combine( - appHostDirectory, + var productsDirectory = Path.Combine(appHostDirectory, "Products"); + var reviewsDirectory = Path.Combine(appHostDirectory, "Reviews"); + var gatewayDirectory = Path.Combine(appHostDirectory, "Gateway"); + Directory.CreateDirectory(productsDirectory); + Directory.CreateDirectory(reviewsDirectory); + Directory.CreateDirectory(gatewayDirectory); + var productsProjectPath = Path.Combine( + productsDirectory, "Products.csproj"); + var reviewsProjectPath = Path.Combine( + reviewsDirectory, + "Reviews.csproj"); var gatewayProjectPath = Path.Combine( - appHostDirectory, + gatewayDirectory, "Gateway.csproj"); await File.WriteAllTextAsync( - sourceProjectPath, + productsProjectPath, + "", + TestContext.Current.CancellationToken); + await File.WriteAllTextAsync( + reviewsProjectPath, "", TestContext.Current.CancellationToken); await File.WriteAllTextAsync( @@ -302,14 +368,30 @@ await File.WriteAllTextAsync( "", TestContext.Current.CancellationToken); - Assert.False( - File.Exists(Path.Combine(appHostDirectory, "schema.graphqls"))); - Assert.False( - File.Exists( - Path.Combine( - appHostDirectory, - "schema-settings.json"))); - return (sourceProjectPath, gatewayProjectPath); + Assert.Empty( + Directory.GetFiles( + runnerPath, + "schema.graphqls", + SearchOption.AllDirectories)); + Assert.Empty( + Directory.GetFiles( + runnerPath, + "schema-settings.json", + SearchOption.AllDirectories)); + Assert.Empty( + Directory.GetFiles( + runnerPath, + "*manifest*", + SearchOption.AllDirectories)); + Assert.Empty( + Directory.GetDirectories( + runnerPath, + ".git", + SearchOption.AllDirectories)); + return ( + productsProjectPath, + reviewsProjectPath, + gatewayProjectPath); } private sealed class RecordingFusionDeploymentWorkflow @@ -351,23 +433,12 @@ await FusionSourceSchemaContent.ComputeSha256Async( public Task DownloadSourceSchemaAsync( FusionTarget target, FusionSourceSchemaVersion source, - string expectedContentSha256, CancellationToken cancellationToken) { Downloads.Add(source); _sources.TryGetValue( CreateKey(target, source.Name, source.Version), out var download); - if (download is not null - && !string.Equals( - download.ContentSha256, - expectedContentSha256, - StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - "The requested source content digest does not match."); - } - return Task.FromResult(download); } @@ -383,20 +454,24 @@ await archive.TryGetGatewayConfigurationAsync( cancellationToken) ?? throw new InvalidDataException( "The composed Fusion archive has no gateway configuration."); - var sourceUrl = configuration.Settings.RootElement + var sourceUrls = configuration.Settings.RootElement .GetProperty("sourceSchemas") - .GetProperty("products") - .GetProperty("transports") - .GetProperty("http") - .GetProperty("url") - .GetString() - ?? throw new InvalidDataException( - "The composed source URL is missing."); + .EnumerateObject() + .ToDictionary( + source => source.Name, + source => source.Value + .GetProperty("transports") + .GetProperty("http") + .GetProperty("url") + .GetString() + ?? throw new InvalidDataException( + "The composed source URL is missing."), + StringComparer.Ordinal); Publications.Add( new RecordedPublication( request.Stage, request.SourceSchemas.ToArray(), - sourceUrl)); + sourceUrls)); } private static ( @@ -417,7 +492,7 @@ private static ( private sealed record RecordedPublication( string Stage, IReadOnlyList Sources, - string SourceUrl); + IReadOnlyDictionary SourceUrls); private sealed class TestPipelineOutputService(string outputPath) : IPipelineOutputService diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs index 90724f2e0cf..b31e09897eb 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs @@ -82,18 +82,12 @@ type Query { } [Fact] - public async Task DownloadSourceSchemaAsync_Should_ReturnExactArchive_When_ContentMatches() + public async Task DownloadSourceSchemaAsync_Should_ReturnArchiveAndDigest_When_VersionExists() { var directory = CreateTemporaryDirectory(); try { - var expectedPath = Path.Combine(directory, "expected.fss"); var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - expectedPath, - "products", - "type Query { product: String }", - """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); await CreateArchiveAsync( remotePath, "products", @@ -106,11 +100,6 @@ type Query { var remoteArchive = await File.ReadAllBytesAsync( remotePath, TestContext.Current.CancellationToken); - var expectedContentSha256 = - await FusionSourceSchemaContent.ComputeSha256Async( - expectedPath, - "products", - TestContext.Current.CancellationToken); var transport = new FakeTransport { RemoteArchive = remoteArchive @@ -120,13 +109,14 @@ await FusionSourceSchemaContent.ComputeSha256Async( var result = await workflow.DownloadSourceSchemaAsync( CreateTarget(), new FusionSourceSchemaVersion("products", "20260730"), - expectedContentSha256, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("products", result.Name); Assert.Equal("20260730", result.Version); - Assert.Equal(expectedContentSha256, result.ContentSha256); + Assert.Equal( + "3B7C4FBDBEC6ECB8C072794A67BD1CB8B4DCFB5153B6D284E662A0D3CB49EC08", + result.ContentSha256); Assert.Equal(remoteArchive, result.Archive.ToArray()); Assert.Equal("products", transport.LastDownloadName); Assert.Equal("20260730", transport.LastDownloadVersion); @@ -146,7 +136,6 @@ public async Task DownloadSourceSchemaAsync_Should_ReturnNull_When_VersionDoesNo var result = await workflow.DownloadSourceSchemaAsync( CreateTarget(), new FusionSourceSchemaVersion("products", "missing"), - new string('0', 64), TestContext.Current.CancellationToken); Assert.Null(result); @@ -155,67 +144,18 @@ public async Task DownloadSourceSchemaAsync_Should_ReturnNull_When_VersionDoesNo Assert.Equal("missing", transport.LastDownloadVersion); } - [Fact] - public async Task DownloadSourceSchemaAsync_Should_ThrowCollision_When_DigestDiffers() - { - var directory = CreateTemporaryDirectory(); - try - { - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - remotePath, - "products", - "type Query { product: String }", - """{"name":"products"}"""); - var transport = new FakeTransport - { - RemoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken) - }; - var workflow = CreateWorkflow(transport); - - var exception = await Assert.ThrowsAsync( - () => workflow.DownloadSourceSchemaAsync( - CreateTarget(), - new FusionSourceSchemaVersion("products", "20260730"), - new string('0', 64), - TestContext.Current.CancellationToken)); - - Assert.Equal( - "Source schema 'products' version '20260730' was downloaded " - + "with a different canonical content SHA-256.", - exception.Message); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - [Fact] public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_NameDiffers() { var directory = CreateTemporaryDirectory(); try { - var expectedPath = Path.Combine(directory, "expected.fss"); var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - expectedPath, - "products", - "type Query { product: String }", - """{"name":"products"}"""); await CreateArchiveAsync( remotePath, "inventory", "type Query { product: String }", """{"name":"inventory"}"""); - var expectedContentSha256 = - await FusionSourceSchemaContent.ComputeSha256Async( - expectedPath, - "products", - TestContext.Current.CancellationToken); var transport = new FakeTransport { RemoteArchive = await File.ReadAllBytesAsync( @@ -228,7 +168,6 @@ await FusionSourceSchemaContent.ComputeSha256Async( () => workflow.DownloadSourceSchemaAsync( CreateTarget(), new FusionSourceSchemaVersion("products", "20260730"), - expectedContentSha256, TestContext.Current.CancellationToken)); Assert.Equal( @@ -261,7 +200,6 @@ public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_Decompress () => workflow.DownloadSourceSchemaAsync( CreateTarget(), new FusionSourceSchemaVersion("products", "20260730"), - new string('0', 64), TestContext.Current.CancellationToken)); Assert.Equal( From aaa097fd69f1af8853e6c4715cd2f5ce58f186aa Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:31:39 +0200 Subject: [PATCH 05/11] Use in-memory Aspire Fusion publishing --- .../Fusion.Aspire/AspireCompositionHelper.cs | 138 +- .../ArchiveEntryStorage.cs | 353 ++ .../src/Fusion.Packaging/ArchiveSession.cs | 203 +- .../src/Fusion.Packaging/FusionArchive.cs | 70 +- .../Fusion.Packaging/FusionArchiveOptions.cs | 5 + .../FusionArchiveReadOptions.cs | 6 +- .../HotChocolate.Fusion.Packaging.csproj | 5 + .../FusionSourceSchemaArchive.cs | 73 +- .../FusionSourceSchemaArchiveOptions.cs | 5 + .../FusionSourceSchemaArchiveReadOptions.cs | 5 +- .../FusionSourceSchemaArchiveSession.cs | 183 +- ...olate.Fusion.SourceSchema.Packaging.csproj | 4 + .../FusionArchiveStorageTests.cs | 178 + .../PooledMemoryArchiveEntryStorageTests.cs | 45 + .../FusionSourceSchemaArchiveStorageTests.cs | 184 + .../BoundedMemoryStream.cs | 94 + .../FUSION_PUBLISHING.md | 29 +- .../FusionPipeline.cs | 91 +- .../FusionPipelineExecutor.cs | 878 +-- .../FusionPipelineSession.cs | 394 ++ .../IFusionPipelineExecutor.cs | 20 +- .../src/ChilliCream.Nitro.Aspire/README.md | 14 +- .../FUSION_ASPIRE_PUBLISH_DEPLOY.md | 67 +- .../FusionDeploymentWorkflow.cs | 82 +- .../FusionSourceSchemaContent.cs | 28 + .../FusionSourceSchemaDownload.cs | 83 +- .../Generated/FusionApiClient.Client.cs | 5164 ++++++++--------- .../IFusionDeploymentWorkflow.cs | 4 +- .../Operations/BeginFusionDeployment.graphql | 9 + .../Operations/ClaimFusionDeployment.graphql | 8 + .../Operations/CommitFusionDeployment.graphql | 8 + .../Operations/FusionDeployment.graphql | 65 - ...{Fragments.graphql => FusionError.graphql} | 0 .../ReleaseFusionDeployment.graphql | 8 + ...aphql => UploadFusionSourceSchema.graphql} | 0 .../ValidateFusionDeployment.graphql | 8 + .../Operations/WatchFusionDeployment.graphql | 19 + .../Transport/FusionApiTransportFactory.cs | 154 +- .../Transport/IFusionDeploymentTransport.cs | 5 +- .../FusionPipelineTests.cs | 56 +- .../FusionReleaseAcceptanceTests.cs | 744 ++- .../FusionApiTransportFactoryTests.cs | 224 + .../FusionDeploymentWorkflowTests.cs | 248 +- 43 files changed, 6467 insertions(+), 3494 deletions(-) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Packaging.Shared/ArchiveEntryStorage.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/FusionArchiveStorageTests.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/PooledMemoryArchiveEntryStorageTests.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.SourceSchema.Packaging.Tests/FusionSourceSchemaArchiveStorageTests.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql rename src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/{Fragments.graphql => FusionError.graphql} (100%) create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql rename src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/{FusionSourceSchema.graphql => UploadFusionSourceSchema.graphql} (100%) create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql create mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs index bf4253a263c..8592bf29035 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs @@ -46,14 +46,84 @@ public static async Task TryComposeArchivesAsync( { ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + var sourceSchemas = await ReadSourceSchemasAsync( + archives, + cancellationToken); + + try + { + // the archives are composed for a deployment, so the configured URLs are composed as + // they are, without local overrides and without a preference for development URLs. + return await ComposeAsync( + fusionArchivePath, + seedArchivePath: null, + [.. sourceSchemas], + environmentName, + settings, + SettingsComposerOptions.Default, + logger, + cancellationToken); + } + finally + { + foreach (var sourceSchema in sourceSchemas) + { + sourceSchema.SchemaSettings.Dispose(); + } + } + } + + internal static async Task TryComposeArchivesAsync( + Stream fusionArchive, + IReadOnlyList archives, + string environmentName, + GraphQLCompositionSettings settings, + ILogger logger, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(fusionArchive); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + var sourceSchemas = await ReadSourceSchemasAsync( + archives, + cancellationToken); + + try + { + using var archive = FusionArchive.Create( + fusionArchive, + leaveOpen: true); + return await ComposeAsync( + archive, + [.. sourceSchemas], + environmentName, + settings, + SettingsComposerOptions.Default, + logger, + cancellationToken); + } + finally + { + foreach (var sourceSchema in sourceSchemas) + { + sourceSchema.SchemaSettings.Dispose(); + } + } + } + + private static async Task> ReadSourceSchemasAsync( + IReadOnlyList archives, + CancellationToken cancellationToken) + { var sourceSchemas = new List(archives.Count); try { foreach (var archiveInfo in archives) { + await using var archiveStream = archiveInfo.OpenRead(); using var archive = FusionSourceSchemaArchive.Open( - archiveInfo.ArchivePath); + archiveStream); var schema = await archive.TryGetSchemaAsync(cancellationToken) ?? throw new InvalidOperationException( $"Fusion source archive '{archiveInfo.Name}' has no schema."); @@ -78,24 +148,16 @@ extensions is null }); } - // the archives are composed for a deployment, so the configured URLs are composed as - // they are, without local overrides and without a preference for development URLs. - return await ComposeAsync( - fusionArchivePath, - seedArchivePath: null, - [.. sourceSchemas], - environmentName, - settings, - SettingsComposerOptions.Default, - logger, - cancellationToken); + return sourceSchemas; } - finally + catch { foreach (var sourceSchema in sourceSchemas) { sourceSchema.SchemaSettings.Dispose(); } + + throw; } } @@ -173,6 +235,27 @@ private static async Task ComposeAsync( using var archive = OpenArchive(fusionArchivePath, seedArchivePath); + return await ComposeAsync( + archive, + newSourceSchemas, + environment, + settings, + settingsComposerOptions, + logger, + cancellationToken); + } + + private static async Task ComposeAsync( + FusionArchive archive, + ImmutableArray newSourceSchemas, + string environment, + GraphQLCompositionSettings settings, + SettingsComposerOptions settingsComposerOptions, + ILogger logger, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(environment); + var compositionLog = new CompositionLog(); var compositionSettings = CreateCompositionSettings(settings); var sourceSchemas = newSourceSchemas.ToDictionary( @@ -357,6 +440,29 @@ internal static CompositionSettings CreateCompositionSettings( } } -internal readonly record struct SourceSchemaArchiveInfo( - string Name, - string ArchivePath); +internal readonly record struct SourceSchemaArchiveInfo +{ + private readonly string? _archivePath; + private readonly byte[]? _archive; + + public SourceSchemaArchiveInfo(string name, string archivePath) + { + Name = name; + _archivePath = archivePath; + } + + public SourceSchemaArchiveInfo( + string name, + byte[] archive) + { + Name = name; + _archive = archive; + } + + public string Name { get; } + + public Stream OpenRead() + => _archivePath is null + ? new MemoryStream(_archive!, writable: false) + : File.OpenRead(_archivePath); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging.Shared/ArchiveEntryStorage.cs b/src/HotChocolate/Fusion/src/Fusion.Packaging.Shared/ArchiveEntryStorage.cs new file mode 100644 index 00000000000..49f05060317 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging.Shared/ArchiveEntryStorage.cs @@ -0,0 +1,353 @@ +using System.Buffers; + +namespace HotChocolate.Fusion.Packaging.Storage; + +internal enum ArchiveEntryStorageKind +{ + Memory, + TempFile +} + +internal abstract class ArchiveEntryStorage : IDisposable +{ + public abstract long Length { get; } + + public abstract Stream OpenRead(); + + public abstract Stream OpenWrite(int maximumLength, Action? completed = null); + + public abstract Task CopyToAsync(Stream destination, CancellationToken cancellationToken); + + public abstract void Dispose(); + + public static ArchiveEntryStorage Create(ArchiveEntryStorageKind kind) + => kind switch + { + ArchiveEntryStorageKind.Memory => new PooledMemoryArchiveEntryStorage(), + ArchiveEntryStorageKind.TempFile => new TempFileArchiveEntryStorage(), + _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null) + }; +} + +internal sealed class TempFileArchiveEntryStorage : ArchiveEntryStorage +{ + private readonly string _path = Path.Combine( + Path.GetTempPath(), + $"hotchocolate-fusion-{Environment.ProcessId}-{Guid.NewGuid():N}.tmp"); + + public override long Length + => File.Exists(_path) ? new FileInfo(_path).Length : 0; + + public override Stream OpenRead() + => File.OpenRead(_path); + + public override Stream OpenWrite(int maximumLength, Action? completed = null) + => File.Open(_path, FileMode.Create, FileAccess.Write); + + public override async Task CopyToAsync( + Stream destination, + CancellationToken cancellationToken) + { + await using var source = OpenRead(); + await source.CopyToAsync(destination, cancellationToken); + } + + public override void Dispose() + { + if (!File.Exists(_path)) + { + return; + } + + try + { + File.Delete(_path); + } + catch + { + // Temp-file cleanup is best effort. + } + } +} + +internal sealed class PooledMemoryArchiveEntryStorage : ArchiveEntryStorage +{ + private readonly PooledBuffer _buffer; + + public PooledMemoryArchiveEntryStorage() + : this(ArrayPool.Shared) + { + } + + internal PooledMemoryArchiveEntryStorage(ArrayPool pool) + { + _buffer = new PooledBuffer(pool); + } + + public override long Length => _buffer.Length; + + public override Stream OpenRead() + => new PooledBufferReadStream(_buffer.WrittenMemory); + + public override Stream OpenWrite(int maximumLength, Action? completed = null) + { + _buffer.Reset(maximumLength); + return new PooledBufferWriteStream(_buffer, completed); + } + + public override Task CopyToAsync( + Stream destination, + CancellationToken cancellationToken) + => destination.WriteAsync(_buffer.WrittenMemory, cancellationToken).AsTask(); + + public override void Dispose() + => _buffer.Dispose(); +} + +internal sealed class PooledBuffer : IDisposable +{ + private readonly ArrayPool _pool; + private byte[]? _buffer; + private int _length; + private int _maximumLength; + private bool _disposed; + + public PooledBuffer(ArrayPool pool) + { + ArgumentNullException.ThrowIfNull(pool); + _pool = pool; + } + + public int Length => _length; + + public ReadOnlyMemory WrittenMemory + => _buffer is null ? ReadOnlyMemory.Empty : _buffer.AsMemory(0, _length); + + public void Reset(int maximumLength) + { + ObjectDisposedException.ThrowIf(_disposed, this); + ArgumentOutOfRangeException.ThrowIfNegative(maximumLength); + + if (_buffer is not null && _length > 0) + { + _buffer.AsSpan(0, _length).Clear(); + } + + _length = 0; + _maximumLength = maximumLength; + } + + public void Write(ReadOnlySpan source) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var requiredLength = checked(_length + source.Length); + + if (requiredLength > _maximumLength) + { + throw new InvalidOperationException( + $"File is too large and exceeds the allowed size of {_maximumLength}."); + } + + EnsureCapacity(requiredLength); + source.CopyTo(_buffer.AsSpan(_length)); + _length = requiredLength; + } + + private void EnsureCapacity(int requiredLength) + { + if (requiredLength is 0 || (_buffer?.Length ?? 0) >= requiredLength) + { + return; + } + + var currentLength = _buffer?.Length ?? 0; + var newLength = Math.Min( + _maximumLength, + Math.Max(requiredLength, currentLength is 0 ? 4096 : checked(currentLength * 2))); + var newBuffer = _pool.Rent(newLength); + + if (_buffer is not null) + { + _buffer.AsSpan(0, _length).CopyTo(newBuffer); + _pool.Return(_buffer, clearArray: true); + } + + _buffer = newBuffer; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_buffer is not null) + { + _pool.Return(_buffer, clearArray: true); + _buffer = null; + } + + _length = 0; + _disposed = true; + } +} + +internal sealed class PooledBufferWriteStream( + PooledBuffer buffer, + Action? completed) : Stream +{ + private bool _disposed; + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => !_disposed; + + public override long Length => buffer.Length; + + public override long Position + { + get => buffer.Length; + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] source, int offset, int count) + => Write(source.AsSpan(offset, count)); + + public override void Write(ReadOnlySpan source) + { + ObjectDisposedException.ThrowIf(_disposed, this); + buffer.Write(source); + } + + public override Task WriteAsync( + byte[] source, + int offset, + int count, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(source.AsSpan(offset, count)); + return Task.CompletedTask; + } + + public override ValueTask WriteAsync( + ReadOnlyMemory source, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(source.Span); + return ValueTask.CompletedTask; + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + completed?.Invoke(buffer.Length); + } + + base.Dispose(disposing); + } +} + +internal sealed class PooledBufferReadStream(ReadOnlyMemory memory) : Stream +{ + private int _position; + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => memory.Length; + + public override long Position + { + get => _position; + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, memory.Length); + + _position = checked((int)value); + } + } + + public override void Flush() + { + } + + public override int Read(byte[] destination, int offset, int count) + => Read(destination.AsSpan(offset, count)); + + public override int Read(Span destination) + { + var bytesToCopy = Math.Min(destination.Length, memory.Length - _position); + memory.Span.Slice(_position, bytesToCopy).CopyTo(destination); + _position += bytesToCopy; + return bytesToCopy; + } + + public override Task ReadAsync( + byte[] destination, + int offset, + int count, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(Read(destination.AsSpan(offset, count))); + } + + public override ValueTask ReadAsync( + Memory destination, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.FromResult(Read(destination.Span)); + } + + public override long Seek(long offset, SeekOrigin origin) + { + var position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => checked(_position + offset), + SeekOrigin.End => checked(memory.Length + offset), + _ => throw new ArgumentOutOfRangeException(nameof(origin), origin, null) + }; + + Position = position; + return position; + } + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging/ArchiveSession.cs b/src/HotChocolate/Fusion/src/Fusion.Packaging/ArchiveSession.cs index 96771aa75bd..8c6f8bbe778 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Packaging/ArchiveSession.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging/ArchiveSession.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.IO.Compression; +using HotChocolate.Fusion.Packaging.Storage; namespace HotChocolate.Fusion.Packaging; @@ -8,16 +9,23 @@ internal sealed class ArchiveSession : IDisposable private readonly Dictionary _files = []; private readonly ZipArchive _archive; private readonly FusionArchiveReadOptions _readOptions; + private readonly ArchiveEntryStorageKind _storageKind; private FusionArchiveMode _mode; + private long _storedBytes; private bool _disposed; - public ArchiveSession(ZipArchive archive, FusionArchiveMode mode, FusionArchiveReadOptions readOptions) + public ArchiveSession( + ZipArchive archive, + FusionArchiveMode mode, + FusionArchiveReadOptions readOptions, + ArchiveEntryStorageKind storageKind) { ArgumentNullException.ThrowIfNull(archive); _archive = archive; _mode = mode; _readOptions = readOptions; + _storageKind = storageKind; } public bool HasUncommittedChanges @@ -25,19 +33,19 @@ public bool HasUncommittedChanges public IEnumerable GetFiles() { - var tempFiles = _files.Where(file => file.Value.State is not FileState.Deleted).Select(file => file.Key); + var stagedFiles = _files + .Where(file => file.Value.State is not FileState.Deleted) + .Select(file => file.Key); if (_mode is FusionArchiveMode.Create) { - return tempFiles; + return stagedFiles; } - var files = new HashSet(tempFiles); + var files = new HashSet(stagedFiles); foreach (var entry in _archive.Entries) { - // Skip entries that are explicitly marked Deleted in this session; - // they are still in the underlying ZipArchive but logically gone. if (_files.TryGetValue(entry.FullName, out var tracked) && tracked.State is FileState.Deleted) { @@ -50,7 +58,10 @@ public IEnumerable GetFiles() return files; } - public async Task ExistsAsync(string path, FileKind kind, CancellationToken cancellationToken) + public async Task ExistsAsync( + string path, + FileKind kind, + CancellationToken cancellationToken) { if (_files.TryGetValue(path, out var file)) { @@ -59,8 +70,7 @@ public async Task ExistsAsync(string path, FileKind kind, CancellationToke if (_mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is { } entry) { - file = FileEntry.Read(path); - await ExtractFileAsync(entry, file, GetAllowedSize(kind), cancellationToken); + file = await ExtractFileAsync(entry, path, GetAllowedSize(kind), cancellationToken); _files.Add(path, file); return true; } @@ -78,7 +88,10 @@ public bool Exists(string path) return _mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is not null; } - public async Task OpenReadAsync(string path, FileKind kind, CancellationToken cancellationToken) + public async Task OpenReadAsync( + string path, + FileKind kind, + CancellationToken cancellationToken) { if (_files.TryGetValue(path, out var file)) { @@ -87,16 +100,14 @@ public async Task OpenReadAsync(string path, FileKind kind, Cancellation throw new FileNotFoundException(path); } - return File.OpenRead(file.TempPath); + return file.Storage.OpenRead(); } if (_mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is { } entry) { - file = FileEntry.Read(path); - await ExtractFileAsync(entry, file, GetAllowedSize(kind), cancellationToken); - var stream = File.OpenRead(file.TempPath); + file = await ExtractFileAsync(entry, path, GetAllowedSize(kind), cancellationToken); _files.Add(path, file); - return stream; + return file.Storage.OpenRead(); } throw new FileNotFoundException(path); @@ -109,22 +120,34 @@ public Stream OpenWrite(string path) throw new InvalidOperationException("Cannot write to a read-only archive."); } - if (_files.TryGetValue(path, out var file)) + if (!_files.TryGetValue(path, out var file)) + { + var state = _mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is not null + ? FileState.Replaced + : FileState.Created; + file = new FileEntry(path, ArchiveEntryStorage.Create(_storageKind), state); + _files.Add(path, file); + } + else { file.MarkMutated(); - return File.Open(file.TempPath, FileMode.Create, FileAccess.Write); } - if (_mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is not null) + if (_storageKind is ArchiveEntryStorageKind.TempFile) { - file = FileEntry.Read(path); - file.MarkMutated(); + return file.Storage.OpenWrite(int.MaxValue); } - file ??= FileEntry.Created(path); - var stream = File.Open(file.TempPath, FileMode.Create, FileAccess.Write); - _files.Add(path, file); - return stream; + var previousLength = file.Storage.Length; + var remainingSessionBytes = checked( + _readOptions.MaxAllowedInMemorySessionSize - _storedBytes + previousLength); + var maximumLength = (int)Math.Min( + Math.Max(0, remainingSessionBytes), + GetAllowedSize(FileNames.GetFileKind(path))); + + return file.Storage.OpenWrite( + maximumLength, + length => _storedBytes = checked(_storedBytes - previousLength + length)); } public void Delete(string path) @@ -143,39 +166,32 @@ public void Delete(string path) if (file.State is FileState.Created) { - // File was added in this uncommitted session and never existed - // in the original archive: drop it entirely. - TryDeleteTempFile(file); + RemoveStorage(file); _files.Remove(path); return; } - // File was previously read or replaced (extracted to a temp file). - // Clean up the temp file now since Dispose skips Deleted entries. - TryDeleteTempFile(file); + RemoveStorage(file); file.MarkDeleted(); return; } if (_mode is not FusionArchiveMode.Create && _archive.GetEntry(path) is not null) { - _files.Add(path, FileEntry.Deleted(path)); + _files.Add( + path, + new FileEntry(path, ArchiveEntryStorage.Create(_storageKind), FileState.Deleted)); } } - private static void TryDeleteTempFile(FileEntry file) + private void RemoveStorage(FileEntry file) { - if (File.Exists(file.TempPath)) + if (_storageKind is ArchiveEntryStorageKind.Memory) { - try - { - File.Delete(file.TempPath); - } - catch - { - // ignore - } + _storedBytes -= file.Storage.Length; } + + file.Storage.Dispose(); } public void SetMode(FusionArchiveMode mode) @@ -190,12 +206,12 @@ public async Task CommitAsync(CancellationToken cancellationToken) switch (file.State) { case FileState.Created: - await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); + await CreateEntryFromStorageAsync(file, cancellationToken); break; case FileState.Replaced: _archive.GetEntry(file.Path)?.Delete(); - await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); + await CreateEntryFromStorageAsync(file, cancellationToken); break; case FileState.Deleted: @@ -207,41 +223,43 @@ public async Task CommitAsync(CancellationToken cancellationToken) } } - /// - /// Creates a ZIP entry from a file with a deterministic timestamp. - /// Using a fixed timestamp ensures binary reproducibility of the archive. - /// - private async Task CreateEntryFromFileAsync( - string sourceFileName, - string entryName, + private async Task CreateEntryFromStorageAsync( + FileEntry file, CancellationToken cancellationToken) { - var entry = _archive.CreateEntry(entryName); - // Use a fixed timestamp to ensure deterministic archive output + var entry = _archive.CreateEntry(file.Path); entry.LastWriteTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero); - await using var source = File.OpenRead(sourceFileName); #if NET10_0_OR_GREATER await using var destination = await entry.OpenAsync(cancellationToken); #else await using var destination = entry.Open(); #endif - await source.CopyToAsync(destination, cancellationToken); + await file.Storage.CopyToAsync(destination, cancellationToken); } - private static async Task ExtractFileAsync( + private async Task ExtractFileAsync( ZipArchiveEntry zipEntry, - FileEntry fileEntry, + string path, int maxAllowedSize, CancellationToken cancellationToken) { + var file = new FileEntry( + path, + ArchiveEntryStorage.Create(_storageKind), + FileState.Read); var buffer = ArrayPool.Shared.Rent(4096); - var consumed = 0; + var consumed = 0L; try { await using var readStream = zipEntry.Open(); - await using var writeStream = File.Open(fileEntry.TempPath, FileMode.Create, FileAccess.Write); + await using var writeStream = file.Storage.OpenWrite( + _storageKind is ArchiveEntryStorageKind.Memory + ? (int)Math.Min( + maxAllowedSize, + Math.Max(0L, _readOptions.MaxAllowedInMemorySessionSize - _storedBytes)) + : int.MaxValue); int read; while ((read = await readStream.ReadAsync(buffer, cancellationToken)) > 0) @@ -254,12 +272,32 @@ private static async Task ExtractFileAsync( $"File is too large and exceeds the allowed size of {maxAllowedSize}."); } + if (_storageKind is ArchiveEntryStorageKind.Memory + && _storedBytes + consumed > _readOptions.MaxAllowedInMemorySessionSize) + { + throw new InvalidOperationException( + "The archive exceeds the allowed in-memory session size of " + + $"{_readOptions.MaxAllowedInMemorySessionSize}."); + } + await writeStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken); } + + if (_storageKind is ArchiveEntryStorageKind.Memory) + { + _storedBytes += file.Storage.Length; + } + + return file; + } + catch + { + file.Storage.Dispose(); + throw; } finally { - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(buffer, clearArray: true); } } @@ -282,36 +320,23 @@ public void Dispose() foreach (var file in _files.Values) { - if (file.State is not FileState.Deleted && File.Exists(file.TempPath)) - { - try - { - File.Delete(file.TempPath); - } - catch - { - // ignore - } - } + file.Storage.Dispose(); } + _storedBytes = 0; _disposed = true; } - private class FileEntry + private sealed class FileEntry( + string path, + ArchiveEntryStorage storage, + FileState state) { - private FileEntry(string path, string tempPath, FileState state) - { - Path = path; - TempPath = tempPath; - State = state; - } - - public string Path { get; } + public string Path { get; } = path; - public string TempPath { get; } + public ArchiveEntryStorage Storage { get; } = storage; - public FileState State { get; private set; } + public FileState State { get; private set; } = state; public void MarkMutated() { @@ -330,22 +355,6 @@ public void MarkRead() { State = FileState.Read; } - - public static FileEntry Created(string path) - => new(path, GetRandomTempFileName(), FileState.Created); - - public static FileEntry Read(string path) - => new(path, GetRandomTempFileName(), FileState.Read); - - public static FileEntry Deleted(string path) - => new(path, GetRandomTempFileName(), FileState.Deleted); - - private static string GetRandomTempFileName() - { - var tempDir = System.IO.Path.GetTempPath(); - var fileName = System.IO.Path.GetRandomFileName(); - return System.IO.Path.Combine(tempDir, fileName); - } } private enum FileState diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchive.cs b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchive.cs index 1f2627e0a44..ccf13af30b0 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchive.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchive.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.Json; using HotChocolate.Fusion.Packaging.Serializers; +using HotChocolate.Fusion.Packaging.Storage; namespace HotChocolate.Fusion.Packaging; @@ -30,13 +31,14 @@ private FusionArchive( Stream stream, FusionArchiveMode mode, bool leaveOpen, - FusionArchiveReadOptions options) + FusionArchiveReadOptions options, + ArchiveEntryStorageKind storageKind) { _stream = stream; _mode = mode; _leaveOpen = leaveOpen; _archive = new ZipArchive(stream, (ZipArchiveMode)mode, leaveOpen); - _session = new ArchiveSession(_archive, mode, options); + _session = new ArchiveSession(_archive, mode, options, storageKind); } /// @@ -48,7 +50,12 @@ private FusionArchive( public static FusionArchive Create(string filename) { ArgumentNullException.ThrowIfNull(filename); - return Create(File.Create(filename)); + return new FusionArchive( + File.Create(filename), + FusionArchiveMode.Create, + leaveOpen: false, + FusionArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile); } /// @@ -61,7 +68,29 @@ public static FusionArchive Create(string filename) public static FusionArchive Create(Stream stream, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(stream); - return new FusionArchive(stream, FusionArchiveMode.Create, leaveOpen, FusionArchiveReadOptions.Default); + return new FusionArchive( + stream, + FusionArchiveMode.Create, + leaveOpen, + FusionArchiveReadOptions.Default, + ArchiveEntryStorageKind.Memory); + } + + /// + /// Creates a new Fusion Archive using the provided stream and options. + /// + public static FusionArchive Create( + Stream stream, + FusionArchiveOptions options, + bool leaveOpen = false) + { + ArgumentNullException.ThrowIfNull(stream); + return new FusionArchive( + stream, + FusionArchiveMode.Create, + leaveOpen, + CreateReadOptions(options), + ArchiveEntryStorageKind.Memory); } /// @@ -80,9 +109,19 @@ public static FusionArchive Open( return mode switch { - FusionArchiveMode.Read => Open(File.OpenRead(filename), mode), - FusionArchiveMode.Create => Create(File.Create(filename)), - FusionArchiveMode.Update => Open(File.Open(filename, FileMode.Open, FileAccess.ReadWrite), mode), + FusionArchiveMode.Read => new FusionArchive( + File.OpenRead(filename), + mode, + leaveOpen: false, + FusionArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile), + FusionArchiveMode.Create => Create(filename), + FusionArchiveMode.Update => new FusionArchive( + File.Open(filename, FileMode.Open, FileAccess.ReadWrite), + mode, + leaveOpen: false, + FusionArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile), _ => throw new ArgumentException("Invalid mode.", nameof(mode)) }; } @@ -103,12 +142,21 @@ public static FusionArchive Open( FusionArchiveOptions options = default) { ArgumentNullException.ThrowIfNull(stream); - var readOptions = new FusionArchiveReadOptions( + return new FusionArchive( + stream, + mode, + leaveOpen, + CreateReadOptions(options), + ArchiveEntryStorageKind.Memory); + } + + private static FusionArchiveReadOptions CreateReadOptions(FusionArchiveOptions options) + => new( options.MaxAllowedSchemaSize ?? FusionArchiveReadOptions.Default.MaxAllowedSchemaSize, options.MaxAllowedSettingsSize ?? FusionArchiveReadOptions.Default.MaxAllowedSettingsSize, - options.MaxAllowedLegacyArchiveSize ?? FusionArchiveReadOptions.Default.MaxAllowedLegacyArchiveSize); - return new FusionArchive(stream, mode, leaveOpen, readOptions); - } + options.MaxAllowedLegacyArchiveSize ?? FusionArchiveReadOptions.Default.MaxAllowedLegacyArchiveSize, + options.MaxAllowedInMemorySessionSize + ?? FusionArchiveReadOptions.Default.MaxAllowedInMemorySessionSize); /// /// Sets the archive metadata containing format version and schema information. diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveOptions.cs b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveOptions.cs index 99390563f5a..ff9744df370 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveOptions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveOptions.cs @@ -19,4 +19,9 @@ public struct FusionArchiveOptions /// Gets or sets the maximum allowed size of the legacy archive in the archive. /// public int? MaxAllowedLegacyArchiveSize { get; set; } + + /// + /// Gets or sets the maximum total size of entries staged in memory for a stream-based archive. + /// + public int? MaxAllowedInMemorySessionSize { get; set; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveReadOptions.cs b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveReadOptions.cs index 60e17aa2689..0060e8e3881 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveReadOptions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging/FusionArchiveReadOptions.cs @@ -6,7 +6,8 @@ namespace HotChocolate.Fusion.Packaging; internal readonly record struct FusionArchiveReadOptions( int MaxAllowedSchemaSize, int MaxAllowedSettingsSize, - int MaxAllowedLegacyArchiveSize) + int MaxAllowedLegacyArchiveSize, + int MaxAllowedInMemorySessionSize) { /// /// Gets the default read options. @@ -14,5 +15,6 @@ internal readonly record struct FusionArchiveReadOptions( public static FusionArchiveReadOptions Default { get; } = new( 50_000_000, 512_000, - 100_000_000); + 100_000_000, + 512_000_000); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Packaging/HotChocolate.Fusion.Packaging.csproj b/src/HotChocolate/Fusion/src/Fusion.Packaging/HotChocolate.Fusion.Packaging.csproj index 4423e0f6d80..309074d0c98 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Packaging/HotChocolate.Fusion.Packaging.csproj +++ b/src/HotChocolate/Fusion/src/Fusion.Packaging/HotChocolate.Fusion.Packaging.csproj @@ -10,4 +10,9 @@ + + + + + diff --git a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchive.cs b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchive.cs index 84325dcdc61..b50031089ca 100644 --- a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchive.cs +++ b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchive.cs @@ -2,6 +2,7 @@ using System.IO.Compression; using System.IO.Pipelines; using System.Text.Json; +using HotChocolate.Fusion.Packaging.Storage; using HotChocolate.Fusion.SourceSchema.Packaging.Serializers; namespace HotChocolate.Fusion.SourceSchema.Packaging; @@ -26,13 +27,14 @@ private FusionSourceSchemaArchive( Stream stream, FusionSourceSchemaArchiveMode mode, bool leaveOpen, - FusionSourceSchemaArchiveReadOptions options) + FusionSourceSchemaArchiveReadOptions options, + ArchiveEntryStorageKind storageKind) { _stream = stream; _mode = mode; _leaveOpen = leaveOpen; _archive = new ZipArchive(stream, (ZipArchiveMode)mode, leaveOpen); - _session = new FusionSourceSchemaArchiveSession(_archive, mode, options); + _session = new FusionSourceSchemaArchiveSession(_archive, mode, options, storageKind); } /// @@ -44,7 +46,12 @@ private FusionSourceSchemaArchive( public static FusionSourceSchemaArchive Create(string filename) { ArgumentNullException.ThrowIfNull(filename); - return Create(File.Create(filename)); + return new FusionSourceSchemaArchive( + File.Create(filename), + FusionSourceSchemaArchiveMode.Create, + leaveOpen: false, + FusionSourceSchemaArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile); } /// @@ -57,7 +64,29 @@ public static FusionSourceSchemaArchive Create(string filename) public static FusionSourceSchemaArchive Create(Stream stream, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(stream); - return new FusionSourceSchemaArchive(stream, FusionSourceSchemaArchiveMode.Create, leaveOpen, FusionSourceSchemaArchiveReadOptions.Default); + return new FusionSourceSchemaArchive( + stream, + FusionSourceSchemaArchiveMode.Create, + leaveOpen, + FusionSourceSchemaArchiveReadOptions.Default, + ArchiveEntryStorageKind.Memory); + } + + /// + /// Creates a new Fusion source schema archive using the provided stream and options. + /// + public static FusionSourceSchemaArchive Create( + Stream stream, + FusionSourceSchemaArchiveOptions options, + bool leaveOpen = false) + { + ArgumentNullException.ThrowIfNull(stream); + return new FusionSourceSchemaArchive( + stream, + FusionSourceSchemaArchiveMode.Create, + leaveOpen, + CreateReadOptions(options), + ArchiveEntryStorageKind.Memory); } /// @@ -76,9 +105,19 @@ public static FusionSourceSchemaArchive Open( return mode switch { - FusionSourceSchemaArchiveMode.Read => Open(File.OpenRead(filename), mode), - FusionSourceSchemaArchiveMode.Create => Create(File.Create(filename)), - FusionSourceSchemaArchiveMode.Update => Open(File.Open(filename, FileMode.Open, FileAccess.ReadWrite), mode), + FusionSourceSchemaArchiveMode.Read => new FusionSourceSchemaArchive( + File.OpenRead(filename), + mode, + leaveOpen: false, + FusionSourceSchemaArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile), + FusionSourceSchemaArchiveMode.Create => Create(filename), + FusionSourceSchemaArchiveMode.Update => new FusionSourceSchemaArchive( + File.Open(filename, FileMode.Open, FileAccess.ReadWrite), + mode, + leaveOpen: false, + FusionSourceSchemaArchiveReadOptions.Default, + ArchiveEntryStorageKind.TempFile), _ => throw new ArgumentException("Invalid mode.", nameof(mode)) }; } @@ -99,12 +138,24 @@ public static FusionSourceSchemaArchive Open( FusionSourceSchemaArchiveOptions options = default) { ArgumentNullException.ThrowIfNull(stream); - var readOptions = new FusionSourceSchemaArchiveReadOptions( - options.MaxAllowedSchemaSize ?? FusionSourceSchemaArchiveReadOptions.Default.MaxAllowedSchemaSize, - options.MaxAllowedSettingsSize ?? FusionSourceSchemaArchiveReadOptions.Default.MaxAllowedSettingsSize); - return new FusionSourceSchemaArchive(stream, mode, leaveOpen, readOptions); + return new FusionSourceSchemaArchive( + stream, + mode, + leaveOpen, + CreateReadOptions(options), + ArchiveEntryStorageKind.Memory); } + private static FusionSourceSchemaArchiveReadOptions CreateReadOptions( + FusionSourceSchemaArchiveOptions options) + => new( + options.MaxAllowedSchemaSize + ?? FusionSourceSchemaArchiveReadOptions.Default.MaxAllowedSchemaSize, + options.MaxAllowedSettingsSize + ?? FusionSourceSchemaArchiveReadOptions.Default.MaxAllowedSettingsSize, + options.MaxAllowedInMemorySessionSize + ?? FusionSourceSchemaArchiveReadOptions.Default.MaxAllowedInMemorySessionSize); + /// /// Sets the archive metadata. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveOptions.cs b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveOptions.cs index 0cf28a8bf3f..1931376fa7b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveOptions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveOptions.cs @@ -14,4 +14,9 @@ public struct FusionSourceSchemaArchiveOptions /// Gets or sets the maximum allowed size of the settings in the archive. /// public int? MaxAllowedSettingsSize { get; set; } + + /// + /// Gets or sets the maximum total size of entries staged in memory for a stream-based archive. + /// + public int? MaxAllowedInMemorySessionSize { get; set; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveReadOptions.cs b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveReadOptions.cs index 4365ff7eb99..6fa86a1a2b5 100644 --- a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveReadOptions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveReadOptions.cs @@ -5,11 +5,12 @@ namespace HotChocolate.Fusion.SourceSchema.Packaging; /// internal readonly record struct FusionSourceSchemaArchiveReadOptions( int MaxAllowedSchemaSize, - int MaxAllowedSettingsSize) + int MaxAllowedSettingsSize, + int MaxAllowedInMemorySessionSize) { /// /// Gets the default read options. /// public static FusionSourceSchemaArchiveReadOptions Default { get; } - = new(50_000_000, 512_000); + = new(50_000_000, 512_000, 128_000_000); } diff --git a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveSession.cs b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveSession.cs index bd5bbebd717..25527ae804f 100644 --- a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveSession.cs +++ b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/FusionSourceSchemaArchiveSession.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.IO.Compression; +using HotChocolate.Fusion.Packaging.Storage; namespace HotChocolate.Fusion.SourceSchema.Packaging; @@ -8,19 +9,23 @@ internal sealed class FusionSourceSchemaArchiveSession : IDisposable private readonly Dictionary _files = []; private readonly ZipArchive _archive; private readonly FusionSourceSchemaArchiveReadOptions _readOptions; + private readonly ArchiveEntryStorageKind _storageKind; private FusionSourceSchemaArchiveMode _mode; + private long _storedBytes; private bool _disposed; public FusionSourceSchemaArchiveSession( ZipArchive archive, FusionSourceSchemaArchiveMode mode, - FusionSourceSchemaArchiveReadOptions readOptions) + FusionSourceSchemaArchiveReadOptions readOptions, + ArchiveEntryStorageKind storageKind) { ArgumentNullException.ThrowIfNull(archive); _archive = archive; _mode = mode; _readOptions = readOptions; + _storageKind = storageKind; } public bool HasUncommittedChanges @@ -28,14 +33,16 @@ public bool HasUncommittedChanges public IEnumerable GetFiles() { - var tempFiles = _files.Where(file => file.Value.State is not FileState.Deleted).Select(file => file.Key); + var stagedFiles = _files + .Where(file => file.Value.State is not FileState.Deleted) + .Select(file => file.Key); if (_mode is FusionSourceSchemaArchiveMode.Create) { - return tempFiles; + return stagedFiles; } - var files = new HashSet(tempFiles); + var files = new HashSet(stagedFiles); foreach (var entry in _archive.Entries) { @@ -45,7 +52,10 @@ public IEnumerable GetFiles() return files; } - public async Task ExistsAsync(string path, FileKind kind, CancellationToken cancellationToken) + public async Task ExistsAsync( + string path, + FileKind kind, + CancellationToken cancellationToken) { if (_files.TryGetValue(path, out var file)) { @@ -54,8 +64,7 @@ public async Task ExistsAsync(string path, FileKind kind, CancellationToke if (_mode is not FusionSourceSchemaArchiveMode.Create && _archive.GetEntry(path) is { } entry) { - file = FileEntry.Read(path); - await ExtractFileAsync(entry, file, GetAllowedSize(kind), cancellationToken); + file = await ExtractFileAsync(entry, path, GetAllowedSize(kind), cancellationToken); _files.Add(path, file); return true; } @@ -73,7 +82,10 @@ public bool Exists(string path) return _mode is not FusionSourceSchemaArchiveMode.Create && _archive.GetEntry(path) is not null; } - public async Task OpenReadAsync(string path, FileKind kind, CancellationToken cancellationToken) + public async Task OpenReadAsync( + string path, + FileKind kind, + CancellationToken cancellationToken) { if (_files.TryGetValue(path, out var file)) { @@ -82,16 +94,14 @@ public async Task OpenReadAsync(string path, FileKind kind, Cancellation throw new FileNotFoundException(path); } - return File.OpenRead(file.TempPath); + return file.Storage.OpenRead(); } if (_mode is not FusionSourceSchemaArchiveMode.Create && _archive.GetEntry(path) is { } entry) { - file = FileEntry.Read(path); - await ExtractFileAsync(entry, file, GetAllowedSize(kind), cancellationToken); - var stream = File.OpenRead(file.TempPath); + file = await ExtractFileAsync(entry, path, GetAllowedSize(kind), cancellationToken); _files.Add(path, file); - return stream; + return file.Storage.OpenRead(); } throw new FileNotFoundException(path); @@ -104,23 +114,35 @@ public Stream OpenWrite(string path) throw new InvalidOperationException("Cannot write to a read-only archive."); } - if (_files.TryGetValue(path, out var file)) + if (!_files.TryGetValue(path, out var file)) + { + var state = _mode is not FusionSourceSchemaArchiveMode.Create + && _archive.GetEntry(path) is not null + ? FileState.Replaced + : FileState.Created; + file = new FileEntry(path, ArchiveEntryStorage.Create(_storageKind), state); + _files.Add(path, file); + } + else { file.MarkMutated(); - return File.Open(file.TempPath, FileMode.Create, FileAccess.Write); } - if (_mode is not FusionSourceSchemaArchiveMode.Create && _archive.GetEntry(path) is not null) + if (_storageKind is ArchiveEntryStorageKind.TempFile) { - file = FileEntry.Read(path); - file.MarkMutated(); + return file.Storage.OpenWrite(int.MaxValue); } - file ??= FileEntry.Created(path); - var stream = File.Open(file.TempPath, FileMode.Create, FileAccess.Write); - _files.Add(path, file); + var previousLength = file.Storage.Length; + var remainingSessionBytes = checked( + _readOptions.MaxAllowedInMemorySessionSize - _storedBytes + previousLength); + var maximumLength = (int)Math.Min( + Math.Max(0, remainingSessionBytes), + GetAllowedSize(GetFileKind(path))); - return stream; + return file.Storage.OpenWrite( + maximumLength, + length => _storedBytes = checked(_storedBytes - previousLength + length)); } public void SetMode(FusionSourceSchemaArchiveMode mode) @@ -135,12 +157,12 @@ public async Task CommitAsync(CancellationToken cancellationToken) switch (file.State) { case FileState.Created: - await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); + await CreateEntryFromStorageAsync(file, cancellationToken); break; case FileState.Replaced: _archive.GetEntry(file.Path)?.Delete(); - await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken); + await CreateEntryFromStorageAsync(file, cancellationToken); break; case FileState.Deleted: @@ -152,41 +174,43 @@ public async Task CommitAsync(CancellationToken cancellationToken) } } - /// - /// Creates a ZIP entry from a file with a deterministic timestamp. - /// Using a fixed timestamp ensures binary reproducibility of the archive. - /// - private async Task CreateEntryFromFileAsync( - string sourceFileName, - string entryName, + private async Task CreateEntryFromStorageAsync( + FileEntry file, CancellationToken cancellationToken) { - var entry = _archive.CreateEntry(entryName); - // Use a fixed timestamp to ensure deterministic archive output + var entry = _archive.CreateEntry(file.Path); entry.LastWriteTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero); - await using var source = File.OpenRead(sourceFileName); #if NET10_0_OR_GREATER await using var destination = await entry.OpenAsync(cancellationToken); #else await using var destination = entry.Open(); #endif - await source.CopyToAsync(destination, cancellationToken); + await file.Storage.CopyToAsync(destination, cancellationToken); } - private static async Task ExtractFileAsync( + private async Task ExtractFileAsync( ZipArchiveEntry zipEntry, - FileEntry fileEntry, + string path, int maxAllowedSize, CancellationToken cancellationToken) { + var file = new FileEntry( + path, + ArchiveEntryStorage.Create(_storageKind), + FileState.Read); var buffer = ArrayPool.Shared.Rent(4096); - var consumed = 0; + var consumed = 0L; try { await using var readStream = zipEntry.Open(); - await using var writeStream = File.Open(fileEntry.TempPath, FileMode.Create, FileAccess.Write); + await using var writeStream = file.Storage.OpenWrite( + _storageKind is ArchiveEntryStorageKind.Memory + ? (int)Math.Min( + maxAllowedSize, + Math.Max(0L, _readOptions.MaxAllowedInMemorySessionSize - _storedBytes)) + : int.MaxValue); int read; while ((read = await readStream.ReadAsync(buffer, cancellationToken)) > 0) @@ -199,25 +223,52 @@ private static async Task ExtractFileAsync( $"File is too large and exceeds the allowed size of {maxAllowedSize}."); } + if (_storageKind is ArchiveEntryStorageKind.Memory + && _storedBytes + consumed > _readOptions.MaxAllowedInMemorySessionSize) + { + throw new InvalidOperationException( + "The archive exceeds the allowed in-memory session size of " + + $"{_readOptions.MaxAllowedInMemorySessionSize}."); + } + await writeStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken); } + + if (_storageKind is ArchiveEntryStorageKind.Memory) + { + _storedBytes += file.Storage.Length; + } + + return file; + } + catch + { + file.Storage.Dispose(); + throw; } finally { - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(buffer, clearArray: true); } } private int GetAllowedSize(FileKind kind) => kind switch { - FileKind.GraphQLSchema - => _readOptions.MaxAllowedSchemaSize, - FileKind.Settings or FileKind.Metadata - => _readOptions.MaxAllowedSettingsSize, + FileKind.GraphQLSchema => _readOptions.MaxAllowedSchemaSize, + FileKind.Settings or FileKind.Metadata => _readOptions.MaxAllowedSettingsSize, _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null) }; + private static FileKind GetFileKind(string path) + => Path.GetFileName(path) switch + { + FileNames.GraphQLSchema or FileNames.GraphQLSchemaExtensions => FileKind.GraphQLSchema, + FileNames.ArchiveMetadata => FileKind.Metadata, + FileNames.Settings => FileKind.Settings, + _ => FileKind.Settings + }; + public void Dispose() { if (_disposed) @@ -227,36 +278,23 @@ public void Dispose() foreach (var file in _files.Values) { - if (file.State is not FileState.Deleted && File.Exists(file.TempPath)) - { - try - { - File.Delete(file.TempPath); - } - catch - { - // ignore - } - } + file.Storage.Dispose(); } + _storedBytes = 0; _disposed = true; } - private class FileEntry + private sealed class FileEntry( + string path, + ArchiveEntryStorage storage, + FileState state) { - private FileEntry(string path, string tempPath, FileState state) - { - Path = path; - TempPath = tempPath; - State = state; - } + public string Path { get; } = path; - public string Path { get; } + public ArchiveEntryStorage Storage { get; } = storage; - public string TempPath { get; } - - public FileState State { get; private set; } + public FileState State { get; private set; } = state; public void MarkMutated() { @@ -270,19 +308,6 @@ public void MarkRead() { State = FileState.Read; } - - public static FileEntry Created(string path) - => new(path, GetRandomTempFileName(), FileState.Created); - - public static FileEntry Read(string path) - => new(path, GetRandomTempFileName(), FileState.Read); - - private static string GetRandomTempFileName() - { - var tempDir = System.IO.Path.GetTempPath(); - var fileName = System.IO.Path.GetRandomFileName(); - return System.IO.Path.Combine(tempDir, fileName); - } } private enum FileState diff --git a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/HotChocolate.Fusion.SourceSchema.Packaging.csproj b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/HotChocolate.Fusion.SourceSchema.Packaging.csproj index f258e4e6093..c479a1a6899 100644 --- a/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/HotChocolate.Fusion.SourceSchema.Packaging.csproj +++ b/src/HotChocolate/Fusion/src/Fusion.SourceSchema.Packaging/HotChocolate.Fusion.SourceSchema.Packaging.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/FusionArchiveStorageTests.cs b/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/FusionArchiveStorageTests.cs new file mode 100644 index 00000000000..b2ee742a15a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/FusionArchiveStorageTests.cs @@ -0,0 +1,178 @@ +using System.IO.Compression; +using System.Text.Json; + +namespace HotChocolate.Fusion.Packaging; + +public sealed class FusionArchiveStorageTests +{ + [Fact] + public async Task StreamCreateAndOpen_Should_NotCreateTempFiles() + { + var before = SnapshotFusionTempFiles(); + await using var stream = new MemoryStream(); + + using (var archive = FusionArchive.Create(stream, leaveOpen: true)) + { + await using var content = new MemoryStream("archive"u8.ToArray()); + await archive.SetLegacyArchiveFileAsync(content, TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + stream.Position = 0; + using (var archive = FusionArchive.Open(stream, leaveOpen: true)) + { + await using var content = await archive.TryGetLegacyArchiveFileAsync( + TestContext.Current.CancellationToken); + Assert.NotNull(content); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + + [Fact] + public async Task PathOpen_Should_CleanTempFile_WhenExtractionIsCanceled() + { + var archivePath = System.IO.Path.GetTempFileName(); + + try + { + CreateZipEntry(archivePath, "legacy-v1-archive.fgp", "archive"); + var before = SnapshotFusionTempFiles(); + using var cancellation = new CancellationTokenSource(); + cancellation.Cancel(); + + using (var archive = FusionArchive.Open(archivePath)) + { + await Assert.ThrowsAnyAsync( + () => archive.TryGetLegacyArchiveFileAsync(cancellation.Token)); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + finally + { + File.Delete(archivePath); + } + } + + [Fact] + public async Task PathOpen_Should_CleanTempFile_WhenEntryIsOversized() + { + var archivePath = System.IO.Path.GetTempFileName(); + + try + { + CreateZipEntry( + archivePath, + "composition-settings.json", + new string('a', 512_001)); + var before = SnapshotFusionTempFiles(); + + using (var archive = FusionArchive.Open(archivePath)) + { + await Assert.ThrowsAsync( + () => archive.GetCompositionSettingsAsync(TestContext.Current.CancellationToken)); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + finally + { + File.Delete(archivePath); + } + } + + [Fact] + public async Task StreamCreate_Should_EnforceEntrySizeIncrementally() + { + await using var stream = new MemoryStream(); + using var archive = FusionArchive.Create( + stream, + new FusionArchiveOptions + { + MaxAllowedLegacyArchiveSize = 4, + MaxAllowedInMemorySessionSize = 100 + }); + await using var content = new MemoryStream("12345"u8.ToArray()); + + var exception = await Assert.ThrowsAsync( + () => archive.SetLegacyArchiveFileAsync(content, TestContext.Current.CancellationToken)); + + Assert.Equal("File is too large and exceeds the allowed size of 4.", exception.Message); + } + + [Fact] + public async Task StreamCreate_Should_EnforceTotalSessionSizeIncrementally() + { + await using var stream = new MemoryStream(); + using var archive = FusionArchive.Create( + stream, + new FusionArchiveOptions + { + MaxAllowedLegacyArchiveSize = 10, + MaxAllowedSettingsSize = 10, + MaxAllowedInMemorySessionSize = 6 + }); + await using var content = new MemoryStream("12345"u8.ToArray()); + await archive.SetLegacyArchiveFileAsync(content, TestContext.Current.CancellationToken); + using var settings = JsonDocument.Parse("{}"); + + var exception = await Assert.ThrowsAsync( + () => archive.SetCompositionSettingsAsync( + settings, + TestContext.Current.CancellationToken)); + + Assert.Equal("File is too large and exceeds the allowed size of 1.", exception.Message); + } + + [Fact] + public async Task MemoryAndTempStorage_Should_CreateIdenticalArchives() + { + var archivePath = System.IO.Path.GetTempFileName(); + var content = "deterministic archive"u8.ToArray(); + + try + { + using (var archive = FusionArchive.Create(archivePath)) + { + await using var input = new MemoryStream(content); + await archive.SetLegacyArchiveFileAsync(input, TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + await using var stream = new MemoryStream(); + using (var archive = FusionArchive.Create(stream, leaveOpen: true)) + { + await using var input = new MemoryStream(content); + await archive.SetLegacyArchiveFileAsync(input, TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + Assert.Equal( + await File.ReadAllBytesAsync( + archivePath, + TestContext.Current.CancellationToken), + stream.ToArray()); + } + finally + { + File.Delete(archivePath); + } + } + + private static string[] SnapshotFusionTempFiles() + => Directory + .EnumerateFiles( + System.IO.Path.GetTempPath(), + $"hotchocolate-fusion-{Environment.ProcessId}-*.tmp") + .Order(StringComparer.Ordinal) + .ToArray(); + + private static void CreateZipEntry(string archivePath, string entryName, string content) + { + using var archive = ZipFile.Open(archivePath, ZipArchiveMode.Update); + var entry = archive.CreateEntry(entryName); + using var writer = new StreamWriter(entry.Open()); + writer.Write(content); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/PooledMemoryArchiveEntryStorageTests.cs b/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/PooledMemoryArchiveEntryStorageTests.cs new file mode 100644 index 00000000000..2045d824b93 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Packaging.Tests/PooledMemoryArchiveEntryStorageTests.cs @@ -0,0 +1,45 @@ +using System.Buffers; +using HotChocolate.Fusion.Packaging.Storage; + +namespace HotChocolate.Fusion.Packaging; + +public sealed class PooledMemoryArchiveEntryStorageTests +{ + [Fact] + public void Dispose_Should_ClearRentedBufferBeforeReturningIt() + { + var pool = new RecordingArrayPool(); + var storage = new PooledMemoryArchiveEntryStorage(pool); + using (var stream = storage.OpenWrite(16)) + { + stream.Write("sensitive"u8); + } + + storage.Dispose(); + + Assert.True(pool.ClearArray); + Assert.NotNull(pool.ReturnedArray); + Assert.All(pool.ReturnedArray, value => Assert.Equal(0, value)); + } + + private sealed class RecordingArrayPool : ArrayPool + { + public byte[]? ReturnedArray { get; private set; } + + public bool ClearArray { get; private set; } + + public override byte[] Rent(int minimumLength) + => new byte[minimumLength]; + + public override void Return(byte[] array, bool clearArray = false) + { + if (clearArray) + { + array.AsSpan().Clear(); + } + + ReturnedArray = array; + ClearArray = clearArray; + } + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.SourceSchema.Packaging.Tests/FusionSourceSchemaArchiveStorageTests.cs b/src/HotChocolate/Fusion/test/Fusion.SourceSchema.Packaging.Tests/FusionSourceSchemaArchiveStorageTests.cs new file mode 100644 index 00000000000..4ac8f85158d --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.SourceSchema.Packaging.Tests/FusionSourceSchemaArchiveStorageTests.cs @@ -0,0 +1,184 @@ +using System.IO.Compression; +using System.Text.Json; + +namespace HotChocolate.Fusion.SourceSchema.Packaging; + +public sealed class FusionSourceSchemaArchiveStorageTests +{ + [Fact] + public async Task StreamCreateAndOpen_Should_NotCreateTempFiles() + { + var before = SnapshotFusionTempFiles(); + await using var stream = new MemoryStream(); + + using (var archive = FusionSourceSchemaArchive.Create(stream, leaveOpen: true)) + { + await SetRequiredEntriesAsync(archive); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + stream.Position = 0; + using (var archive = FusionSourceSchemaArchive.Open(stream, leaveOpen: true)) + { + Assert.NotNull(await archive.GetArchiveMetadataAsync( + TestContext.Current.CancellationToken)); + Assert.NotNull(await archive.TryGetSchemaAsync( + TestContext.Current.CancellationToken)); + Assert.NotNull(await archive.TryGetSettingsAsync( + TestContext.Current.CancellationToken)); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + + [Fact] + public async Task PathOpen_Should_CleanTempFile_WhenExtractionIsCanceled() + { + var archivePath = System.IO.Path.GetTempFileName(); + + try + { + CreateZipEntry(archivePath, "schema.graphqls", "type Query { field: String }"); + var before = SnapshotFusionTempFiles(); + using var cancellation = new CancellationTokenSource(); + cancellation.Cancel(); + + using (var archive = FusionSourceSchemaArchive.Open(archivePath)) + { + await Assert.ThrowsAnyAsync( + () => archive.TryGetSchemaAsync(cancellation.Token)); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + finally + { + File.Delete(archivePath); + } + } + + [Fact] + public async Task PathOpen_Should_CleanTempFile_WhenEntryIsOversized() + { + var archivePath = System.IO.Path.GetTempFileName(); + + try + { + CreateZipEntry(archivePath, "schema-settings.json", new string('a', 512_001)); + var before = SnapshotFusionTempFiles(); + + using (var archive = FusionSourceSchemaArchive.Open(archivePath)) + { + await Assert.ThrowsAsync( + () => archive.TryGetSettingsAsync(TestContext.Current.CancellationToken)); + } + + Assert.Equal(before, SnapshotFusionTempFiles()); + } + finally + { + File.Delete(archivePath); + } + } + + [Fact] + public async Task StreamCreate_Should_EnforceEntrySizeIncrementally() + { + await using var stream = new MemoryStream(); + using var archive = FusionSourceSchemaArchive.Create( + stream, + new FusionSourceSchemaArchiveOptions + { + MaxAllowedSchemaSize = 4, + MaxAllowedInMemorySessionSize = 100 + }); + + var exception = await Assert.ThrowsAsync( + () => archive.SetSchemaAsync( + "12345"u8.ToArray(), + TestContext.Current.CancellationToken)); + + Assert.Equal("File is too large and exceeds the allowed size of 4.", exception.Message); + } + + [Fact] + public async Task StreamCreate_Should_EnforceTotalSessionSizeIncrementally() + { + await using var stream = new MemoryStream(); + using var archive = FusionSourceSchemaArchive.Create( + stream, + new FusionSourceSchemaArchiveOptions + { + MaxAllowedSchemaSize = 10, + MaxAllowedInMemorySessionSize = 6 + }); + await archive.SetSchemaAsync("12345"u8.ToArray(), TestContext.Current.CancellationToken); + + var exception = await Assert.ThrowsAsync( + () => archive.SetSchemaExtensionsAsync( + "12"u8.ToArray(), + TestContext.Current.CancellationToken)); + + Assert.Equal("File is too large and exceeds the allowed size of 1.", exception.Message); + } + + [Fact] + public async Task MemoryAndTempStorage_Should_CreateIdenticalArchives() + { + var archivePath = System.IO.Path.GetTempFileName(); + + try + { + using (var archive = FusionSourceSchemaArchive.Create(archivePath)) + { + await SetRequiredEntriesAsync(archive); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + await using var stream = new MemoryStream(); + using (var archive = FusionSourceSchemaArchive.Create(stream, leaveOpen: true)) + { + await SetRequiredEntriesAsync(archive); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + Assert.Equal( + await File.ReadAllBytesAsync( + archivePath, + TestContext.Current.CancellationToken), + stream.ToArray()); + } + finally + { + File.Delete(archivePath); + } + } + + private static async Task SetRequiredEntriesAsync(FusionSourceSchemaArchive archive) + { + await archive.SetArchiveMetadataAsync( + new ArchiveMetadata { FormatVersion = new Version(2, 0) }, + TestContext.Current.CancellationToken); + await archive.SetSchemaAsync( + "type Query { field: String }"u8.ToArray(), + TestContext.Current.CancellationToken); + using var settings = JsonDocument.Parse("{}"); + await archive.SetSettingsAsync(settings, TestContext.Current.CancellationToken); + } + + private static string[] SnapshotFusionTempFiles() + => Directory + .EnumerateFiles( + System.IO.Path.GetTempPath(), + $"hotchocolate-fusion-{Environment.ProcessId}-*.tmp") + .Order(StringComparer.Ordinal) + .ToArray(); + + private static void CreateZipEntry(string archivePath, string entryName, string content) + { + using var archive = ZipFile.Open(archivePath, ZipArchiveMode.Update); + var entry = archive.CreateEntry(entryName); + using var writer = new StreamWriter(entry.Open()); + writer.Write(content); + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs new file mode 100644 index 00000000000..5322523cad6 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs @@ -0,0 +1,94 @@ +namespace ChilliCream.Nitro.Aspire; + +internal sealed class BoundedMemoryStream(int maximumLength) + : MemoryStream +{ + public override void SetLength(long value) + { + EnsureCapacity(value); + base.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + EnsureWrite(count); + base.Write(buffer, offset, count); + } + + public override void Write(ReadOnlySpan buffer) + { + EnsureWrite(buffer.Length); + base.Write(buffer); + } + + public override Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken) + { + EnsureWrite(count); + return base.WriteAsync( + buffer, + offset, + count, + cancellationToken); + } + + public override ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default) + { + EnsureWrite(buffer.Length); + return base.WriteAsync(buffer, cancellationToken); + } + + public override void WriteByte(byte value) + { + EnsureWrite(1); + base.WriteByte(value); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Array.Clear(GetBuffer()); + } + + base.Dispose(disposing); + } + + private void EnsureWrite(int count) + => EnsureCapacity(checked(Position + count)); + + private void ThrowLimitExceeded() + => throw new InvalidDataException( + "The composed Fusion archive exceeds the " + + $"{maximumLength:N0}-byte in-memory size limit."); + + private void EnsureCapacity(long length) + { + if (length > maximumLength) + { + ThrowLimitExceeded(); + } + } +} + +internal sealed record FusionPipelineMemoryLimits( + int SourceArchiveBytes, + long TotalSourceArchiveBytes, + int FusionArchiveBytes) +{ + public const int DefaultSourceArchiveBytes = 128_000_000; + + public const int DefaultTotalSourceArchiveBytes = 512_000_000; + + public const int DefaultFusionArchiveBytes = 256_000_000; + + public static FusionPipelineMemoryLimits Default { get; } = new( + DefaultSourceArchiveBytes, + DefaultTotalSourceArchiveBytes, + DefaultFusionArchiveBytes); +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md index 2fce04d5193..9f15d066fd7 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md @@ -85,19 +85,24 @@ export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-publish \ --apphost ./src/AppHost/AppHost.csproj \ --environment Development \ - --output-path "$RUNNER_TEMP/aspire-apply/Development" \ --non-interactive ``` Publish infers the complete, sorted source-name set from the current AppHost. Before source compute is changed it downloads every exact `name@tag` from the selected Nitro API. A missing source fails -the deployment. Download records an atomic apply state binding the tag, Nitro target, complete -source set, archive paths, and canonical content digests. - -Composition revalidates every downloaded archive and resolves its settings with the current -AppHost composition options and selected composition environment. Readiness and publication -revalidate the apply state and composed FAR digest. Publish never exports a schema, reads a source -checkout, invokes Git, or calls the source-upload API. +the deployment. This preflight records only identities and canonical content digests, then clears +the archive buffers before provider deployment begins. + +After every source provider deploys, composition downloads the exact versions again, rejects any +canonical digest change from preflight, and resolves their settings with the current AppHost +composition options and selected composition environment. Readiness and publication +revalidate the session state and composed FAR digest. Publish never exports a schema, reads a +source checkout, invokes Git, or calls the source-upload API. The Fusion-specific download, +composition, readiness, and publication steps create no Fusion apply-state files and do not resolve +Aspire's output-path service. Provider-contributed deployment dependencies may still write target +artifacts or Aspire deployment state. Source archives are limited to 128,000,000 bytes each and +512,000,000 bytes in aggregate; the FAR is limited to 256,000,000 bytes. Owned buffers are cleared +after success, failure, or cancellation. ## Ordering @@ -110,8 +115,7 @@ fusion-artifacts -> fusion-upload The deployment graph is: ```text -fusion-download -> source DeployCompute -> fusion-readiness - \-> fusion-compose ------/ +fusion-download -> source DeployCompute -> fusion-compose -> fusion-readiness fusion-readiness -> fusion-publish-stage @@ -119,7 +123,8 @@ fusion-readiness -> fusion-publish ``` -`fusion-download` is a fail-before-compute preflight. `fusion-publish-stage` is internal. The public +`fusion-download` is a fail-before-compute, metadata-only preflight. `fusion-compose` performs the +second exact download after source compute. `fusion-publish-stage` is internal. The public `fusion-publish` step is terminal and completes only after gateway deployment. The broader `aspire deploy` root requires the same terminal step. @@ -170,7 +175,7 @@ and per Nitro stage for publish. Queue writers instead of cancelling them. - An existing `name@tag` with different content is an immutable-version collision and fails. - A missing exact source during publish fails before source compute deployment. - A changed AppHost source set, tag, target, downloaded archive, composition environment, or FAR - fails apply-state validation. + fails in-memory session validation. - A transient source endpoint is polled until the configured operation timeout. - Approval rejection, timeout, failed commit, or unverified terminal Nitro state fails publication. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs index ea8ebd0fee3..5c081fc6b23 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs @@ -94,16 +94,25 @@ private static IEnumerable CreateSteps( topology.HasDeployments = deployments.Count > 0; - return CreateStepDefinitions(context.Resource, topology); + var session = new FusionPipelineSession( + context.PipelineContext.CancellationToken); + return CreateStepDefinitions( + context.Resource, + topology, + session); } internal static PipelineStep[] CreateStepDefinitionsForTest( IResource resource) - => CreateStepDefinitions(resource, new FusionPipelineTopology()); + => CreateStepDefinitions( + resource, + new FusionPipelineTopology(), + new FusionPipelineSession()); private static PipelineStep[] CreateStepDefinitions( IResource resource, - FusionPipelineTopology topology) + FusionPipelineTopology topology, + FusionPipelineSession session) { var buildSteps = new[] { @@ -133,7 +142,11 @@ private static PipelineStep[] CreateStepDefinitions( Name = DownloadStepName, Description = "Download exact Fusion source schema versions from Nitro.", Resource = resource, - Action = ExecuteDownloadAsync + Action = context => ExecuteSessionStepAsync( + context, + session, + static (executor, stepContext, pipelineSession) => + executor.PreflightAsync(stepContext, pipelineSession)) }, new PipelineStep { @@ -141,7 +154,18 @@ private static PipelineStep[] CreateStepDefinitions( Description = "Compose the Fusion configuration for this environment.", Resource = resource, DependsOnSteps = [DownloadStepName], - Action = ExecuteComposeAsync + Action = context => ExecuteSessionStepAsync( + context, + session, + static async (executor, stepContext, pipelineSession) => + { + await executor.DownloadAsync( + stepContext, + pipelineSession); + await executor.ComposeAsync( + stepContext, + pipelineSession); + }) }, new PipelineStep { @@ -149,7 +173,17 @@ private static PipelineStep[] CreateStepDefinitions( Description = "Verify deployed Fusion source services are ready.", Resource = resource, DependsOnSteps = [ComposeStepName], - Action = stepContext => ExecuteReadinessAsync(stepContext, topology) + Action = context => ExecuteSessionStepAsync( + context, + session, + (executor, stepContext, pipelineSession) => + { + EnsureResourceDeploymentOrdering( + topology.ResourcesWithoutCompute); + return executor.VerifyReadinessAsync( + stepContext, + pipelineSession); + }) }, new PipelineStep { @@ -157,7 +191,11 @@ private static PipelineStep[] CreateStepDefinitions( Description = "Publish the Fusion configuration to Nitro.", Resource = resource, DependsOnSteps = [ReadinessStepName], - Action = ExecutePublishAsync + Action = context => ExecuteSessionStepAsync( + context, + session, + static (executor, stepContext, pipelineSession) => + executor.PublishAsync(stepContext, pipelineSession)) }, new PipelineStep { @@ -166,7 +204,11 @@ private static PipelineStep[] CreateStepDefinitions( Resource = resource, DependsOnSteps = [PublishStageStepName], RequiredBySteps = [WellKnownPipelineSteps.Deploy], - Action = _ => Task.CompletedTask + Action = _ => + { + session.Dispose(); + return Task.CompletedTask; + } } ]; } @@ -188,6 +230,8 @@ private static void ConfigureSteps( step => step.Name == DownloadStepName); var readiness = context.Steps.Single( step => step.Name == ReadinessStepName); + var compose = context.Steps.Single( + step => step.Name == ComposeStepName); var stagePublication = context.Steps.Single( step => step.Name == PublishStageStepName); var publication = context.Steps.Single( @@ -210,6 +254,7 @@ private static void ConfigureSteps( foreach (var computeStep in computeSteps) { computeStep.DependsOn(download); + compose.DependsOn(computeStep); readiness.DependsOn(computeStep); } } @@ -279,13 +324,24 @@ internal static PipelineStep[] SelectResourceDeploymentSteps( private static Task ExecuteArtifactsAsync(PipelineStepContext context) => GetExecutor(context).CreateArtifactsAsync(context); - private static Task ExecuteReadinessAsync( + private static async Task ExecuteSessionStepAsync( PipelineStepContext context, - FusionPipelineTopology topology) + FusionPipelineSession session, + Func< + IFusionPipelineExecutor, + PipelineStepContext, + FusionPipelineSession, + Task> execute) { - EnsureResourceDeploymentOrdering(topology.ResourcesWithoutCompute); - - return GetExecutor(context).VerifyReadinessAsync(context); + try + { + await execute(GetExecutor(context), context, session); + } + catch + { + session.Dispose(); + throw; + } } internal static void EnsureResourceDeploymentOrdering( @@ -302,15 +358,6 @@ internal static void EnsureResourceDeploymentOrdering( private static Task ExecuteUploadAsync(PipelineStepContext context) => GetExecutor(context).UploadAsync(context); - private static Task ExecuteDownloadAsync(PipelineStepContext context) - => GetExecutor(context).DownloadAsync(context); - - private static Task ExecuteComposeAsync(PipelineStepContext context) - => GetExecutor(context).ComposeAsync(context); - - private static Task ExecutePublishAsync(PipelineStepContext context) - => GetExecutor(context).PublishAsync(context); - private static IFusionPipelineExecutor GetExecutor( PipelineStepContext context) => context.Services.GetService() diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs index f4f37b97c72..36d7e792ae1 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs @@ -28,6 +28,17 @@ internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor WriteIndented = true }; + private readonly FusionPipelineMemoryLimits _memoryLimits; + private readonly Action? _bufferCleared; + + internal FusionPipelineExecutor( + FusionPipelineMemoryLimits? memoryLimits = null, + Action? bufferCleared = null) + { + _memoryLimits = memoryLimits ?? FusionPipelineMemoryLimits.Default; + _bufferCleared = bufferCleared; + } + public static FusionPipelineExecutor Instance { get; } = new(); public async Task CreateArtifactsAsync(PipelineStepContext context) @@ -71,7 +82,9 @@ await CreateDeploymentArtifactsAsync( } } - public async Task VerifyReadinessAsync(PipelineStepContext context) + public async Task VerifyReadinessAsync( + PipelineStepContext context, + FusionPipelineSession session) { var environment = context.Services .GetRequiredService() @@ -89,61 +102,61 @@ public async Task VerifyReadinessAsync(PipelineStepContext context) context.Model); var composition = GraphQLResourceModel.GetComposition( compositionResource); - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; - foreach (var deployment in deployments) + try { - var applyDirectory = GetApplyDirectory(output, deployment); - var state = await ReadApplyStateAsync( - applyDirectory, - context.CancellationToken); - await ValidateApplyStateAsync( - state, - deployment, - context, - context.CancellationToken); - ValidateCompositionState( - state, - deployment, - composition.Settings); - var farPath = ResolveApplyPath( - applyDirectory, - state.FusionArchivePath - ?? throw new InvalidOperationException( - "The downloaded Fusion sources have not been composed.")); - await VerifyFileDigestAsync( - farPath, - state.FusionArchiveSha256 - ?? throw new InvalidOperationException( - "The composed Fusion archive has no digest."), - "composed Fusion archive", - context.CancellationToken); - using var archive = FusionArchive.Open(farPath); - using var configuration = - await archive.TryGetGatewayConfigurationAsync( - WellKnownVersions.LatestGatewayFormatVersion, - context.CancellationToken) - ?? throw new InvalidDataException( - "The composed Fusion archive contains no gateway configuration."); - - foreach (var (name, endpoint) in GetTransportEndpoints( - configuration.Settings)) + foreach (var deployment in deployments) { - RejectLoopbackEndpoint(endpoint); - await WaitForReadinessAsync( - httpClient, - name, - endpoint, - deployment.OperationTimeout, - s_readinessRetryDelay, + using var lease = session.Acquire(deployment); + var state = lease.State; + await ValidateSessionStateAsync( + state, + deployment, + context, context.CancellationToken); + ValidateCompositionState( + state, + deployment, + composition.Settings); + VerifyMemoryDigest( + state.FusionArchive, + state.FusionArchiveSha256 + ?? throw new InvalidOperationException( + "The composed Fusion archive has no digest."), + "composed Fusion archive"); + await using var archiveStream = new MemoryStream( + state.FusionArchive, + writable: false); + using var archive = FusionArchive.Open(archiveStream); + using var configuration = + await archive.TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + context.CancellationToken) + ?? throw new InvalidDataException( + "The composed Fusion archive contains no gateway configuration."); + + foreach (var (name, endpoint) in GetTransportEndpoints( + configuration.Settings)) + { + RejectLoopbackEndpoint(endpoint); + await WaitForReadinessAsync( + httpClient, + name, + endpoint, + deployment.OperationTimeout, + s_readinessRetryDelay, + context.CancellationToken); + } } } + catch + { + session.Clear(); + throw; + } } public async Task UploadAsync(PipelineStepContext context) @@ -177,7 +190,9 @@ await workflow.ReconcileSourceSchemaAsync( } } - public async Task DownloadAsync(PipelineStepContext context) + public async Task PreflightAsync( + PipelineStepContext context, + FusionPipelineSession session) { var environment = context.Services .GetRequiredService() @@ -192,121 +207,212 @@ public async Task DownloadAsync(PipelineStepContext context) var workflow = context.Services .GetRequiredService(); - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); var sourceNames = GetSourceNames(context.Model); + var preparedStates = new List<( + FusionDeploymentResource Deployment, + FusionDeploymentSessionState State)>(deployments.Count); + long totalSourceBytes = 0; + var transferred = false; - foreach (var deployment in deployments) + try { - var tag = await ResolveConfigurationTagAsync( - deployment, - context.CancellationToken); - var target = await ResolveTargetAsync( - deployment, - context, - context.CancellationToken); - var applyDirectory = GetApplyDirectory(output, deployment); - var applyParent = Path.GetDirectoryName(applyDirectory)!; - Directory.CreateDirectory(applyParent); - var temporaryDirectory = Path.Combine( - applyParent, - $".{deployment.Name}.{Guid.NewGuid():N}.tmp"); - - try + foreach (var deployment in deployments) { - var sources = new List(sourceNames.Count); + var tag = await ResolveConfigurationTagAsync( + deployment, + context.CancellationToken); + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var sources = new List( + sourceNames.Count); foreach (var sourceName in sourceNames) { - var download = await workflow.DownloadSourceSchemaAsync( + using var download = await DownloadExactSourceAsync( + workflow, target, - new FusionSourceSchemaVersion( - sourceName, - tag), - context.CancellationToken) - ?? throw new InvalidOperationException( - $"Fusion source '{sourceName}' version " - + $"'{tag}' does not exist on target " - + $"'{deployment.Nitro.ApiId}'."); - if (!string.Equals( - download.Name, - sourceName, - StringComparison.Ordinal) - || !string.Equals( - download.Version, - tag, - StringComparison.Ordinal)) - { - throw new InvalidDataException( - $"Nitro returned Fusion source '{download.Name}@{download.Version}' " - + $"when '{sourceName}@{tag}' was requested."); - } - - var relativePath = Path.Combine( - "sources", + deployment, sourceName, - $"{tag}.zip"); - var archivePath = Path.Combine( - temporaryDirectory, - relativePath); - Directory.CreateDirectory( - Path.GetDirectoryName(archivePath)!); - await File.WriteAllBytesAsync( - archivePath, - download.Archive.ToArray(), + tag, context.CancellationToken); - var contentSha256 = - await FusionSourceSchemaContent.ComputeSha256Async( - archivePath, - sourceName, - context.CancellationToken); - if (!string.Equals( - contentSha256, - download.ContentSha256, - StringComparison.OrdinalIgnoreCase)) + AddSourceBytes( + download.Archive.Length, + sourceName, + tag, + ref totalSourceBytes); + byte[]? archive = download.Archive.ToArray(); + try { - throw new InvalidDataException( - $"Downloaded Fusion source '{sourceName}@{tag}' content " - + "does not match its canonical digest."); + var contentSha256 = + await ValidateCanonicalDigestAsync( + download, + archive, + sourceName, + tag, + context.CancellationToken); + sources.Add( + new FusionSessionSourceIdentity( + sourceName, + tag, + contentSha256)); + } + finally + { + if (archive is not null) + { + ClearOwnedBuffer(archive); + } } - - sources.Add( - new FusionApplySource( - sourceName, - tag, - relativePath.Replace( - Path.DirectorySeparatorChar, - '/'), - download.ContentSha256)); } - await WriteJsonAtomicallyAsync( - Path.Combine( - temporaryDirectory, - "fusion-apply.json"), - new FusionApplyState( + preparedStates.Add( + (deployment, + new FusionDeploymentSessionState( tag, deployment.Nitro.CloudUrl!, deployment.Nitro.ApiId!, - CompositionEnvironment: null, - FusionArchivePath: null, - FusionArchiveSha256: null, - sources), - context.CancellationToken); + sources))); + } - ReplaceDirectoryAtomically( - temporaryDirectory, - applyDirectory); + session.SetAll(preparedStates); + transferred = true; + } + catch + { + session.Clear(); + throw; + } + finally + { + if (!transferred) + { + foreach (var (_, state) in preparedStates) + { + state.Dispose(); + } } - finally + } + } + + public async Task DownloadAsync( + PipelineStepContext context, + FusionPipelineSession session) + { + var environment = context.Services + .GetRequiredService() + .EnvironmentName; + var deployments = FusionPipeline.SelectDeployments( + context.Model, + environment); + if (deployments.Count == 0) + { + return; + } + + var workflow = context.Services + .GetRequiredService(); + long totalSourceBytes = 0; + + try + { + foreach (var deployment in deployments) { - DeleteDirectoryBestEffort(temporaryDirectory); + using var lease = session.Acquire(deployment); + var state = lease.State; + await ValidatePreflightStateAsync( + state, + deployment, + context, + context.CancellationToken); + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var sources = new List( + state.SourceIdentities.Count); + var sourceListTransferred = false; + + try + { + foreach (var identity in state.SourceIdentities) + { + using var download = await DownloadExactSourceAsync( + workflow, + target, + deployment, + identity.Name, + identity.Version, + context.CancellationToken); + AddSourceBytes( + download.Archive.Length, + identity.Name, + identity.Version, + ref totalSourceBytes); + byte[]? archive = download.Archive.ToArray(); + try + { + var contentSha256 = + await ValidateCanonicalDigestAsync( + download, + archive, + identity.Name, + identity.Version, + context.CancellationToken); + if (!string.Equals( + contentSha256, + identity.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Fusion source '{identity.Name}@{identity.Version}' " + + "changed between preflight and composition."); + } + + sources.Add( + new FusionSessionSource( + identity.Name, + identity.Version, + archive, + contentSha256)); + archive = null; + } + finally + { + if (archive is not null) + { + ClearOwnedBuffer(archive); + } + } + } + + context.CancellationToken.ThrowIfCancellationRequested(); + state.SetSources(sources); + sourceListTransferred = true; + } + finally + { + if (!sourceListTransferred) + { + foreach (var source in sources) + { + ClearOwnedBuffer(source.Archive); + } + } + } } } + catch + { + session.Clear(); + throw; + } } - public async Task ComposeAsync(PipelineStepContext context) + public async Task ComposeAsync( + PipelineStepContext context, + FusionPipelineSession session) { var environment = context.Services .GetRequiredService() @@ -319,96 +425,63 @@ public async Task ComposeAsync(PipelineStepContext context) return; } - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); var compositionResource = FusionPipeline.GetCompositionResource( context.Model); var currentComposition = GraphQLResourceModel.GetComposition( compositionResource); - foreach (var deployment in deployments) + try { - var applyDirectory = GetApplyDirectory(output, deployment); - var state = await ReadApplyStateAsync( - applyDirectory, - context.CancellationToken); - await ValidateApplyStateAsync( - state, - deployment, - context, - context.CancellationToken); - - var compositionEnvironment = ResolveCompositionEnvironment( - deployment, - currentComposition.Settings); - var farPath = Path.Combine( - applyDirectory, - "fusion-configuration.far"); - - foreach (var source in state.Sources) + foreach (var deployment in deployments) { - var archivePath = ResolveApplyPath( - applyDirectory, - source.ArchivePath); - var contentSha256 = - await FusionSourceSchemaContent.ComputeSha256Async( - archivePath, - source.Name, - context.CancellationToken); - if (!string.Equals( - contentSha256, - source.ContentSha256, - StringComparison.OrdinalIgnoreCase)) + using var lease = session.Acquire(deployment); + var state = lease.State; + await ValidateSessionStateAsync( + state, + deployment, + context, + context.CancellationToken); + + var compositionEnvironment = ResolveCompositionEnvironment( + deployment, + currentComposition.Settings); + await using var farStream = new BoundedMemoryStream( + _memoryLimits.FusionArchiveBytes); + var logger = context.Services + .GetRequiredService>(); + if (!await AspireCompositionHelper.TryComposeArchivesAsync( + farStream, + state.Sources.Select( + source => new SourceSchemaArchiveInfo( + source.Name, + source.Archive)) + .ToArray(), + compositionEnvironment, + currentComposition.Settings, + logger, + context.CancellationToken)) { - throw new InvalidDataException( - $"Prepared Fusion source '{source.Name}' content changed after download."); + throw new InvalidOperationException( + "Fusion configuration composition failed."); } - } - - if (File.Exists(farPath)) - { - File.Delete(farPath); - } - var logger = context.Services - .GetRequiredService>(); - if (!await AspireCompositionHelper.TryComposeArchivesAsync( - farPath, - state.Sources.Select( - source => new SourceSchemaArchiveInfo( - source.Name, - ResolveApplyPath( - applyDirectory, - source.ArchivePath))) - .ToArray(), + TransferComposition( + state, compositionEnvironment, - currentComposition.Settings, - logger, - context.CancellationToken)) - { - throw new InvalidOperationException( - "Fusion configuration composition failed."); + farStream.ToArray(), + context.CancellationToken); } - - await WriteJsonAtomicallyAsync( - Path.Combine(applyDirectory, "fusion-apply.json"), - state with - { - CompositionEnvironment = compositionEnvironment, - FusionArchivePath = Path.GetRelativePath( - applyDirectory, - farPath) - .Replace(Path.DirectorySeparatorChar, '/'), - FusionArchiveSha256 = await ComputeFileDigestAsync( - farPath, - context.CancellationToken) - }, - context.CancellationToken); + } + catch + { + session.Clear(); + throw; } } - public async Task PublishAsync(PipelineStepContext context) + public async Task PublishAsync( + PipelineStepContext context, + FusionPipelineSession session) { var environment = context.Services .GetRequiredService() @@ -421,9 +494,6 @@ public async Task PublishAsync(PipelineStepContext context) return; } - var output = context.Services - .GetRequiredService() - .GetOutputDirectory(); var workflow = context.Services .GetRequiredService(); var compositionResource = FusionPipeline.GetCompositionResource( @@ -431,56 +501,54 @@ public async Task PublishAsync(PipelineStepContext context) var composition = GraphQLResourceModel.GetComposition( compositionResource); - foreach (var deployment in deployments) + try { - var applyDirectory = GetApplyDirectory(output, deployment); - var state = await ReadApplyStateAsync( - applyDirectory, - context.CancellationToken); - await ValidateApplyStateAsync( - state, - deployment, - context, - context.CancellationToken); - var target = await ResolveTargetAsync( - deployment, - context, - context.CancellationToken); - var farPath = ResolveApplyPath( - applyDirectory, - state.FusionArchivePath - ?? throw new InvalidOperationException( - "The downloaded Fusion sources have not been composed.")); - ValidateCompositionState( - state, - deployment, - composition.Settings); - - await VerifyFileDigestAsync( - farPath, - state.FusionArchiveSha256 - ?? throw new InvalidOperationException( - "The composed Fusion archive has no digest."), - "composed Fusion archive", - context.CancellationToken); + foreach (var deployment in deployments) + { + using var lease = session.Acquire(deployment); + var state = lease.State; + await ValidateSessionStateAsync( + state, + deployment, + context, + context.CancellationToken); + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + ValidateCompositionState( + state, + deployment, + composition.Settings); + VerifyMemoryDigest( + state.FusionArchive, + state.FusionArchiveSha256 + ?? throw new InvalidOperationException( + "The composed Fusion archive has no digest."), + "composed Fusion archive"); - await workflow.PublishAsync( - new FusionPublicationRequest( - target, - deployment.StageName!, - state.Tag, - state.Sources - .Select(source => - new FusionSourceSchemaVersion( - source.Name, - source.Version)) - .ToArray(), - deployment.WaitForApproval, - deployment.Force, - deployment.OperationTimeout, - deployment.ApprovalTimeout), - farPath, - context.CancellationToken); + await workflow.PublishAsync( + new FusionPublicationRequest( + target, + deployment.StageName!, + state.Tag, + state.SourceIdentities + .Select(source => + new FusionSourceSchemaVersion( + source.Name, + source.Version)) + .ToArray(), + deployment.WaitForApproval, + deployment.Force, + deployment.OperationTimeout, + deployment.ApprovalTimeout), + state.FusionArchive, + context.CancellationToken); + } + } + finally + { + session.Clear(); } } @@ -952,7 +1020,7 @@ internal static string ResolveCompositionEnvironment( $"Fusion deployment '{deployment.Name}' has no composition environment."); private static void ValidateCompositionState( - FusionApplyState state, + FusionDeploymentSessionState state, FusionDeploymentResource deployment, GraphQLCompositionSettings settings) { @@ -1012,8 +1080,96 @@ internal static IReadOnlyList GetSourceNames( return sourceNames; } - private static async Task ValidateApplyStateAsync( - FusionApplyState state, + private static async Task + DownloadExactSourceAsync( + IFusionDeploymentWorkflow workflow, + FusionTarget target, + FusionDeploymentResource deployment, + string sourceName, + string tag, + CancellationToken cancellationToken) + { + var download = await workflow.DownloadSourceSchemaAsync( + target, + new FusionSourceSchemaVersion(sourceName, tag), + cancellationToken) + ?? throw new InvalidOperationException( + $"Fusion source '{sourceName}' version '{tag}' does not exist " + + $"on target '{deployment.Nitro.ApiId}'."); + if (!string.Equals( + download.Name, + sourceName, + StringComparison.Ordinal) + || !string.Equals( + download.Version, + tag, + StringComparison.Ordinal)) + { + throw new InvalidDataException( + $"Nitro returned Fusion source '{download.Name}@{download.Version}' " + + $"when '{sourceName}@{tag}' was requested."); + } + + return download; + } + + private void ClearOwnedBuffer(byte[] buffer) + { + Array.Clear(buffer); + _bufferCleared?.Invoke(buffer); + } + + private void AddSourceBytes( + int sourceBytes, + string sourceName, + string tag, + ref long totalSourceBytes) + { + if (sourceBytes > _memoryLimits.SourceArchiveBytes) + { + throw new InvalidDataException( + $"Downloaded Fusion source '{sourceName}@{tag}' exceeds " + + $"the {_memoryLimits.SourceArchiveBytes:N0}-byte " + + "per-source in-memory size limit."); + } + + totalSourceBytes = checked(totalSourceBytes + sourceBytes); + if (totalSourceBytes > _memoryLimits.TotalSourceArchiveBytes) + { + throw new InvalidDataException( + "The downloaded Fusion sources exceed the " + + $"{_memoryLimits.TotalSourceArchiveBytes:N0}-byte " + + "aggregate in-memory size limit."); + } + } + + private static async Task ValidateCanonicalDigestAsync( + FusionSourceSchemaDownload download, + byte[] archive, + string sourceName, + string tag, + CancellationToken cancellationToken) + { + var contentSha256 = + await FusionSourceSchemaContent.ComputeSha256Async( + archive, + sourceName, + cancellationToken); + if (!string.Equals( + contentSha256, + download.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Downloaded Fusion source '{sourceName}@{tag}' content " + + "does not match its canonical digest."); + } + + return contentSha256; + } + + private static async Task ValidatePreflightStateAsync( + FusionDeploymentSessionState state, FusionDeploymentResource deployment, PipelineStepContext context, CancellationToken cancellationToken) @@ -1039,20 +1195,15 @@ private static async Task ValidateApplyStateAsync( state.ApiId, deployment.Nitro.ApiId, StringComparison.Ordinal) - || state.Sources.Count != sourceNames.Count - || !state.Sources.Select(source => source.Name) + || state.SourceIdentities.Count != sourceNames.Count + || !state.SourceIdentities.Select(source => source.Name) .SequenceEqual(sourceNames, StringComparer.Ordinal)) { throw new InvalidDataException( $"Prepared Fusion state does not match deployment '{deployment.Name}'."); } - var applyDirectory = GetApplyDirectory( - context.Services - .GetRequiredService() - .GetOutputDirectory(), - deployment); - foreach (var source in state.Sources) + foreach (var source in state.SourceIdentities) { if (!string.Equals( source.Version, @@ -1062,13 +1213,51 @@ private static async Task ValidateApplyStateAsync( throw new InvalidDataException( $"Prepared Fusion source '{source.Name}' does not use tag '{tag}'."); } + } + } + + private static async Task ValidateSessionStateAsync( + FusionDeploymentSessionState state, + FusionDeploymentResource deployment, + PipelineStepContext context, + CancellationToken cancellationToken) + { + await ValidatePreflightStateAsync( + state, + deployment, + context, + cancellationToken); + var sources = state.Sources; + if (sources.Count != state.SourceIdentities.Count + || !sources.Select(source => source.Name) + .SequenceEqual( + state.SourceIdentities.Select(source => source.Name), + StringComparer.Ordinal)) + { + throw new InvalidDataException( + $"Materialized Fusion sources do not match deployment '{deployment.Name}'."); + } + + for (var i = 0; i < sources.Count; i++) + { + var source = sources[i]; + var identity = state.SourceIdentities[i]; + if (!string.Equals( + source.Version, + identity.Version, + StringComparison.Ordinal) + || !string.Equals( + source.ContentSha256, + identity.ContentSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"Materialized Fusion source '{source.Name}' does not match preflight."); + } - var archivePath = ResolveApplyPath( - applyDirectory, - source.ArchivePath); var contentSha256 = await FusionSourceSchemaContent.ComputeSha256Async( - archivePath, + source.Archive, source.Name, cancellationToken); if (!string.Equals( @@ -1082,69 +1271,6 @@ await FusionSourceSchemaContent.ComputeSha256Async( } } - private static async Task ReadApplyStateAsync( - string applyDirectory, - CancellationToken cancellationToken) - { - var path = Path.Combine( - applyDirectory, - "fusion-apply.json"); - if (!File.Exists(path)) - { - throw new FileNotFoundException( - "Fusion sources have not been downloaded.", - path); - } - - await using var stream = File.OpenRead(path); - var state = await JsonSerializer.DeserializeAsync( - stream, - s_jsonOptions, - cancellationToken) - ?? throw new InvalidDataException( - "The Fusion apply state is empty."); - if (string.IsNullOrWhiteSpace(state.Tag) - || string.IsNullOrWhiteSpace(state.CloudUrl) - || string.IsNullOrWhiteSpace(state.ApiId) - || state.Sources is null - || state.Sources.Any(source => - string.IsNullOrWhiteSpace(source.Name) - || string.IsNullOrWhiteSpace(source.Version) - || string.IsNullOrWhiteSpace(source.ArchivePath) - || string.IsNullOrWhiteSpace(source.ContentSha256))) - { - throw new InvalidDataException( - "The Fusion apply state is missing required content."); - } - - return state; - } - - private static string ResolveApplyPath( - string applyDirectory, - string relativePath) - { - if (Path.IsPathFullyQualified(relativePath)) - { - throw new InvalidDataException( - "A Fusion apply path must be relative."); - } - - var fullApplyDirectory = Path.GetFullPath(applyDirectory); - var path = Path.GetFullPath(relativePath, fullApplyDirectory); - var prefix = fullApplyDirectory.EndsWith( - Path.DirectorySeparatorChar) - ? fullApplyDirectory - : fullApplyDirectory + Path.DirectorySeparatorChar; - if (!path.StartsWith(prefix, StringComparison.Ordinal)) - { - throw new InvalidDataException( - "A Fusion apply path escapes the apply output directory."); - } - - return path; - } - internal static IReadOnlyList<(string Name, Uri Endpoint)> GetTransportEndpoints(JsonDocument gatewaySettings) { @@ -1299,6 +1425,50 @@ private static async Task ComputeFileDigestAsync( return Convert.ToHexStringLower(digest); } + private static string ComputeMemoryDigest(byte[] content) + => Convert.ToHexStringLower(SHA256.HashData(content)); + + internal void TransferComposition( + FusionDeploymentSessionState state, + string compositionEnvironment, + byte[] fusionArchive, + CancellationToken cancellationToken) + { + var transferred = false; + try + { + cancellationToken.ThrowIfCancellationRequested(); + state.SetComposition( + compositionEnvironment, + fusionArchive, + ComputeMemoryDigest(fusionArchive)); + transferred = true; + } + finally + { + if (!transferred) + { + ClearOwnedBuffer(fusionArchive); + } + } + } + + private static void VerifyMemoryDigest( + byte[] content, + string expectedSha256, + string description) + { + var actualSha256 = ComputeMemoryDigest(content); + if (!string.Equals( + actualSha256, + expectedSha256, + StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"The {description} SHA-256 does not match the in-memory session state."); + } + } + internal static async Task VerifyFileDigestAsync( string path, string expectedSha256, @@ -1314,7 +1484,7 @@ internal static async Task VerifyFileDigestAsync( StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException( - $"The {description} SHA-256 does not match prepared apply state."); + $"The {description} SHA-256 does not match prepared state."); } } @@ -1353,11 +1523,6 @@ private static string GetDeploymentDirectory( FusionDeploymentResource deployment) => Path.Combine(output, "fusion", deployment.Name); - private static string GetApplyDirectory( - string output, - FusionDeploymentResource deployment) - => Path.Combine(output, "fusion", "apply", deployment.Name); - private static void DeleteDirectoryBestEffort(string path) { try @@ -1432,20 +1597,5 @@ internal sealed record FusionSourceProvenance( bool LaunchProfile, string WorkingDirectory); -internal sealed record FusionApplyState( - string Tag, - string CloudUrl, - string ApiId, - string? CompositionEnvironment, - string? FusionArchivePath, - string? FusionArchiveSha256, - IReadOnlyList Sources); - -internal sealed record FusionApplySource( - string Name, - string Version, - string ArchivePath, - string ContentSha256); - #pragma warning restore ASPIREPIPELINES004 #pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs new file mode 100644 index 00000000000..16fb74e3a70 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs @@ -0,0 +1,394 @@ +namespace ChilliCream.Nitro.Aspire; + +internal sealed class FusionPipelineSession : IDisposable +{ + private readonly object _sync = new(); + private readonly Dictionary< + FusionDeploymentResource, + FusionDeploymentSessionState> _deployments = new( + ReferenceEqualityComparer.Instance); + private readonly FusionPipelineMemoryLimits _memoryLimits; + private readonly CancellationToken _cancellationToken; + private readonly CancellationTokenRegistration _cancellationRegistration; + private int _activeLeases; + private bool _clearRequested; + private bool _canceled; + private bool _disposed; + + public FusionPipelineSession( + CancellationToken cancellationToken = default, + FusionPipelineMemoryLimits? memoryLimits = null) + { + _memoryLimits = memoryLimits ?? FusionPipelineMemoryLimits.Default; + _cancellationToken = cancellationToken; + _cancellationRegistration = cancellationToken.UnsafeRegister( + static state => ((FusionPipelineSession)state!).Cancel(), + this); + } + + internal int DeploymentCount + { + get + { + lock (_sync) + { + return _deployments.Count; + } + } + } + + public void SetAll( + IReadOnlyList<( + FusionDeploymentResource Deployment, + FusionDeploymentSessionState State)> deployments) + { + ArgumentNullException.ThrowIfNull(deployments); + + var uniqueDeployments = new HashSet( + ReferenceEqualityComparer.Instance); + foreach (var (deployment, _) in deployments) + { + if (!uniqueDeployments.Add(deployment)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' was downloaded more than once."); + } + } + + var totalSourceBytes = deployments.Sum( + item => item.State.SourceArchiveBytes); + if (totalSourceBytes > _memoryLimits.TotalSourceArchiveBytes) + { + throw new InvalidDataException( + "The downloaded Fusion sources exceed the " + + $"{_memoryLimits.TotalSourceArchiveBytes:N0}-byte " + + "aggregate in-memory size limit."); + } + + lock (_sync) + { + ObjectDisposedException.ThrowIf(_disposed, this); + if (_canceled) + { + DisposeStates(deployments.Select(item => item.State)); + throw new OperationCanceledException(_cancellationToken); + } + + if (_clearRequested || _activeLeases > 0) + { + throw new InvalidOperationException( + "Fusion pipeline state cannot be replaced while it is in use."); + } + + ClearCore(); + foreach (var (deployment, state) in deployments) + { + _deployments.Add(deployment, state); + } + } + } + + public FusionPipelineSessionLease Acquire( + FusionDeploymentResource deployment) + { + ArgumentNullException.ThrowIfNull(deployment); + + lock (_sync) + { + ObjectDisposedException.ThrowIf(_disposed, this); + if (_canceled) + { + throw new OperationCanceledException(_cancellationToken); + } + + if (_clearRequested) + { + throw new InvalidOperationException( + "Fusion pipeline state is being cleared."); + } + + if (!_deployments.TryGetValue(deployment, out var state)) + { + throw new InvalidOperationException( + $"Fusion sources for deployment '{deployment.Name}' have not been downloaded."); + } + + _activeLeases++; + return new FusionPipelineSessionLease(this, state); + } + } + + public void Clear() + { + lock (_sync) + { + RequestClear(); + } + } + + private void Cancel() + { + lock (_sync) + { + _canceled = true; + RequestClear(); + } + } + + private void RequestClear() + { + _clearRequested = true; + if (_activeLeases == 0) + { + ClearCore(); + if (!_disposed && !_canceled) + { + _clearRequested = false; + } + } + } + + private void Release() + { + lock (_sync) + { + _activeLeases--; + if (_activeLeases == 0 && _clearRequested) + { + ClearCore(); + if (!_disposed && !_canceled) + { + _clearRequested = false; + } + } + } + } + + private void ClearCore() + { + DisposeStates(_deployments.Values); + _deployments.Clear(); + } + + private static void DisposeStates( + IEnumerable states) + { + foreach (var state in states) + { + state.Dispose(); + } + } + + public void Dispose() + { + lock (_sync) + { + if (_disposed) + { + return; + } + + _disposed = true; + RequestClear(); + } + + _cancellationRegistration.Dispose(); + } + + internal sealed class FusionPipelineSessionLease( + FusionPipelineSession session, + FusionDeploymentSessionState state) + : IDisposable + { + private FusionPipelineSession? _session = session; + + public FusionDeploymentSessionState State { get; } = state; + + public void Dispose() + { + var current = Interlocked.Exchange(ref _session, null); + current?.Release(); + } + } +} + +internal sealed class FusionDeploymentSessionState( + string tag, + string cloudUrl, + string apiId, + IReadOnlyList sourceIdentities) + : IDisposable +{ + private readonly object _sync = new(); + private IReadOnlyList? _sources; + private byte[]? _fusionArchive; + private string? _compositionEnvironment; + private string? _fusionArchiveSha256; + private bool _disposed; + + public string Tag { get; } = tag; + + public string CloudUrl { get; } = cloudUrl; + + public string ApiId { get; } = apiId; + + public IReadOnlyList SourceIdentities { get; } = + sourceIdentities; + + public IReadOnlyList Sources + { + get + { + lock (_sync) + { + ObjectDisposedException.ThrowIf(_disposed, this); + return _sources + ?? throw new InvalidOperationException( + "Fusion source archives have not been materialized."); + } + } + } + + public long SourceArchiveBytes + { + get + { + lock (_sync) + { + return _sources?.Sum( + source => (long)source.Archive.Length) ?? 0; + } + } + } + + public string? CompositionEnvironment + { + get + { + lock (_sync) + { + return _compositionEnvironment; + } + } + } + + public byte[] FusionArchive + { + get + { + lock (_sync) + { + ObjectDisposedException.ThrowIf(_disposed, this); + return _fusionArchive + ?? throw new InvalidOperationException( + "The downloaded Fusion sources have not been composed."); + } + } + } + + public string? FusionArchiveSha256 + { + get + { + lock (_sync) + { + return _fusionArchiveSha256; + } + } + } + + public void SetSources(IReadOnlyList sources) + { + ArgumentNullException.ThrowIfNull(sources); + + lock (_sync) + { + if (_disposed || _sources is not null) + { + foreach (var source in sources) + { + Array.Clear(source.Archive); + } + + ObjectDisposedException.ThrowIf(_disposed, this); + throw new InvalidOperationException( + "Fusion source archives have already been materialized."); + } + + _sources = sources; + } + } + + public void SetComposition( + string compositionEnvironment, + byte[] fusionArchive, + string fusionArchiveSha256) + { + ArgumentException.ThrowIfNullOrWhiteSpace(compositionEnvironment); + ArgumentNullException.ThrowIfNull(fusionArchive); + ArgumentException.ThrowIfNullOrWhiteSpace(fusionArchiveSha256); + + lock (_sync) + { + if (_disposed) + { + Array.Clear(fusionArchive); + throw new ObjectDisposedException( + nameof(FusionDeploymentSessionState)); + } + + if (_fusionArchive is not null) + { + Array.Clear(fusionArchive); + throw new InvalidOperationException( + "The Fusion archive has already been composed."); + } + + _compositionEnvironment = compositionEnvironment; + _fusionArchive = fusionArchive; + _fusionArchiveSha256 = fusionArchiveSha256; + } + } + + public void Dispose() + { + lock (_sync) + { + if (_disposed) + { + return; + } + + _disposed = true; + if (_sources is not null) + { + foreach (var source in _sources) + { + Array.Clear(source.Archive); + } + + _sources = null; + } + + if (_fusionArchive is not null) + { + Array.Clear(_fusionArchive); + _fusionArchive = null; + } + + _compositionEnvironment = null; + _fusionArchiveSha256 = null; + } + } +} + +internal sealed record FusionSessionSourceIdentity( + string Name, + string Version, + string ContentSha256); + +internal sealed record FusionSessionSource( + string Name, + string Version, + byte[] Archive, + string ContentSha256); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs index 9197e653100..fb0a8ba1829 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs @@ -8,15 +8,27 @@ internal interface IFusionPipelineExecutor { Task CreateArtifactsAsync(PipelineStepContext context); - Task VerifyReadinessAsync(PipelineStepContext context); + Task VerifyReadinessAsync( + PipelineStepContext context, + FusionPipelineSession session); Task UploadAsync(PipelineStepContext context); - Task DownloadAsync(PipelineStepContext context); + Task PreflightAsync( + PipelineStepContext context, + FusionPipelineSession session); - Task ComposeAsync(PipelineStepContext context); + Task DownloadAsync( + PipelineStepContext context, + FusionPipelineSession session); - Task PublishAsync(PipelineStepContext context); + Task ComposeAsync( + PipelineStepContext context, + FusionPipelineSession session); + + Task PublishAsync( + PipelineStepContext context, + FusionPipelineSession session); } #pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md index 82aef3ef76d..387669c70a7 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md @@ -31,7 +31,13 @@ exports the AppHost-declared source set and reconciles every source as the immut `name@tag` on the selected Nitro target. Run `aspire do fusion-publish --environment Production` on the deployment runner. It infers the -same complete source set from the AppHost, downloads each exact `name@tag` from Nitro, composes with -the selected environment settings, deploys and checks the source services, publishes the Nitro -stage, and then deploys the gateway. It does not read source schemas, use Git, or upload source -versions. No manifest or CI artifact is passed between upload and publish. +same complete source set from the AppHost, preflight-downloads each exact `name@tag`, deploys the +source services, downloads and digest-matches the exact versions again, composes with the selected +environment settings, checks readiness, publishes the Nitro stage, and then deploys the gateway. +The preflight retains only identities and digests while the providers run. It does not read source +schemas, use Git, or upload source versions. No manifest or CI artifact is passed between upload and +publish. Exact source archives +and the composed FAR stay in a bounded, invocation-scoped memory session that is cleared after +success, failure, or cancellation. The Fusion-specific publish steps create no Fusion apply-state +files and do not resolve Aspire's output-path service. Hosting-provider dependencies may still +write target artifacts or Aspire deployment state according to that provider's configuration. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md index fe97727ff04..b331e4d4dc0 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md @@ -160,20 +160,24 @@ The command performs these release-critical phases: 1. select the current environment declaration and resolve `tag`; 2. infer and sort the complete effective source-name set; -3. download each exact `name@tag` from the selected Nitro API; -4. atomically record apply state that binds tag, target, complete source set, archive paths, and - canonical content digests; -5. compose a FAR from only those downloaded archives using current AppHost composition settings +3. preflight-download each exact `name@tag`, record its canonical digest, and clear the archive + buffers before source compute starts; +4. deploy source compute; +5. download the exact versions again and require their canonical digests to match preflight; +6. compose a FAR from only those second-download archives using current AppHost composition settings and the selected composition environment; -6. revalidate source digests, composition environment, and FAR digest; -7. deploy source compute and poll the composed production endpoints until ready; +7. revalidate source digests, composition environment, and FAR digest, then poll the composed + production endpoints until ready; 8. publish the FAR and exact source references to the selected Nitro stage; 9. wait for approval when configured and verify the terminal Nitro result; 10. deploy gateway compute; and 11. complete the public terminal step. A missing exact source fails during download before source compute changes. Publish never calls the -source reconciliation API, runs schema export, reads a source checkout, or invokes Git. +source reconciliation API, runs schema export, reads a source checkout, or invokes Git. The +Fusion-specific download, composition, readiness, and publication steps create no Fusion +apply-state directory and do not resolve Aspire's output-path service. Provider-contributed +deployment dependencies may still write target artifacts or Aspire deployment state. ## Pipeline graph and first release @@ -186,8 +190,7 @@ fusion-artifacts -> fusion-upload Deployment uses this graph: ```text -fusion-download -> source DeployCompute -> fusion-readiness - \-> fusion-compose ------/ +fusion-download -> source DeployCompute -> fusion-compose -> fusion-readiness fusion-readiness -> fusion-publish-stage @@ -196,21 +199,27 @@ fusion-readiness ``` Every source deploy-compute step depends on `fusion-download`, so the exact Nitro source set is a -fail-before-compute preflight. Readiness depends on both composition and all source deploy-compute -steps. The internal `fusion-publish-stage` step runs only after readiness. Gateway deployment -depends on stage publication, which is required for a first release because no gateway can start -from Nitro before the first FAR exists. The public `fusion-publish` terminal depends on gateway -deployment and is required by the broader Deploy root. - -The ordering is intentionally source deploy, readiness, internal Nitro publication, gateway -deploy, terminal public publication. Upload is never in the transitive dependency set of the -public publish command. - -## Apply-state integrity - -Download writes into a temporary sibling directory and atomically replaces the deployment apply -directory only after all exact sources succeed. State paths must be relative and remain beneath the -apply directory. +fail-before-compute preflight. The preflight retains only identities and canonical digests, not +archive bytes. Composition runs only after every source deploy-compute step, re-downloads the exact +versions, and rejects any digest change from preflight. The internal `fusion-publish-stage` step +runs only after readiness. Gateway deployment depends on stage publication, which is required for a +first release because no gateway can start from Nitro before the first FAR exists. The public +`fusion-publish` terminal depends on gateway deployment and is required by the broader Deploy root. + +The ordering is intentionally preflight, source deploy, exact re-download and composition, +readiness, internal Nitro publication, gateway deploy, terminal public publication. Upload is never +in the transitive dependency set of the public publish command. + +## Invocation-memory integrity + +Each `fusion-publish` invocation owns a private in-memory session shared only by its pipeline step +closures. Preflight retains only small source identities and canonical digests while provider steps +run. Exact archive bytes are downloaded again after source compute, compared with preflight, and +leased while composition, readiness, or publication reads them. Cancellation requests cleanup but +does not zero an actively leased buffer until its reader unwinds. The session retains no credentials +and writes no source archive, state file, apply directory, or composed FAR to disk. +Source archives are limited to 128,000,000 bytes each and 512,000,000 bytes in aggregate. The +composed FAR is limited to 256,000,000 bytes. Compose, readiness, and publish validate: @@ -218,12 +227,12 @@ Compose, readiness, and publish validate: - normalized cloud URL and API ID equal the selected target; - recorded source names exactly equal the current sorted AppHost set; - every source version equals the tag; -- every archive still has its recorded canonical content digest; +- every in-memory archive still has its recorded canonical content digest; - the composition environment still matches the current declaration; and - the FAR still has its recorded raw digest. -This state is local execution integrity, not a promotion artifact. A new deployment runner -recreates it by downloading exact source versions from Nitro. +All owned source and FAR buffers are cleared after success, failure, or cancellation. A retry starts +a new isolated session and downloads the exact source versions from Nitro again. ## Readiness, approval, and retries @@ -308,6 +317,10 @@ The implementation is complete only when focused tests and a real materialized A | Environment selection | Only the matching declaration is used; ambiguous mappings fail. | | Complete source set | Duplicate effective names and missing exact downloads fail. | | Cross-runner publish | Publish succeeds with AppHost metadata and Nitro downloads but no schema files, Git metadata, or upload artifact. | +| Fusion-only disk behavior | Fusion download, composition, readiness, and publication succeed without resolving `IPipelineOutputService` or writing source archives, apply state, or a FAR. Provider dependencies own their target output and deployment state. | +| Invocation isolation | Interleaved environments and repeated deployments use separate sessions with no retained state. | +| Cleanup | Success, failure, and cancellation clear all owned source and FAR buffers. | +| Memory bounds | Oversized individual sources, aggregate sources, and FAR output fail with explicit diagnostics. | | Environment composition | The same `name@tag` archives compose different Development/Test endpoints. | | Integrity | Tag, target, source-set, archive, environment, and FAR drift fail. | | Provider ordering | Source deploy waits for download; readiness waits for source compute; gateway waits for Nitro publication. | diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs index a92c499d576..1d30d75f281 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs @@ -28,18 +28,30 @@ internal sealed class FusionDeploymentWorkflow( return null; } - await using var stream = new MemoryStream(archive, writable: false); - var content = await FusionArchiveContent.ReadAsync( - stream, - source.Name, - cancellationToken); - var contentSha256 = content.ComputeSha256(cancellationToken); - - return new FusionSourceSchemaDownload( - source.Name, - source.Version, - archive, - contentSha256); + var ownershipTransferred = false; + try + { + await using var stream = new MemoryStream(archive, writable: false); + var content = await FusionArchiveContent.ReadAsync( + stream, + source.Name, + cancellationToken); + var contentSha256 = content.ComputeSha256(cancellationToken); + var download = new FusionSourceSchemaDownload( + source.Name, + source.Version, + archive, + contentSha256); + ownershipTransferred = true; + return download; + } + finally + { + if (!ownershipTransferred) + { + Array.Clear(archive); + } + } } public async Task ReconcileSourceSchemaAsync( @@ -67,7 +79,7 @@ public async Task ReconcileSourceSchemaAsync( if (remoteArchive is not null) { - await EnsureContentMatchesAsync( + await EnsureContentMatchesAndClearAsync( source, localContentSha256, remoteArchive, @@ -118,7 +130,7 @@ await ReconcileAfterUncertainUploadAsync( + "could not be read back."); } - await EnsureContentMatchesAsync( + await EnsureContentMatchesAndClearAsync( source, localContentSha256, racedArchive, @@ -133,10 +145,10 @@ await EnsureContentMatchesAsync( public async Task PublishAsync( FusionPublicationRequest request, - string fusionArchivePath, + ReadOnlyMemory fusionArchive, CancellationToken cancellationToken) { - ValidatePublication(request, fusionArchivePath); + ValidatePublication(request, fusionArchive); using var timeout = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken); @@ -205,7 +217,7 @@ await EnsureRemoteCommandSucceededAsync( await EnsureRemoteCommandSucceededAsync( () => transport.ValidatePublishAsync( requestId, - fusionArchivePath, + fusionArchive, operationToken), "validate the Fusion configuration", requestId); @@ -229,7 +241,7 @@ await ReleaseKnownFailedValidationAsync( await EnsureRemoteCommandSucceededAsync( () => transport.CommitPublishAsync( requestId, - fusionArchivePath, + fusionArchive, operationToken), "commit the Fusion configuration", requestId); @@ -284,13 +296,33 @@ private static async Task ReconcileAfterUncertainUploadAsync( uploadException); } - await EnsureContentMatchesAsync( + await EnsureContentMatchesAndClearAsync( source, localContentSha256, remoteArchive, cancellationToken); } + private static async Task EnsureContentMatchesAndClearAsync( + FusionSourceSchemaUpload source, + string localContentSha256, + byte[] remoteArchive, + CancellationToken cancellationToken) + { + try + { + await EnsureContentMatchesAsync( + source, + localContentSha256, + remoteArchive, + cancellationToken); + } + finally + { + Array.Clear(remoteArchive); + } + } + private static async Task EnsureContentMatchesAsync( FusionSourceSchemaUpload source, string localContentSha256, @@ -572,13 +604,12 @@ private static void ValidateSha256(string sha256, string description) private static void ValidatePublication( FusionPublicationRequest request, - string fusionArchivePath) + ReadOnlyMemory fusionArchive) { ArgumentNullException.ThrowIfNull(request); ValidateTarget(request.Target); ArgumentException.ThrowIfNullOrWhiteSpace(request.Stage); ArgumentException.ThrowIfNullOrWhiteSpace(request.ConfigurationTag); - ArgumentException.ThrowIfNullOrWhiteSpace(fusionArchivePath); ArgumentNullException.ThrowIfNull(request.SourceSchemas); if (request.SourceSchemas.Count is 0) @@ -618,14 +649,7 @@ private static void ValidatePublication( "The approval timeout must be greater than zero."); } - if (!File.Exists(fusionArchivePath)) - { - throw new FileNotFoundException( - "The Fusion configuration archive does not exist.", - fusionArchivePath); - } - - if (new FileInfo(fusionArchivePath).Length is 0) + if (fusionArchive.IsEmpty) { throw new FusionDeploymentException( "The Fusion configuration archive is empty."); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs index 380deda6e3d..16f086ba048 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs @@ -29,4 +29,32 @@ public static async Task ComputeSha256Async( cancellationToken); return content.ComputeSha256(cancellationToken); } + + /// + /// Computes a SHA-256 over the normalized schema, settings, and extensions in memory. + /// + public static async Task ComputeSha256Async( + byte[] archive, + string expectedName, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(archive); + ArgumentException.ThrowIfNullOrWhiteSpace(expectedName); + + if (archive.Length is 0) + { + throw new ArgumentException( + "The Fusion source schema archive is empty.", + nameof(archive)); + } + + await using var stream = new MemoryStream( + archive, + writable: false); + var content = await FusionArchiveContent.ReadAsync( + stream, + expectedName, + cancellationToken); + return content.ComputeSha256(cancellationToken); + } } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs index 2d5b14abe72..94e94316498 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs @@ -1,11 +1,80 @@ namespace ChilliCream.Nitro.Fusion; /// -/// An exact immutable source schema archive downloaded from Nitro together with its canonical -/// content identity. +/// Owns an exact immutable source schema archive downloaded from Nitro together with its +/// canonical content identity. /// -public sealed record FusionSourceSchemaDownload( - string Name, - string Version, - ReadOnlyMemory Archive, - string ContentSha256); +/// +/// The caller must dispose this instance after consuming . Disposal clears +/// the owned archive buffer. +/// +public sealed class FusionSourceSchemaDownload : IDisposable +{ + private byte[]? _archive; + + /// + /// Initializes a new owned source schema download. + /// + /// + /// Ownership of transfers to this instance. + /// + public FusionSourceSchemaDownload( + string name, + string version, + byte[] archive, + string contentSha256) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentException.ThrowIfNullOrWhiteSpace(version); + ArgumentNullException.ThrowIfNull(archive); + ArgumentException.ThrowIfNullOrWhiteSpace(contentSha256); + + Name = name; + Version = version; + _archive = archive; + ContentSha256 = contentSha256; + } + + /// + /// Gets the source schema name. + /// + public string Name { get; } + + /// + /// Gets the exact source schema version. + /// + public string Version { get; } + + /// + /// Gets read-only access to the owned archive buffer. + /// + /// + /// The download has already been disposed. + /// + public ReadOnlyMemory Archive + { + get + { + var archive = Volatile.Read(ref _archive); + ObjectDisposedException.ThrowIf(archive is null, this); + return archive; + } + } + + /// + /// Gets the canonical source content digest. + /// + public string ContentSha256 { get; } + + /// + /// Clears and releases the owned archive buffer. + /// + public void Dispose() + { + var archive = Interlocked.Exchange(ref _archive, null); + if (archive is not null) + { + Array.Clear(archive); + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs index 1c881506ba9..2817e38a53d 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs @@ -6,16 +6,16 @@ namespace ChilliCream.Nitro.Fusion.Transport { // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaResult : global::System.IEquatable, IUploadFusionSourceSchemaResult + internal partial class ClaimFusionDeploymentResult : global::System.IEquatable, IClaimFusionDeploymentResult { - public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph uploadFusionSubgraph) + public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition startFusionConfigurationComposition) { - UploadFusionSubgraph = uploadFusionSubgraph; + StartFusionConfigurationComposition = startFusionConfigurationComposition; } - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchemaResult? other) + public virtual global::System.Boolean Equals(ClaimFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -32,7 +32,7 @@ public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport return false; } - return (UploadFusionSubgraph.Equals(other.UploadFusionSubgraph)); + return (StartFusionConfigurationComposition.Equals(other.StartFusionConfigurationComposition)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -52,7 +52,7 @@ public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport return false; } - return Equals((UploadFusionSourceSchemaResult)obj); + return Equals((ClaimFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -60,7 +60,7 @@ public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport unchecked { int hash = 5; - hash ^= 397 * UploadFusionSubgraph.GetHashCode(); + hash ^= 397 * StartFusionConfigurationComposition.GetHashCode(); return hash; } } @@ -68,18 +68,16 @@ public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload { - public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? fusionSubgraphVersion, global::System.Collections.Generic.IReadOnlyList? errors) + public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) { - FusionSubgraphVersion = fusionSubgraphVersion; Errors = errors; } - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload? other) + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload? other) { if (ReferenceEquals(null, other)) { @@ -96,7 +94,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload return false; } - return (((FusionSubgraphVersion is null && other.FusionSubgraphVersion is null) || FusionSubgraphVersion != null && FusionSubgraphVersion.Equals(other.FusionSubgraphVersion))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -116,7 +114,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload)obj); + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload)obj); } public override global::System.Int32 GetHashCode() @@ -124,11 +122,6 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload unchecked { int hash = 5; - if (FusionSubgraphVersion != null) - { - hash ^= 397 * FusionSubgraphVersion.GetHashCode(); - } - if (Errors != null) { foreach (var Errors_elm in Errors) @@ -144,16 +137,21 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation { - public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(global::System.String id) + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { - Id = id; + this.__typename = __typename; + Message = message; } - public global::System.String Id { get; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion? other) + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -170,7 +168,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_Fusio return false; } - return (Id.Equals(other.Id)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -190,7 +188,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_Fusio return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion)obj); + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -198,7 +196,8 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_Fusio unchecked { int hash = 5; - hash ^= 397 * Id.GetHashCode(); + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -206,9 +205,9 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_Fusio // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(global::System.String __typename, global::System.String message) + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -220,7 +219,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceS public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError? other) + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -257,7 +256,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceS return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError)obj); + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -274,9 +273,9 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceS // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError + internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) + public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -288,7 +287,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(glo public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError? other) + public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) { if (ReferenceEquals(null, other)) { @@ -325,7 +324,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(glo return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError)obj); + return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); } public override global::System.Int32 GetHashCode() @@ -340,23 +339,70 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(glo } } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : IClaimFusionDeployment_StartFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IFusionError + { + public global::System.String Message { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError + { + } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError + internal partial class WatchFusionDeploymentResult : global::System.IEquatable, IWatchFusionDeploymentResult { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(global::System.String __typename, global::System.String message) + public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged onFusionConfigurationPublishingTaskChanged) { - this.__typename = __typename; - Message = message; + OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError? other) + public virtual global::System.Boolean Equals(WatchFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -373,7 +419,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationE return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (OnFusionConfigurationPublishingTaskChanged.Equals(other.OnFusionConfigurationPublishingTaskChanged)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -393,7 +439,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationE return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError)obj); + return Equals((WatchFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -401,8 +447,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationE unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * OnFusionConfigurationPublishingTaskChanged.GetHashCode(); return hash; } } @@ -410,21 +455,23 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationE // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) { this.__typename = __typename; - Message = message; + State = state; + Errors = errors; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Collections.Generic.IReadOnlyList Errors { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed? other) { if (ReferenceEquals(null, other)) { @@ -441,7 +488,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperatio return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -461,7 +508,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperatio return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed)obj); } public override global::System.Int32 GetHashCode() @@ -470,7 +517,12 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperatio { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + return hash; } } @@ -478,21 +530,21 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperatio // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { this.__typename = __typename; - Message = message; + State = state; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess? other) { if (ReferenceEquals(null, other)) { @@ -509,7 +561,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(g return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -529,7 +581,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(g return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess)obj); } public override global::System.Int32 GetHashCode() @@ -538,7 +590,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(g { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -546,21 +598,23 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(g // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) { this.__typename = __typename; - Message = message; + State = state; + Errors = errors; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Collections.Generic.IReadOnlyList Errors { get; } - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed? other) { if (ReferenceEquals(null, other)) { @@ -577,7 +631,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadat return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -597,7 +651,7 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadat return false; } - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed)obj); } public override global::System.Int32 GetHashCode() @@ -606,108 +660,102 @@ public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadat { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + return hash; } } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchemaResult + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess { - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } - } + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + { + this.__typename = __typename; + State = state; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph - { - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : IUploadFusionSourceSchema_UploadFusionSubgraph - { - } + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion - { - public global::System.String Id { get; } - } + if (ReferenceEquals(this, other)) + { + return true; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IFusionError - { - public global::System.String Message { get; } - } + if (other.GetType() != GetType()) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } + return (__typename.Equals(other.__typename)) && State.Equals(other.State); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } + if (ReferenceEquals(this, obj)) + { + return true; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } + if (obj.GetType() != GetType()) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess)obj); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + return hash; + } + } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentResult : global::System.IEquatable, IBeginFusionDeploymentResult + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress { - public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish beginFusionConfigurationPublish) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { - BeginFusionConfigurationPublish = beginFusionConfigurationPublish; + this.__typename = __typename; + State = state; } - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(BeginFusionDeploymentResult? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress? other) { if (ReferenceEquals(null, other)) { @@ -724,7 +772,7 @@ public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IB return false; } - return (BeginFusionConfigurationPublish.Equals(other.BeginFusionConfigurationPublish)); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -744,7 +792,7 @@ public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IB return false; } - return Equals((BeginFusionDeploymentResult)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress)obj); } public override global::System.Int32 GetHashCode() @@ -752,7 +800,8 @@ public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IB unchecked { int hash = 5; - hash ^= 397 * BeginFusionConfigurationPublish.GetHashCode(); + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -760,18 +809,21 @@ public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IB // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved { - public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(global::System.String? requestId, global::System.Collections.Generic.IReadOnlyList? errors) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { - RequestId = requestId; - Errors = errors; + this.__typename = __typename; + State = state; } - public global::System.String? RequestId { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved? other) { if (ReferenceEquals(null, other)) { @@ -788,7 +840,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigur return false; } - return (((RequestId is null && other.RequestId is null) || RequestId != null && RequestId.Equals(other.RequestId))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -808,7 +860,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigur return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved)obj); } public override global::System.Int32 GetHashCode() @@ -816,19 +868,8 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigur unchecked { int hash = 5; - if (RequestId != null) - { - hash ^= 397 * RequestId.GetHashCode(); - } - - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -836,21 +877,23 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigur // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Int32 queuePosition) { this.__typename = __typename; - Message = message; + State = state; + QueuePosition = queuePosition; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Int32 QueuePosition { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued? other) { if (ReferenceEquals(null, other)) { @@ -867,7 +910,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_Unauthorized return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::System.Object.Equals(QueuePosition, other.QueuePosition); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -887,7 +930,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_Unauthorized return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued)obj); } public override global::System.Int32 GetHashCode() @@ -896,7 +939,8 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_Unauthorized { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); + hash ^= 397 * QueuePosition.GetHashCode(); return hash; } } @@ -904,21 +948,21 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_Unauthorized // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { this.__typename = __typename; - Message = message; + State = state; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady? other) { if (ReferenceEquals(null, other)) { @@ -935,7 +979,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundE return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -955,7 +999,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundE return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady)obj); } public override global::System.Int32 GetHashCode() @@ -964,7 +1008,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundE { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -972,21 +1016,21 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundE // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { this.__typename = __typename; - Message = message; + State = state; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress? other) { if (ReferenceEquals(null, other)) { @@ -1003,7 +1047,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoun return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1023,7 +1067,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoun return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress)obj); } public override global::System.Int32 GetHashCode() @@ -1032,7 +1076,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoun { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -1040,21 +1084,21 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoun // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) { this.__typename = __typename; - Message = message; + State = state; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval? other) { if (ReferenceEquals(null, other)) { @@ -1071,7 +1115,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInva return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (__typename.Equals(other.__typename)) && State.Equals(other.State); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1091,7 +1135,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInva return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval)obj); } public override global::System.Int32 GetHashCode() @@ -1100,7 +1144,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInva { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * State.GetHashCode(); return hash; } } @@ -1108,21 +1152,16 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInva // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError? other) { if (ReferenceEquals(null, other)) { @@ -1139,7 +1178,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProce return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1159,7 +1198,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProce return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError)obj); } public override global::System.Int32 GetHashCode() @@ -1167,7 +1206,6 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProce unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -1176,21 +1214,16 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProce // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError? other) { if (ReferenceEquals(null, other)) { @@ -1207,7 +1240,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourc return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1227,7 +1260,7 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourc return false; } - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError)obj); } public override global::System.Int32 GetHashCode() @@ -1235,89 +1268,24 @@ public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourc unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeploymentResult + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError { - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } - } + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(global::System.String message) + { + Message = message; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish - { - public global::System.String? RequestId { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } + public global::System.String Message { get; } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : IBeginFusionDeployment_BeginFusionConfigurationPublish - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResult : global::System.IEquatable, IClaimFusionDeploymentResult - { - public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition startFusionConfigurationComposition) - { - StartFusionConfigurationComposition = startFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeploymentResult? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError? other) { if (ReferenceEquals(null, other)) { @@ -1334,7 +1302,7 @@ public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IC return false; } - return (StartFusionConfigurationComposition.Equals(other.StartFusionConfigurationComposition)); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1354,7 +1322,7 @@ public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IC return false; } - return Equals((ClaimFusionDeploymentResult)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError)obj); } public override global::System.Int32 GetHashCode() @@ -1362,7 +1330,7 @@ public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IC unchecked { int hash = 5; - hash ^= 397 * StartFusionConfigurationComposition.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -1370,16 +1338,16 @@ public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IC // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError { - public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(global::System.String message) { - Errors = errors; + Message = message; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError? other) { if (ReferenceEquals(null, other)) { @@ -1396,7 +1364,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConf return false; } - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1416,7 +1384,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConf return false; } - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError)obj); } public override global::System.Int32 GetHashCode() @@ -1424,14 +1392,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConf unchecked { int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -1439,21 +1400,16 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConf // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError? other) { if (ReferenceEquals(null, other)) { @@ -1470,7 +1426,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_Unauthor return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1490,7 +1446,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_Unauthor return false; } - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError)obj); } public override global::System.Int32 GetHashCode() @@ -1498,7 +1454,6 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_Unauthor unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -1507,21 +1462,16 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_Unauthor // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1? other) { if (ReferenceEquals(null, other)) { @@ -1538,7 +1488,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionCo return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1558,7 +1508,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionCo return false; } - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1)obj); } public override global::System.Int32 GetHashCode() @@ -1566,7 +1516,6 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionCo unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -1575,21 +1524,16 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionCo // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError? other) { if (ReferenceEquals(null, other)) { @@ -1606,7 +1550,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidP return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1626,7 +1570,7 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidP return false; } - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError)obj); } public override global::System.Int32 GetHashCode() @@ -1634,70 +1578,24 @@ public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidP unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : IClaimFusionDeployment_StartFusionConfigurationComposition - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentResult : global::System.IEquatable, IReleaseFusionDeploymentResult + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError { - public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition cancelFusionConfigurationComposition) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(global::System.String message) { - CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; + Message = message; } - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ReleaseFusionDeploymentResult? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError? other) { if (ReferenceEquals(null, other)) { @@ -1714,7 +1612,7 @@ public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport. return false; } - return (CancelFusionConfigurationComposition.Equals(other.CancelFusionConfigurationComposition)); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1734,7 +1632,7 @@ public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport. return false; } - return Equals((ReleaseFusionDeploymentResult)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError)obj); } public override global::System.Int32 GetHashCode() @@ -1742,7 +1640,7 @@ public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport. unchecked { int hash = 5; - hash ^= 397 * CancelFusionConfigurationComposition.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -1750,16 +1648,16 @@ public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport. // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(global::System.String message) { - Errors = errors; + Message = message; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError? other) { if (ReferenceEquals(null, other)) { @@ -1776,7 +1674,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusion return false; } - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1796,7 +1694,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusion return false; } - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError)obj); } public override global::System.Int32 GetHashCode() @@ -1804,14 +1702,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusion unchecked { int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -1819,21 +1710,16 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusion // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError? other) { if (ReferenceEquals(null, other)) { @@ -1850,7 +1736,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Unaut return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1870,7 +1756,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Unaut return false; } - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError)obj); } public override global::System.Int32 GetHashCode() @@ -1878,7 +1764,6 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Unaut unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -1887,21 +1772,16 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Unaut // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(global::System.String message) { - this.__typename = __typename; Message = message; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1? other) { if (ReferenceEquals(null, other)) { @@ -1918,7 +1798,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Fusio return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Message.Equals(other.Message)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -1938,7 +1818,7 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Fusio return false; } - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1)obj); } public override global::System.Int32 GetHashCode() @@ -1946,138 +1826,182 @@ public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_Fusio unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + internal partial interface IWatchFusionDeploymentResult { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } + } - /// - /// The name of the current Object type at runtime. - /// + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + } - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + } - if (ReferenceEquals(this, other)) - { - return true; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } - if (other.GetType() != GetType()) - { - return false; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Collections.Generic.IReadOnlyList Errors { get; } + } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } - if (ReferenceEquals(this, obj)) - { - return true; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } - if (obj.GetType() != GetType()) - { - return false; - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + public global::System.Int32 QueuePosition { get; } + } - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { + } - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeploymentResult + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged { - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::System.String Message { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : IReleaseFusionDeployment_CancelFusionConfigurationComposition + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors { - public global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + public global::System.String Message { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentResult : global::System.IEquatable, IValidateFusionDeploymentResult + internal partial class UploadFusionSourceSchemaResult : global::System.IEquatable, IUploadFusionSourceSchemaResult { - public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition validateFusionConfigurationComposition) + public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph uploadFusionSubgraph) { - ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; + UploadFusionSubgraph = uploadFusionSubgraph; } - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } - public virtual global::System.Boolean Equals(ValidateFusionDeploymentResult? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchemaResult? other) { if (ReferenceEquals(null, other)) { @@ -2094,7 +2018,7 @@ public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport return false; } - return (ValidateFusionConfigurationComposition.Equals(other.ValidateFusionConfigurationComposition)); + return (UploadFusionSubgraph.Equals(other.UploadFusionSubgraph)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2114,7 +2038,7 @@ public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport return false; } - return Equals((ValidateFusionDeploymentResult)obj); + return Equals((UploadFusionSourceSchemaResult)obj); } public override global::System.Int32 GetHashCode() @@ -2122,7 +2046,7 @@ public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport unchecked { int hash = 5; - hash ^= 397 * ValidateFusionConfigurationComposition.GetHashCode(); + hash ^= 397 * UploadFusionSubgraph.GetHashCode(); return hash; } } @@ -2130,16 +2054,18 @@ public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) + public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? fusionSubgraphVersion, global::System.Collections.Generic.IReadOnlyList? errors) { + FusionSubgraphVersion = fusionSubgraphVersion; Errors = errors; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload? other) { if (ReferenceEquals(null, other)) { @@ -2156,7 +2082,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateF return false; } - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + return (((FusionSubgraphVersion is null && other.FusionSubgraphVersion is null) || FusionSubgraphVersion != null && FusionSubgraphVersion.Equals(other.FusionSubgraphVersion))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2176,7 +2102,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateF return false; } - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload)obj); } public override global::System.Int32 GetHashCode() @@ -2184,6 +2110,11 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateF unchecked { int hash = 5; + if (FusionSubgraphVersion != null) + { + hash ^= 397 * FusionSubgraphVersion.GetHashCode(); + } + if (Errors != null) { foreach (var Errors_elm in Errors) @@ -2199,21 +2130,16 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateF // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(global::System.String id) { - this.__typename = __typename; - Message = message; + Id = id; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } + public global::System.String Id { get; } - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion? other) { if (ReferenceEquals(null, other)) { @@ -2230,7 +2156,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Un return false; } - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + return (Id.Equals(other.Id)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2250,7 +2176,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Un return false; } - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion)obj); } public override global::System.Int32 GetHashCode() @@ -2258,8 +2184,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Un unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * Id.GetHashCode(); return hash; } } @@ -2267,9 +2192,9 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Un // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -2281,7 +2206,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Fu public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError? other) { if (ReferenceEquals(null, other)) { @@ -2318,7 +2243,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Fu return false; } - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError)obj); } public override global::System.Int32 GetHashCode() @@ -2335,9 +2260,9 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_Fu // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -2349,7 +2274,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_In public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -2386,7 +2311,7 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_In return false; } - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -2401,125 +2326,23 @@ public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_In } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeploymentResult + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError { - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } - } + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : IValidateFusionDeployment_ValidateFusionConfigurationComposition - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors - { + /// + /// The name of the current Object type at runtime. + /// public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentResult : global::System.IEquatable, ICommitFusionDeploymentResult - { - public CommitFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish commitFusionConfigurationPublish) - { - CommitFusionConfigurationPublish = commitFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (CommitFusionConfigurationPublish.Equals(other.CommitFusionConfigurationPublish)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * CommitFusionConfigurationPublish.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload - { - public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(global::System.Collections.Generic.IReadOnlyList? errors) - { - Errors = errors; - } - - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError? other) { if (ReferenceEquals(null, other)) { @@ -2536,7 +2359,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfi return false; } - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2556,7 +2379,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfi return false; } - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError)obj); } public override global::System.Int32 GetHashCode() @@ -2564,14 +2387,8 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfi unchecked { int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -2579,9 +2396,9 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfi // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -2593,7 +2410,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_Unauthoriz public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -2630,7 +2447,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_Unauthoriz return false; } - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -2647,9 +2464,9 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_Unauthoriz // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -2661,7 +2478,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConf public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError? other) { if (ReferenceEquals(null, other)) { @@ -2698,7 +2515,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConf return false; } - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError)obj); } public override global::System.Int32 GetHashCode() @@ -2715,9 +2532,9 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConf // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError + internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) { this.__typename = __typename; Message = message; @@ -2729,7 +2546,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidPro public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) + public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError? other) { if (ReferenceEquals(null, other)) { @@ -2766,7 +2583,7 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidPro return false; } - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); + return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError)obj); } public override global::System.Int32 GetHashCode() @@ -2783,61 +2600,93 @@ public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidPro // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeploymentResult + internal partial interface IUploadFusionSourceSchemaResult { - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : ICommitFusionDeployment_CommitFusionConfigurationPublish + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : IUploadFusionSourceSchema_UploadFusionSubgraph { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion + { + public global::System.String Id { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors { public global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResult : global::System.IEquatable, IWatchFusionDeploymentResult + internal partial class BeginFusionDeploymentResult : global::System.IEquatable, IBeginFusionDeploymentResult { - public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged onFusionConfigurationPublishingTaskChanged) + public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish beginFusionConfigurationPublish) { - OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; + BeginFusionConfigurationPublish = beginFusionConfigurationPublish; } - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } - public virtual global::System.Boolean Equals(WatchFusionDeploymentResult? other) + public virtual global::System.Boolean Equals(BeginFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -2854,7 +2703,7 @@ public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IW return false; } - return (OnFusionConfigurationPublishingTaskChanged.Equals(other.OnFusionConfigurationPublishingTaskChanged)); + return (BeginFusionConfigurationPublish.Equals(other.BeginFusionConfigurationPublish)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2874,7 +2723,7 @@ public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IW return false; } - return Equals((WatchFusionDeploymentResult)obj); + return Equals((BeginFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -2882,7 +2731,7 @@ public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IW unchecked { int hash = 5; - hash ^= 397 * OnFusionConfigurationPublishingTaskChanged.GetHashCode(); + hash ^= 397 * BeginFusionConfigurationPublish.GetHashCode(); return hash; } } @@ -2890,23 +2739,18 @@ public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IW // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) + public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(global::System.String? requestId, global::System.Collections.Generic.IReadOnlyList? errors) { - this.__typename = __typename; - State = state; + RequestId = requestId; Errors = errors; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Collections.Generic.IReadOnlyList Errors { get; } + public global::System.String? RequestId { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload? other) { if (ReferenceEquals(null, other)) { @@ -2923,7 +2767,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + return (((RequestId is null && other.RequestId is null) || RequestId != null && RequestId.Equals(other.RequestId))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -2943,7 +2787,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload)obj); } public override global::System.Int32 GetHashCode() @@ -2951,11 +2795,17 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - foreach (var Errors_elm in Errors) + if (RequestId != null) { - hash ^= 397 * Errors_elm.GetHashCode(); + hash ^= 397 * RequestId.GetHashCode(); + } + + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } } return hash; @@ -2965,21 +2815,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -2996,7 +2846,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3016,7 +2866,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -3025,7 +2875,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3033,23 +2883,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; - Errors = errors; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Collections.Generic.IReadOnlyList Errors { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -3066,7 +2914,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3086,7 +2934,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -3095,12 +2943,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3108,21 +2951,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -3139,7 +2982,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3159,7 +3002,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -3168,7 +3011,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3176,21 +3019,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionCo // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError? other) { if (ReferenceEquals(null, other)) { @@ -3207,7 +3050,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Operatio return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3227,7 +3070,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Operatio return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError)obj); } public override global::System.Int32 GetHashCode() @@ -3236,7 +3079,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Operatio { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3244,21 +3087,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Operatio // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) { if (ReferenceEquals(null, other)) { @@ -3275,7 +3118,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3295,7 +3138,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); } public override global::System.Int32 GetHashCode() @@ -3304,7 +3147,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3312,23 +3155,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued + internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Int32 queuePosition) + public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; - QueuePosition = queuePosition; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Int32 QueuePosition { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued? other) + public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError? other) { if (ReferenceEquals(null, other)) { @@ -3345,7 +3186,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::System.Object.Equals(QueuePosition, other.QueuePosition); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3365,7 +3206,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued)obj); + return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError)obj); } public override global::System.Int32 GetHashCode() @@ -3374,30 +3215,88 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - hash ^= 397 * QueuePosition.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish + { + public global::System.String? RequestId { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : IBeginFusionDeployment_BeginFusionConfigurationPublish + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError + { + } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady + internal partial class ReleaseFusionDeploymentResult : global::System.IEquatable, IReleaseFusionDeploymentResult { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition cancelFusionConfigurationComposition) { - this.__typename = __typename; - State = state; + CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady? other) + public virtual global::System.Boolean Equals(ReleaseFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -3414,7 +3313,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (CancelFusionConfigurationComposition.Equals(other.CancelFusionConfigurationComposition)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3434,7 +3333,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady)obj); + return Equals((ReleaseFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -3442,8 +3341,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * CancelFusionConfigurationComposition.GetHashCode(); return hash; } } @@ -3451,21 +3349,16 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Processi // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) { - this.__typename = __typename; - State = state; + Errors = errors; } - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress? other) + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload? other) { if (ReferenceEquals(null, other)) { @@ -3482,7 +3375,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Validati return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3502,7 +3395,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Validati return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress)obj); + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload)obj); } public override global::System.Int32 GetHashCode() @@ -3510,8 +3403,14 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Validati unchecked { int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + return hash; } } @@ -3519,21 +3418,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Validati // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { this.__typename = __typename; - State = state; + Message = message; } /// /// The name of the current Object type at runtime. /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } + public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval? other) + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -3550,7 +3449,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForA return false; } - return (__typename.Equals(other.__typename)) && State.Equals(other.State); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3570,7 +3469,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForA return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval)obj); + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -3579,7 +3478,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForA { int hash = 5; hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); return hash; } } @@ -3587,16 +3486,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForA // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(global::System.String message) + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError? other) + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -3613,7 +3517,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_C return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3633,7 +3537,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_C return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError)obj); + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -3641,6 +3545,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_C unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -3649,16 +3554,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_C // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError + internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(global::System.String message) + public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError? other) + public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) { if (ReferenceEquals(null, other)) { @@ -3675,7 +3585,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3695,7 +3605,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError)obj); + return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); } public override global::System.Int32 GetHashCode() @@ -3703,24 +3613,70 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : IReleaseFusionDeployment_CancelFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError + { + } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError + internal partial class ValidateFusionDeploymentResult : global::System.IEquatable, IValidateFusionDeploymentResult { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(global::System.String message) + public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition validateFusionConfigurationComposition) { - Message = message; + ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError? other) + public virtual global::System.Boolean Equals(ValidateFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -3737,7 +3693,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P return false; } - return (Message.Equals(other.Message)); + return (ValidateFusionConfigurationComposition.Equals(other.ValidateFusionConfigurationComposition)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3757,7 +3713,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError)obj); + return Equals((ValidateFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -3765,7 +3721,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P unchecked { int hash = 5; - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * ValidateFusionConfigurationComposition.GetHashCode(); return hash; } } @@ -3773,16 +3729,16 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(global::System.String message) + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) { - Message = message; + Errors = errors; } - public global::System.String Message { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError? other) + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload? other) { if (ReferenceEquals(null, other)) { @@ -3799,7 +3755,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_R return false; } - return (Message.Equals(other.Message)); + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3819,7 +3775,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_R return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError)obj); + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload)obj); } public override global::System.Int32 GetHashCode() @@ -3827,7 +3783,14 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_R unchecked { int hash = 5; - hash ^= 397 * Message.GetHashCode(); + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + return hash; } } @@ -3835,16 +3798,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_R // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(global::System.String message) + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError? other) + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -3861,7 +3829,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3881,7 +3849,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError)obj); + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -3889,6 +3857,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -3897,16 +3866,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(global::System.String message) + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1? other) + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -3923,7 +3897,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -3943,7 +3917,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1)obj); + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -3951,6 +3925,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -3959,16 +3934,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_I // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError + internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(global::System.String message) + public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError? other) + public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) { if (ReferenceEquals(null, other)) { @@ -3985,7 +3965,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_M return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -4005,7 +3985,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_M return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError)obj); + return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); } public override global::System.Int32 GetHashCode() @@ -4013,24 +3993,70 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_M unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeploymentResult + { + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition + { + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : IValidateFusionDeployment_ValidateFusionConfigurationComposition + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors + { + public global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError + { + } + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError + internal partial class CommitFusionDeploymentResult : global::System.IEquatable, ICommitFusionDeploymentResult { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(global::System.String message) + public CommitFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish commitFusionConfigurationPublish) { - Message = message; + CommitFusionConfigurationPublish = commitFusionConfigurationPublish; } - public global::System.String Message { get; } + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError? other) + public virtual global::System.Boolean Equals(CommitFusionDeploymentResult? other) { if (ReferenceEquals(null, other)) { @@ -4047,7 +4073,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_O return false; } - return (Message.Equals(other.Message)); + return (CommitFusionConfigurationPublish.Equals(other.CommitFusionConfigurationPublish)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -4067,7 +4093,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_O return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError)obj); + return Equals((CommitFusionDeploymentResult)obj); } public override global::System.Int32 GetHashCode() @@ -4075,7 +4101,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_O unchecked { int hash = 5; - hash ^= 397 * Message.GetHashCode(); + hash ^= 397 * CommitFusionConfigurationPublish.GetHashCode(); return hash; } } @@ -4083,16 +4109,16 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_O // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(global::System.String message) + public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(global::System.Collections.Generic.IReadOnlyList? errors) { - Message = message; + Errors = errors; } - public global::System.String Message { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError? other) + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload? other) { if (ReferenceEquals(null, other)) { @@ -4109,7 +4135,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P return false; } - return (Message.Equals(other.Message)); + return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -4129,7 +4155,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError)obj); + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload)obj); } public override global::System.Int32 GetHashCode() @@ -4137,7 +4163,14 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P unchecked { int hash = 5; - hash ^= 397 * Message.GetHashCode(); + if (Errors != null) + { + foreach (var Errors_elm in Errors) + { + hash ^= 397 * Errors_elm.GetHashCode(); + } + } + return hash; } } @@ -4145,16 +4178,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_P // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(global::System.String message) + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError? other) + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation? other) { if (ReferenceEquals(null, other)) { @@ -4171,7 +4209,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_S return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -4191,7 +4229,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_S return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError)obj); + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); } public override global::System.Int32 GetHashCode() @@ -4199,6 +4237,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_S unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } @@ -4207,16 +4246,21 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_S // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(global::System.String message) + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) { + this.__typename = __typename; Message = message; } + /// + /// The name of the current Object type at runtime. + /// + public global::System.String __typename { get; } public global::System.String Message { get; } - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1? other) + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError? other) { if (ReferenceEquals(null, other)) { @@ -4233,7 +4277,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U return false; } - return (Message.Equals(other.Message)); + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); } public override global::System.Boolean Equals(global::System.Object? obj) @@ -4253,7 +4297,7 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U return false; } - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1)obj); + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError)obj); } public override global::System.Int32 GetHashCode() @@ -4261,168 +4305,219 @@ public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_U unchecked { int hash = 5; + hash ^= 397 * __typename.GetHashCode(); hash ^= 397 * Message.GetHashCode(); return hash; } } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeploymentResult + internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError { - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } - } + public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) + { + this.__typename = __typename; + Message = message; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { + /// + /// The name of the current Object type at runtime. + /// public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - } + public global::System.String Message { get; } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - } + public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } + if (ReferenceEquals(this, other)) + { + return true; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - } + if (other.GetType() != GetType()) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } + return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } + public override global::System.Boolean Equals(global::System.Object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } + if (ReferenceEquals(this, obj)) + { + return true; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Int32 QueuePosition { get; } - } + if (obj.GetType() != GetType()) + { + return false; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } + return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * __typename.GetHashCode(); + hash ^= 397 * Message.GetHashCode(); + return hash; + } + } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged + internal partial interface ICommitFusionDeploymentResult { + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish { - public global::System.String Message { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : ICommitFusionDeployment_CommitFusionConfigurationPublish { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors { + public global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError { } // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors + internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError { } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + internal partial class StartFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter { - public global::System.String Message { get; } - } + private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; + public global::System.String TypeName => "StartFusionConfigurationCompositionInput"; - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } + public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } + public global::System.Object? Format(global::System.Object? runtimeValue) + { + if (runtimeValue is null) + { + return null; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo; + if (input is null || inputInfo is null) + { + throw new global::System.ArgumentException(nameof(runtimeValue)); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } + var fields = new global::System.Collections.Generic.List>(); + if (inputInfo.IsRequestIdSet) + { + fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); + } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { + return fields; + } + + private global::System.Object? FormatRequestId(global::System.String input) + { + if (input is null) + { + throw new global::System.ArgumentNullException(nameof(input)); + } + + return _iDFormatter.Format(input); + } } - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record StartFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo { + public virtual global::System.Boolean Equals(StartFusionConfigurationCompositionInput? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other.GetType() != GetType()) + { + return false; + } + + return (RequestId.Equals(other.RequestId)); + } + + public override global::System.Int32 GetHashCode() + { + unchecked + { + int hash = 5; + hash ^= 397 * RequestId.GetHashCode(); + return hash; + } + } + + private global::System.String _value_requestId = default !; + private global::System.Boolean _set_requestId; + public global::System.String RequestId + { + get => _value_requestId; + init + { + _set_requestId = true; + _value_requestId = value; + } + } + + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; } // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator @@ -6017,10 +6112,10 @@ internal partial record FusionSubgraphVersionInput : global::ChilliCream.Nitro.F // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class StartFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + internal partial class CancelFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter { private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "StartFusionConfigurationCompositionInput"; + public global::System.String TypeName => "CancelFusionConfigurationCompositionInput"; public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { @@ -6034,8 +6129,8 @@ public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver return null; } - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo; + var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput; + var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo; if (input is null || inputInfo is null) { throw new global::System.ArgumentException(nameof(runtimeValue)); @@ -6063,9 +6158,9 @@ public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StartFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo + internal partial record CancelFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo { - public virtual global::System.Boolean Equals(StartFusionConfigurationCompositionInput? other) + public virtual global::System.Boolean Equals(CancelFusionConfigurationCompositionInput? other) { if (ReferenceEquals(null, other)) { @@ -6107,109 +6202,14 @@ internal partial record StartFusionConfigurationCompositionInput : global::Chill } } - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; + global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; } // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CancelFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter + internal partial class ValidateFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter { - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "CancelFusionConfigurationCompositionInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsRequestIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); - } - - return fields; - } - - private global::System.Object? FormatRequestId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CancelFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo - { - public virtual global::System.Boolean Equals(CancelFusionConfigurationCompositionInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (RequestId.Equals(other.RequestId)); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * RequestId.GetHashCode(); - return hash; - } - } - - private global::System.String _value_requestId = default !; - private global::System.Boolean _set_requestId; - public global::System.String RequestId - { - get => _value_requestId; - init - { - _set_requestId = true; - _value_requestId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; + private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; public global::System.String TypeName => "ValidateFusionConfigurationCompositionInput"; @@ -6502,15 +6502,13 @@ public ProcessingState Parse(global::System.String serializedValue) // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } /// errors { /// __typename /// ...FusionError @@ -6524,16 +6522,16 @@ public ProcessingState Parse(global::System.String serializedValue) /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaMutationDocument : global::StrawberryShake.IDocument + internal partial class ClaimFusionDeploymentMutationDocument : global::StrawberryShake.IDocument { - private UploadFusionSourceSchemaMutationDocument() + private ClaimFusionDeploymentMutationDocument() { } - public static UploadFusionSourceSchemaMutationDocument Instance { get; } = new UploadFusionSourceSchemaMutationDocument(); + public static ClaimFusionDeploymentMutationDocument Instance { get; } = new ClaimFusionDeploymentMutationDocument(); public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { uploadFusionSubgraph(input: $input) { __typename fusionSubgraphVersion { __typename id } errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "00a2f0dbe78ac0ebc27723990e9d8a6e"); + public global::System.ReadOnlySpan Body => "mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { startFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "260243f143261b45d8ac102e9c5459bd"); public override global::System.String ToString() { @@ -6547,15 +6545,13 @@ private UploadFusionSourceSchemaMutationDocument() // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } /// errors { /// __typename /// ...FusionError @@ -6569,42 +6565,42 @@ private UploadFusionSourceSchemaMutationDocument() /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaMutation : global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation + internal partial class ClaimFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _uploadFusionSubgraphInputFormatter; + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _startFusionConfigurationCompositionInputFormatter; private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + public ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _uploadFusionSubgraphInputFormatter = serializerResolver.GetInputValueFormatter("UploadFusionSubgraphInput"); + _startFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("StartFusionConfigurationCompositionInput"); } - private UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter uploadFusionSubgraphInputFormatter) + private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter startFusionConfigurationCompositionInputFormatter) { _operationExecutor = operationExecutor; _configure = configure; - _uploadFusionSubgraphInputFormatter = uploadFusionSubgraphInputFormatter; + _startFusionConfigurationCompositionInputFormatter = startFusionConfigurationCompositionInputFormatter; } - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IUploadFusionSourceSchemaResult); + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IClaimFusionDeploymentResult); - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure) + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure) { - return new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchemaMutation(_operationExecutor, _configure.Add(configure), _uploadFusionSubgraphInputFormatter); + return new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _startFusionConfigurationCompositionInputFormatter); } - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri) + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) { return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); } - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) { return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); } - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default) + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) { var request = CreateRequest(input); foreach (var configure in _configure) @@ -6615,68 +6611,49 @@ private UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecu return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); } - private void MapFilesFromTypeUploadFusionSubgraphInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) - { - var pathArchive = path + ".archive"; - var valueArchive = value.Archive; - files.Add(pathArchive, valueArchive is global::StrawberryShake.Upload u ? u : null); - } - - private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) - { - if (value is { } value_i) - { - MapFilesFromTypeUploadFusionSubgraphInput(path, value_i, files); - } - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) { var request = CreateRequest(input); return _operationExecutor.Watch(request, strategy); } - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input) + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input) { var variables = new global::System.Collections.Generic.Dictionary(); variables.Add("input", FormatInput(input)); - var files = new global::System.Collections.Generic.Dictionary(); - MapFilesFromArgumentInput("variables.input", input, files); - return CreateRequest(variables, files); + return CreateRequest(variables); } - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) { - return new global::StrawberryShake.OperationRequest(id: UploadFusionSourceSchemaMutationDocument.Instance.Hash.Value, name: "UploadFusionSourceSchema", document: UploadFusionSourceSchemaMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); + return new global::StrawberryShake.OperationRequest(id: ClaimFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ClaimFusionDeployment", document: ClaimFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); } - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value) + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput value) { if (value is null) { throw new global::System.ArgumentNullException(nameof(value)); } - return _uploadFusionSubgraphInputFormatter.Format(value); + return _startFusionConfigurationCompositionInputFormatter.Format(value); } global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) { - return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); + return CreateRequest(variables!); } } // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation + /// Represents the operation service of the ClaimFusionDeployment GraphQL operation /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { + /// mutation ClaimFusionDeployment( + /// $input: StartFusionConfigurationCompositionInput! + /// ) { + /// startFusionConfigurationComposition(input: $input) { /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } /// errors { /// __typename /// ...FusionError @@ -6690,46 +6667,53 @@ private void MapFilesFromArgumentInput(global::System.String path, global::Chill /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchemaMutation : global::StrawberryShake.IOperationRequestFactory + internal partial interface IClaimFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory { - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); } // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// Represents the operation service of the WatchFusionDeployment GraphQL operation /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } /// } /// } /// } - /// - /// fragment FusionError on Error { - /// message - /// } /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + internal partial class WatchFusionDeploymentSubscriptionDocument : global::StrawberryShake.IDocument { - private BeginFusionDeploymentMutationDocument() + private WatchFusionDeploymentSubscriptionDocument() { } - public static BeginFusionDeploymentMutationDocument Instance { get; } = new BeginFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { beginFusionConfigurationPublish(input: $input) { __typename requestId errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "820eda44da60af366f2c12a562552673"); + public static WatchFusionDeploymentSubscriptionDocument Instance { get; } = new WatchFusionDeploymentSubscriptionDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Subscription; + public global::System.ReadOnlySpan Body => "subscription WatchFusionDeployment($requestId: ID!) { onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { __typename state ... on ProcessingTaskIsQueued { queuePosition } ... on FusionConfigurationPublishingFailed { errors { __typename message } } ... on FusionConfigurationValidationFailed { errors { __typename message } } } }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "e2015ba8e5cd3117ae71c8d44bd650d7"); public override global::System.String ToString() { @@ -6743,97 +6727,70 @@ private BeginFusionDeploymentMutationDocument() // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// Represents the operation service of the WatchFusionDeployment GraphQL operation /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } /// } /// } /// } - /// - /// fragment FusionError on Error { - /// message - /// } /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation + internal partial class WatchFusionDeploymentSubscription : global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _beginFusionConfigurationPublishInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter; + public WatchFusionDeploymentSubscription(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _beginFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("BeginFusionConfigurationPublishInput"); - } - - private BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter beginFusionConfigurationPublishInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _beginFusionConfigurationPublishInputFormatter = beginFusionConfigurationPublishInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IBeginFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _beginFusionConfigurationPublishInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); } - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IWatchFusionDeploymentResult); - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + public global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null) { - var request = CreateRequest(input); + var request = CreateRequest(requestId); return _operationExecutor.Watch(request, strategy); } - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input) + private global::StrawberryShake.OperationRequest CreateRequest(global::System.String requestId) { var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); + variables.Add("requestId", FormatRequestId(requestId)); return CreateRequest(variables); } private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) { - return new global::StrawberryShake.OperationRequest(id: BeginFusionDeploymentMutationDocument.Instance.Hash.Value, name: "BeginFusionDeployment", document: BeginFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + return new global::StrawberryShake.OperationRequest(id: WatchFusionDeploymentSubscriptionDocument.Instance.Hash.Value, name: "WatchFusionDeployment", document: WatchFusionDeploymentSubscriptionDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); } - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput value) + private global::System.Object? FormatRequestId(global::System.String value) { if (value is null) { throw new global::System.ArgumentNullException(nameof(value)); } - return _beginFusionConfigurationPublishInputFormatter.Format(value); + return _iDFormatter.Format(value); } global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) @@ -6844,43 +6801,48 @@ private BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// Represents the operation service of the WatchFusionDeployment GraphQL operation /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { + /// subscription WatchFusionDeployment($requestId: ID!) { + /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError + /// state + /// ... on ProcessingTaskIsQueued { + /// queuePosition + /// } + /// ... on FusionConfigurationPublishingFailed { + /// errors { + /// __typename + /// message + /// } + /// } + /// ... on FusionConfigurationValidationFailed { + /// errors { + /// __typename + /// message + /// } /// } /// } /// } - /// - /// fragment FusionError on Error { - /// message - /// } /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + internal partial interface IWatchFusionDeploymentSubscription : global::StrawberryShake.IOperationRequestFactory { - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null); } // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } /// errors { /// __typename /// ...FusionError @@ -6894,16 +6856,16 @@ internal partial interface IBeginFusionDeploymentMutation : global::StrawberrySh /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + internal partial class UploadFusionSourceSchemaMutationDocument : global::StrawberryShake.IDocument { - private ClaimFusionDeploymentMutationDocument() + private UploadFusionSourceSchemaMutationDocument() { } - public static ClaimFusionDeploymentMutationDocument Instance { get; } = new ClaimFusionDeploymentMutationDocument(); + public static UploadFusionSourceSchemaMutationDocument Instance { get; } = new UploadFusionSourceSchemaMutationDocument(); public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { startFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "260243f143261b45d8ac102e9c5459bd"); + public global::System.ReadOnlySpan Body => "mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { uploadFusionSubgraph(input: $input) { __typename fusionSubgraphVersion { __typename id } errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "00a2f0dbe78ac0ebc27723990e9d8a6e"); public override global::System.String ToString() { @@ -6917,13 +6879,15 @@ private ClaimFusionDeploymentMutationDocument() // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } /// errors { /// __typename /// ...FusionError @@ -6937,42 +6901,42 @@ private ClaimFusionDeploymentMutationDocument() /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation + internal partial class UploadFusionSourceSchemaMutation : global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _startFusionConfigurationCompositionInputFormatter; + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _uploadFusionSubgraphInputFormatter; private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + public UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _startFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("StartFusionConfigurationCompositionInput"); + _uploadFusionSubgraphInputFormatter = serializerResolver.GetInputValueFormatter("UploadFusionSubgraphInput"); } - private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter startFusionConfigurationCompositionInputFormatter) + private UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter uploadFusionSubgraphInputFormatter) { _operationExecutor = operationExecutor; _configure = configure; - _startFusionConfigurationCompositionInputFormatter = startFusionConfigurationCompositionInputFormatter; + _uploadFusionSubgraphInputFormatter = uploadFusionSubgraphInputFormatter; } - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IClaimFusionDeploymentResult); + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IUploadFusionSourceSchemaResult); - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure) + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure) { - return new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _startFusionConfigurationCompositionInputFormatter); + return new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchemaMutation(_operationExecutor, _configure.Add(configure), _uploadFusionSubgraphInputFormatter); } - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri) { return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); } - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) { return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); } - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default) { var request = CreateRequest(input); foreach (var configure in _configure) @@ -6983,49 +6947,68 @@ private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); } - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + private void MapFilesFromTypeUploadFusionSubgraphInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) + { + var pathArchive = path + ".archive"; + var valueArchive = value.Archive; + files.Add(pathArchive, valueArchive is global::StrawberryShake.Upload u ? u : null); + } + + private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) + { + if (value is { } value_i) + { + MapFilesFromTypeUploadFusionSubgraphInput(path, value_i, files); + } + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) { var request = CreateRequest(input); return _operationExecutor.Watch(request, strategy); } - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input) + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input) { var variables = new global::System.Collections.Generic.Dictionary(); variables.Add("input", FormatInput(input)); - return CreateRequest(variables); + var files = new global::System.Collections.Generic.Dictionary(); + MapFilesFromArgumentInput("variables.input", input, files); + return CreateRequest(variables, files); } - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) { - return new global::StrawberryShake.OperationRequest(id: ClaimFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ClaimFusionDeployment", document: ClaimFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + return new global::StrawberryShake.OperationRequest(id: UploadFusionSourceSchemaMutationDocument.Instance.Hash.Value, name: "UploadFusionSourceSchema", document: UploadFusionSourceSchemaMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); } - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput value) + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value) { if (value is null) { throw new global::System.ArgumentNullException(nameof(value)); } - return _startFusionConfigurationCompositionInputFormatter.Format(value); + return _uploadFusionSubgraphInputFormatter.Format(value); } global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) { - return CreateRequest(variables!); + return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); } } // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation + /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { + /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { + /// uploadFusionSubgraph(input: $input) { /// __typename + /// fusionSubgraphVersion { + /// __typename + /// id + /// } /// errors { /// __typename /// ...FusionError @@ -7039,24 +7022,23 @@ private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + internal partial interface IUploadFusionSourceSchemaMutation : global::StrawberryShake.IOperationRequestFactory { - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); } // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator /// - /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation + /// Represents the operation service of the BeginFusionDeployment GraphQL operation /// - /// mutation ReleaseFusionDeployment( - /// $input: CancelFusionConfigurationCompositionInput! - /// ) { - /// cancelFusionConfigurationComposition(input: $input) { + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { /// __typename + /// requestId /// errors { /// __typename /// ...FusionError @@ -7070,13 +7052,187 @@ internal partial interface IClaimFusionDeploymentMutation : global::StrawberrySh /// /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + internal partial class BeginFusionDeploymentMutationDocument : global::StrawberryShake.IDocument { - private ReleaseFusionDeploymentMutationDocument() + private BeginFusionDeploymentMutationDocument() { } - public static ReleaseFusionDeploymentMutationDocument Instance { get; } = new ReleaseFusionDeploymentMutationDocument(); + public static BeginFusionDeploymentMutationDocument Instance { get; } = new BeginFusionDeploymentMutationDocument(); + public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; + public global::System.ReadOnlySpan Body => "mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { beginFusionConfigurationPublish(input: $input) { __typename requestId errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; + public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "820eda44da60af366f2c12a562552673"); + + public override global::System.String ToString() + { +#if NETCOREAPP3_1_OR_GREATER + return global::System.Text.Encoding.UTF8.GetString(Body); +#else + return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); +#endif + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + /// + /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { + /// __typename + /// requestId + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class BeginFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation + { + private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; + private readonly global::StrawberryShake.Serialization.IInputValueFormatter _beginFusionConfigurationPublishInputFormatter; + private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; + public BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); + _beginFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("BeginFusionConfigurationPublishInput"); + } + + private BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter beginFusionConfigurationPublishInputFormatter) + { + _operationExecutor = operationExecutor; + _configure = configure; + _beginFusionConfigurationPublishInputFormatter = beginFusionConfigurationPublishInputFormatter; + } + + global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IBeginFusionDeploymentResult); + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure) + { + return new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _beginFusionConfigurationPublishInputFormatter); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); + } + + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) + { + return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); + } + + public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) + { + var request = CreateRequest(input); + foreach (var configure in _configure) + { + configure(request); + } + + return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + } + + public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) + { + var request = CreateRequest(input); + return _operationExecutor.Watch(request, strategy); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input) + { + var variables = new global::System.Collections.Generic.Dictionary(); + variables.Add("input", FormatInput(input)); + return CreateRequest(variables); + } + + private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return new global::StrawberryShake.OperationRequest(id: BeginFusionDeploymentMutationDocument.Instance.Hash.Value, name: "BeginFusionDeployment", document: BeginFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + } + + private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput value) + { + if (value is null) + { + throw new global::System.ArgumentNullException(nameof(value)); + } + + return _beginFusionConfigurationPublishInputFormatter.Format(value); + } + + global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + { + return CreateRequest(variables!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator + /// + /// Represents the operation service of the BeginFusionDeployment GraphQL operation + /// + /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + /// beginFusionConfigurationPublish(input: $input) { + /// __typename + /// requestId + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial interface IBeginFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory + { + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure); + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); + global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); + global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + /// + /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation + /// + /// mutation ReleaseFusionDeployment( + /// $input: CancelFusionConfigurationCompositionInput! + /// ) { + /// cancelFusionConfigurationComposition(input: $input) { + /// __typename + /// errors { + /// __typename + /// ...FusionError + /// } + /// } + /// } + /// + /// fragment FusionError on Error { + /// message + /// } + /// + /// + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ReleaseFusionDeploymentMutationDocument : global::StrawberryShake.IDocument + { + private ReleaseFusionDeploymentMutationDocument() + { + } + + public static ReleaseFusionDeploymentMutationDocument Instance { get; } = new ReleaseFusionDeploymentMutationDocument(); public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; public global::System.ReadOnlySpan Body => "mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { cancelFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "66f0c2afee4b4150360b31081c019f81"); @@ -7604,222 +7760,414 @@ internal partial interface ICommitFusionDeploymentMutation : global::StrawberryS global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); } - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ClientGenerator /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// + /// Represents the FusionApiClient GraphQL client /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentSubscriptionDocument : global::StrawberryShake.IDocument + internal partial class FusionApiClient : global::ChilliCream.Nitro.Fusion.Transport.IFusionApiClient { - private WatchFusionDeploymentSubscriptionDocument() + private readonly global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation _claimFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription _watchFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation _uploadFusionSourceSchema; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation _beginFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation _releaseFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation _validateFusionDeployment; + private readonly global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation _commitFusionDeployment; + public FusionApiClient(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation claimFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription watchFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation uploadFusionSourceSchema, global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation beginFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation releaseFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation validateFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation commitFusionDeployment) { + _claimFusionDeployment = claimFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(claimFusionDeployment)); + _watchFusionDeployment = watchFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(watchFusionDeployment)); + _uploadFusionSourceSchema = uploadFusionSourceSchema ?? throw new global::System.ArgumentNullException(nameof(uploadFusionSourceSchema)); + _beginFusionDeployment = beginFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(beginFusionDeployment)); + _releaseFusionDeployment = releaseFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(releaseFusionDeployment)); + _validateFusionDeployment = validateFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(validateFusionDeployment)); + _commitFusionDeployment = commitFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(commitFusionDeployment)); } - public static WatchFusionDeploymentSubscriptionDocument Instance { get; } = new WatchFusionDeploymentSubscriptionDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Subscription; - public global::System.ReadOnlySpan Body => "subscription WatchFusionDeployment($requestId: ID!) { onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { __typename state ... on ProcessingTaskIsQueued { queuePosition } ... on FusionConfigurationPublishingFailed { errors { __typename message } } ... on FusionConfigurationValidationFailed { errors { __typename message } } } }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "e2015ba8e5cd3117ae71c8d44bd650d7"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } + public static global::System.String ClientName => "FusionApiClient"; + public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment => _claimFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment => _watchFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema => _uploadFusionSourceSchema; + public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment => _beginFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment => _releaseFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment => _validateFusionDeployment; + public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment => _commitFusionDeployment; } - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator + // StrawberryShake.CodeGeneration.CSharp.Generators.ClientInterfaceGenerator /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// + /// Represents the FusionApiClient GraphQL client /// [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentSubscription : global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription + internal partial interface IFusionApiClient { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter; - public WatchFusionDeploymentSubscription(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment { get; } + + global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment { get; } + } +} + +namespace ChilliCream.Nitro.Fusion.Transport.State +{ + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public ClaimFusionDeploymentResultFactory() { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); } - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IWatchFusionDeploymentResult); + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentResult); - public global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null) + public ClaimFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) { - var request = CreateRequest(requestId); - return _operationExecutor.Watch(request, strategy); + if (dataInfo is ClaimFusionDeploymentResultInfo info) + { + return new ClaimFusionDeploymentResult(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(info.StartFusionConfigurationComposition)); + } + + throw new global::System.ArgumentException("ClaimFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData data) + { + IClaimFusionDeployment_StartFusionConfigurationComposition returnValue = default !; + if (data.__typename.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) + { + returnValue = new ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList? MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + return null; + } + + var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData child in list) + { + startFusionConfigurationCompositionErrors.Add(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(child)); + } + + return startFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition_Errors MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData data) + { + IClaimFusionDeployment_StartFusionConfigurationComposition_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) + { + return Create(dataInfo, snapshot); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class ClaimFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + { + public ClaimFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData startFusionConfigurationComposition) + { + StartFusionConfigurationComposition = startFusionConfigurationComposition; + } + + public global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData StartFusionConfigurationComposition { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; + + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new ClaimFusionDeploymentResultInfo(StartFusionConfigurationComposition); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory + { + public WatchFusionDeploymentResultFactory() + { + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentResult); + + public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is WatchFusionDeploymentResultInfo info) + { + return new WatchFusionDeploymentResult(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(info.OnFusionConfigurationPublishingTaskChanged)); + } + + throw new global::System.ArgumentException("WatchFusionDeploymentResultInfo expected."); } - private global::StrawberryShake.OperationRequest CreateRequest(global::System.String requestId) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("requestId", FormatRequestId(requestId)); - return CreateRequest(variables); + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData data) + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData fusionConfigurationPublishingFailed) + { + if (!fusionConfigurationPublishingFailed.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(fusionConfigurationPublishingFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(fusionConfigurationPublishingFailed.Errors)); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData fusionConfigurationPublishingSuccess) + { + if (!fusionConfigurationPublishingSuccess.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(fusionConfigurationPublishingSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingSuccess.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData fusionConfigurationValidationFailed) + { + if (!fusionConfigurationValidationFailed.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(fusionConfigurationValidationFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(fusionConfigurationValidationFailed.Errors)); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData fusionConfigurationValidationSuccess) + { + if (!fusionConfigurationValidationSuccess.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(fusionConfigurationValidationSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationSuccess.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData operationInProgress) + { + if (!operationInProgress.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(operationInProgress.__typename ?? throw new global::System.ArgumentNullException(), operationInProgress.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData processingTaskApproved) + { + if (!processingTaskApproved.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(processingTaskApproved.__typename ?? throw new global::System.ArgumentNullException(), processingTaskApproved.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData processingTaskIsQueued) + { + if (!processingTaskIsQueued.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (!processingTaskIsQueued.QueuePosition.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(processingTaskIsQueued.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsQueued.State!.Value, processingTaskIsQueued.QueuePosition!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData processingTaskIsReady) + { + if (!processingTaskIsReady.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(processingTaskIsReady.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsReady.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData validationInProgress) + { + if (!validationInProgress.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(validationInProgress.__typename ?? throw new global::System.ArgumentNullException(), validationInProgress.State!.Value); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData waitForApproval) + { + if (!waitForApproval.State.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(waitForApproval.__typename ?? throw new global::System.ArgumentNullException(), waitForApproval.State!.Value); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; + } + + private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + { + if (list is null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData child in list) + { + fusionConfigurationPublishingErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(child)); + } + + return fusionConfigurationPublishingErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData data) + { + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData processingTimeoutError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(processingTimeoutError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData readyTimeoutError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(readyTimeoutError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); + } + + return returnValue; } - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) + private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) { - return new global::StrawberryShake.OperationRequest(id: WatchFusionDeploymentSubscriptionDocument.Instance.Hash.Value, name: "WatchFusionDeployment", document: WatchFusionDeploymentSubscriptionDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); + if (list is null) + { + throw new global::System.ArgumentNullException(); + } + + var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData child in list) + { + fusionConfigurationValidationErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(child)); + } + + return fusionConfigurationValidationErrors; } - private global::System.Object? FormatRequestId(global::System.String value) + private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData data) { - if (value is null) + IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) { - throw new global::System.ArgumentNullException(nameof(value)); + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData mcpFeatureCollectionValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(mcpFeatureCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData openApiCollectionValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(openApiCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData persistedQueryValidationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(persistedQueryValidationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData schemaVersionChangeViolationError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(schemaVersionChangeViolationError.Message ?? throw new global::System.ArgumentNullException()); + } + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + { + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + } + else + { + throw new global::System.NotSupportedException(); } - return _iDFormatter.Format(value); + return returnValue; } - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) + global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) { - return CreateRequest(variables!); + return Create(dataInfo, snapshot); } } - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeploymentSubscription : global::StrawberryShake.IOperationRequestFactory - { - global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ClientGenerator - /// - /// Represents the FusionApiClient GraphQL client - /// + // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class FusionApiClient : global::ChilliCream.Nitro.Fusion.Transport.IFusionApiClient + internal partial class WatchFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo { - private readonly global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation _uploadFusionSourceSchema; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation _beginFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation _claimFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation _releaseFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation _validateFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation _commitFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription _watchFusionDeployment; - public FusionApiClient(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation uploadFusionSourceSchema, global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation beginFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation claimFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation releaseFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation validateFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation commitFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription watchFusionDeployment) + public WatchFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData onFusionConfigurationPublishingTaskChanged) { - _uploadFusionSourceSchema = uploadFusionSourceSchema ?? throw new global::System.ArgumentNullException(nameof(uploadFusionSourceSchema)); - _beginFusionDeployment = beginFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(beginFusionDeployment)); - _claimFusionDeployment = claimFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(claimFusionDeployment)); - _releaseFusionDeployment = releaseFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(releaseFusionDeployment)); - _validateFusionDeployment = validateFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(validateFusionDeployment)); - _commitFusionDeployment = commitFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(commitFusionDeployment)); - _watchFusionDeployment = watchFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(watchFusionDeployment)); + OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; } - public static global::System.String ClientName => "FusionApiClient"; - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema => _uploadFusionSourceSchema; - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment => _beginFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment => _claimFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment => _releaseFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment => _validateFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment => _commitFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment => _watchFusionDeployment; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ClientInterfaceGenerator - /// - /// Represents the FusionApiClient GraphQL client - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IFusionApiClient - { - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment { get; } + public global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData OnFusionConfigurationPublishingTaskChanged { get; } + public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); + public global::System.UInt64 Version => 0; - global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment { get; } + public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) + { + return new WatchFusionDeploymentResultInfo(OnFusionConfigurationPublishingTaskChanged); + } } -} -namespace ChilliCream.Nitro.Fusion.Transport.State -{ // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] internal partial class UploadFusionSourceSchemaResultFactory : global::StrawberryShake.IOperationResultDataFactory @@ -8049,115 +8397,16 @@ internal partial class BeginFusionDeploymentResultInfo : global::StrawberryShake { public BeginFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData beginFusionConfigurationPublish) { - BeginFusionConfigurationPublish = beginFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData BeginFusionConfigurationPublish { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new BeginFusionDeploymentResultInfo(BeginFusionConfigurationPublish); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public ClaimFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentResult); - - public ClaimFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is ClaimFusionDeploymentResultInfo info) - { - return new ClaimFusionDeploymentResult(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(info.StartFusionConfigurationComposition)); - } - - throw new global::System.ArgumentException("ClaimFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData data) - { - IClaimFusionDeployment_StartFusionConfigurationComposition returnValue = default !; - if (data.__typename.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData child in list) - { - startFusionConfigurationCompositionErrors.Add(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(child)); - } - - return startFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition_Errors MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData data) - { - IClaimFusionDeployment_StartFusionConfigurationComposition_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public ClaimFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData startFusionConfigurationComposition) - { - StartFusionConfigurationComposition = startFusionConfigurationComposition; + BeginFusionConfigurationPublish = beginFusionConfigurationPublish; } - public global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData StartFusionConfigurationComposition { get; } + public global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData BeginFusionConfigurationPublish { get; } public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); public global::System.UInt64 Version => 0; public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) { - return new ClaimFusionDeploymentResultInfo(StartFusionConfigurationComposition); + return new BeginFusionDeploymentResultInfo(BeginFusionConfigurationPublish); } } @@ -8365,263 +8614,26 @@ internal partial class CommitFusionDeploymentResultFactory : global::StrawberryS { public CommitFusionDeploymentResultFactory() { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentResult); - - public CommitFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is CommitFusionDeploymentResultInfo info) - { - return new CommitFusionDeploymentResult(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(info.CommitFusionConfigurationPublish)); - } - - throw new global::System.ArgumentException("CommitFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData data) - { - ICommitFusionDeployment_CommitFusionConfigurationPublish returnValue = default !; - if (data.__typename.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData child in list) - { - commitFusionConfigurationPublishErrors.Add(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(child)); - } - - return commitFusionConfigurationPublishErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData data) - { - ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public CommitFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData commitFusionConfigurationPublish) - { - CommitFusionConfigurationPublish = commitFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData CommitFusionConfigurationPublish { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new CommitFusionDeploymentResultInfo(CommitFusionConfigurationPublish); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public WatchFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentResult); - - public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is WatchFusionDeploymentResultInfo info) - { - return new WatchFusionDeploymentResult(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(info.OnFusionConfigurationPublishingTaskChanged)); - } - - throw new global::System.ArgumentException("WatchFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData data) - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData fusionConfigurationPublishingFailed) - { - if (!fusionConfigurationPublishingFailed.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(fusionConfigurationPublishingFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(fusionConfigurationPublishingFailed.Errors)); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData fusionConfigurationPublishingSuccess) - { - if (!fusionConfigurationPublishingSuccess.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(fusionConfigurationPublishingSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingSuccess.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData fusionConfigurationValidationFailed) - { - if (!fusionConfigurationValidationFailed.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(fusionConfigurationValidationFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(fusionConfigurationValidationFailed.Errors)); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData fusionConfigurationValidationSuccess) - { - if (!fusionConfigurationValidationSuccess.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(fusionConfigurationValidationSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationSuccess.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData operationInProgress) - { - if (!operationInProgress.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(operationInProgress.__typename ?? throw new global::System.ArgumentNullException(), operationInProgress.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData processingTaskApproved) - { - if (!processingTaskApproved.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(processingTaskApproved.__typename ?? throw new global::System.ArgumentNullException(), processingTaskApproved.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData processingTaskIsQueued) - { - if (!processingTaskIsQueued.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (!processingTaskIsQueued.QueuePosition.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(processingTaskIsQueued.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsQueued.State!.Value, processingTaskIsQueued.QueuePosition!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData processingTaskIsReady) - { - if (!processingTaskIsReady.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(processingTaskIsReady.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsReady.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData validationInProgress) - { - if (!validationInProgress.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(validationInProgress.__typename ?? throw new global::System.ArgumentNullException(), validationInProgress.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData waitForApproval) - { - if (!waitForApproval.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(waitForApproval.__typename ?? throw new global::System.ArgumentNullException(), waitForApproval.State!.Value); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - throw new global::System.ArgumentNullException(); - } - - var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData child in list) - { - fusionConfigurationPublishingErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(child)); - } - - return fusionConfigurationPublishingErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData data) - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData processingTimeoutError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(processingTimeoutError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData readyTimeoutError) + } + + global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentResult); + + public CommitFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) + { + if (dataInfo is CommitFusionDeploymentResultInfo info) { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(readyTimeoutError.Message ?? throw new global::System.ArgumentNullException()); + return new CommitFusionDeploymentResult(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(info.CommitFusionConfigurationPublish)); } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + + throw new global::System.ArgumentException("CommitFusionDeploymentResultInfo expected."); + } + + private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData data) + { + ICommitFusionDeployment_CommitFusionConfigurationPublish returnValue = default !; + if (data.__typename.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + returnValue = new CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); } else { @@ -8631,48 +8643,36 @@ public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResu return returnValue; } - private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) + private global::System.Collections.Generic.IReadOnlyList? MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) { if (list is null) { - throw new global::System.ArgumentNullException(); + return null; } - var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData child in list) + var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData child in list) { - fusionConfigurationValidationErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(child)); + commitFusionConfigurationPublishErrors.Add(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(child)); } - return fusionConfigurationValidationErrors; + return commitFusionConfigurationPublishErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData data) + private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData data) { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData mcpFeatureCollectionValidationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(mcpFeatureCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData openApiCollectionValidationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(openApiCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData persistedQueryValidationError) + ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors? returnValue; + if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(persistedQueryValidationError.Message ?? throw new global::System.ArgumentNullException()); + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData schemaVersionChangeViolationError) + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(schemaVersionChangeViolationError.Message ?? throw new global::System.ArgumentNullException()); + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) + else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); + returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); } else { @@ -8690,23 +8690,30 @@ public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResu // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo + internal partial class CommitFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo { - public WatchFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData onFusionConfigurationPublishingTaskChanged) + public CommitFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData commitFusionConfigurationPublish) { - OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; + CommitFusionConfigurationPublish = commitFusionConfigurationPublish; } - public global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData OnFusionConfigurationPublishingTaskChanged { get; } + public global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData CommitFusionConfigurationPublish { get; } public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); public global::System.UInt64 Version => 0; public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) { - return new WatchFusionDeploymentResultInfo(OnFusionConfigurationPublishingTaskChanged); + return new CommitFusionDeploymentResultInfo(CommitFusionConfigurationPublish); } } + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal interface IStartFusionConfigurationCompositionInputInfo + { + global::System.Boolean IsRequestIdSet { get; } + } + // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] internal interface IUploadFusionSubgraphInputInfo @@ -8810,13 +8817,6 @@ internal interface IFusionSubgraphVersionInputInfo global::System.Boolean IsTagSet { get; } } - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IStartFusionConfigurationCompositionInputInfo - { - global::System.Boolean IsRequestIdSet { get; } - } - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] internal interface ICancelFusionConfigurationCompositionInputInfo @@ -8844,71 +8844,219 @@ internal interface ICommitFusionConfigurationPublishInputInfo // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaBuilder : global::StrawberryShake.OperationResultBuilder + internal partial class ClaimFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder { private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; - public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) { - return new UploadFusionSourceSchemaResultInfo(Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "uploadFusionSubgraph"))); + return new ClaimFusionDeploymentResultInfo(Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "startFusionConfigurationComposition"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) + { + startFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(child)); + } + + return startFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + throw new global::System.NotSupportedException(); + } + + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + throw new global::System.ArgumentNullException(); + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + return _stringParser.Parse(obj.Value.GetString()!); + } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class WatchFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _processingStateParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _intParser; + public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + { + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _processingStateParser = serializerResolver.GetLeafValueParser("ProcessingState") ?? throw new global::System.ArgumentException("No serializer for type `ProcessingState` found."); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _intParser = serializerResolver.GetLeafValueParser("Int") ?? throw new global::System.ArgumentException("No serializer for type `Int` found."); + } + + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new WatchFusionDeploymentResultInfo(Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "onFusionConfigurationPublishingTaskChanged"))); + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { throw new global::System.ArgumentNullException(); } - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + throw new global::System.ArgumentNullException(); + } + + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("FusionConfigurationPublishingFailed", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + if (typename?.Equals("FusionConfigurationPublishingSuccess", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("FusionConfigurationValidationFailed", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + } + + if (typename?.Equals("FusionConfigurationValidationSuccess", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("OperationInProgress", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ProcessingTaskApproved", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ProcessingTaskIsQueued", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), queuePosition: Deserialize_NonNullableInt32(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "queuePosition"))); + } + + if (typename?.Equals("ProcessingTaskIsReady", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + } + + if (typename?.Equals("ValidationInProgress", global::System.StringComparison.Ordinal) ?? false) { - throw new global::System.ArgumentNullException(); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); } - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("WaitForApproval", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData(typename, fusionSubgraphVersion: Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "fusionSubgraphVersion")), errors: Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); } throw new global::System.NotSupportedException(); } - private global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::System.Text.Json.JsonElement? obj) + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { - return null; + throw new global::System.ArgumentNullException(); } if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - return null; - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData(typename, id: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"))); + throw new global::System.ArgumentNullException(); } - throw new global::System.NotSupportedException(); + return _stringParser.Parse(obj.Value.GetString()!); } - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.ProcessingState Deserialize_NonNullableProcessingState(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -8920,31 +9068,31 @@ public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultD throw new global::System.ArgumentNullException(); } - return _iDParser.Parse(obj.Value.GetString()!); + return _processingStateParser.Parse(obj.Value.GetString()!); } - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { - return null; + throw new global::System.ArgumentNullException(); } if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - return null; + throw new global::System.ArgumentNullException(); } - var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); + var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - uploadFusionSubgraphErrors.Add(Deserialize_NonNullableIUploadFusionSubgraphErrorData(child)); + fusionConfigurationPublishingErrors.Add(Deserialize_NonNullableIFusionConfigurationPublishingErrorData(child)); } - return uploadFusionSubgraphErrors; + return fusionConfigurationPublishingErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData Deserialize_NonNullableIUploadFusionSubgraphErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData Deserialize_NonNullableIFusionConfigurationPublishingErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -8957,122 +9105,56 @@ public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultD } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("InvalidFusionSourceSchemaArchiveError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) { return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("DuplicatedTagError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - throw new global::System.NotSupportedException(); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _booleanParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; - public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _booleanParser = serializerResolver.GetLeafValueParser("Boolean") ?? throw new global::System.ArgumentException("No serializer for type `Boolean` found."); - _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new BeginFusionDeploymentResultInfo(Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "beginFusionConfigurationPublish"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) + if (typename?.Equals("ProcessingTimeoutError", global::System.StringComparison.Ordinal) ?? false) { - throw new global::System.ArgumentNullException(); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + if (typename?.Equals("ReadyTimeoutError", global::System.StringComparison.Ordinal) ?? false) { - throw new global::System.ArgumentNullException(); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData(typename, requestId: Deserialize_String(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "requestId")), errors: Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } throw new global::System.NotSupportedException(); } - private global::System.String? Deserialize_String(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - return _iDParser.Parse(obj.Value.GetString()!); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { - return null; + throw new global::System.ArgumentNullException(); } if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - return null; + throw new global::System.ArgumentNullException(); } - var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - beginFusionConfigurationPublishErrors.Add(Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(child)); + fusionConfigurationValidationErrors.Add(Deserialize_NonNullableIFusionConfigurationValidationErrorData(child)); } - return beginFusionConfigurationPublishErrors; + return fusionConfigurationValidationErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData Deserialize_NonNullableIFusionConfigurationValidationErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9085,40 +9167,40 @@ public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("McpFeatureCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("StageNotFoundError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("OpenApiCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("SubgraphInvalidError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("PersistedQueryValidationError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("SchemaVersionChangeViolationError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } throw new global::System.NotSupportedException(); } - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) + private global::System.Int32 Deserialize_NonNullableInt32(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9130,31 +9212,35 @@ public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData throw new global::System.ArgumentNullException(); } - return _stringParser.Parse(obj.Value.GetString()!); + return _intParser.Parse(obj.Value.GetInt32()!); } } // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + internal partial class UploadFusionSourceSchemaBuilder : global::StrawberryShake.OperationResultBuilder { private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; + public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) { - return new ClaimFusionDeploymentResultInfo(Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "startFusionConfigurationComposition"))); + return new UploadFusionSourceSchemaResultInfo(Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "uploadFusionSubgraph"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9167,15 +9253,15 @@ public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData(typename, fusionSubgraphVersion: Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "fusionSubgraphVersion")), errors: Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); } throw new global::System.NotSupportedException(); } - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9185,85 +9271,18 @@ public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { return null; - } - - var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - startFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(child)); - } - - return startFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - } + } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData(typename, id: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"))); + } - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new ReleaseFusionDeploymentResultInfo(Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "cancelFusionConfigurationComposition"))); + throw new global::System.NotSupportedException(); } - private global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9275,16 +9294,10 @@ public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDa throw new global::System.ArgumentNullException(); } - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); + return _iDParser.Parse(obj.Value.GetString()!); } - private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9296,16 +9309,16 @@ public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDa return null; } - var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - cancelFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(child)); + uploadFusionSubgraphErrors.Add(Deserialize_NonNullableIUploadFusionSubgraphErrorData(child)); } - return cancelFusionConfigurationCompositionErrors; + return uploadFusionSubgraphErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData Deserialize_NonNullableIUploadFusionSubgraphErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9318,63 +9331,65 @@ public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDa } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("InvalidFusionSourceSchemaArchiveError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - throw new global::System.NotSupportedException(); - } + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) + if (typename?.Equals("DuplicatedTagError", global::System.StringComparison.Ordinal) ?? false) { - throw new global::System.ArgumentNullException(); + return new global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) { - throw new global::System.ArgumentNullException(); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - return _stringParser.Parse(obj.Value.GetString()!); + throw new global::System.NotSupportedException(); } } // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + internal partial class BeginFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + private readonly global::StrawberryShake.Serialization.ILeafValueParser _booleanParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; + public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + _booleanParser = serializerResolver.GetLeafValueParser("Boolean") ?? throw new global::System.ArgumentException("No serializer for type `Boolean` found."); + _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) { - return new ValidateFusionDeploymentResultInfo(Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "validateFusionConfigurationComposition"))); + return new BeginFusionDeploymentResultInfo(Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "beginFusionConfigurationPublish"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9387,15 +9402,15 @@ public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultD } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData(typename, requestId: Deserialize_String(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "requestId")), errors: Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); } throw new global::System.NotSupportedException(); } - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.String? Deserialize_String(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9407,16 +9422,31 @@ public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultD return null; } - var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + return _iDParser.Parse(obj.Value.GetString()!); + } + + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) + { + return null; + } + + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) + { + return null; + } + + var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - validateFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(child)); + beginFusionConfigurationPublishErrors.Add(Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(child)); } - return validateFusionConfigurationCompositionErrors; + return beginFusionConfigurationPublishErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9434,9 +9464,19 @@ public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultD return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("StageNotFoundError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + + if (typename?.Equals("SubgraphInvalidError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) @@ -9444,6 +9484,11 @@ public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultD return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } + if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) + { + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + } + throw new global::System.NotSupportedException(); } @@ -9465,27 +9510,25 @@ public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultD // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + internal partial class ReleaseFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) { - return new CommitFusionDeploymentResultInfo(Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "commitFusionConfigurationPublish"))); + return new ReleaseFusionDeploymentResultInfo(Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "cancelFusionConfigurationComposition"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9498,15 +9541,15 @@ public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDat } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData(typename, errors: Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); } throw new global::System.NotSupportedException(); } - private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9518,16 +9561,16 @@ public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDat return null; } - var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); + var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - commitFusionConfigurationPublishErrors.Add(Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(child)); + cancelFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(child)); } - return commitFusionConfigurationPublishErrors; + return cancelFusionConfigurationCompositionErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9576,29 +9619,27 @@ public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDat // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + internal partial class ValidateFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _processingStateParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _intParser; - public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) + public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _processingStateParser = serializerResolver.GetLeafValueParser("ProcessingState") ?? throw new global::System.ArgumentException("No serializer for type `ProcessingState` found."); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _intParser = serializerResolver.GetLeafValueParser("Int") ?? throw new global::System.ArgumentException("No serializer for type `Int` found."); } - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) { - return new WatchFusionDeploymentResultInfo(Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "onFusionConfigurationPublishingTaskChanged"))); + return new ValidateFusionDeploymentResultInfo(Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "validateFusionConfigurationComposition"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9611,54 +9652,61 @@ public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("FusionConfigurationPublishingFailed", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); } - if (typename?.Equals("FusionConfigurationPublishingSuccess", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } + throw new global::System.NotSupportedException(); + } - if (typename?.Equals("FusionConfigurationValidationFailed", global::System.StringComparison.Ordinal) ?? false) + private global::System.Collections.Generic.IReadOnlyList? Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); + return null; } - if (typename?.Equals("FusionConfigurationValidationSuccess", global::System.StringComparison.Ordinal) ?? false) + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + return null; } - if (typename?.Equals("OperationInProgress", global::System.StringComparison.Ordinal) ?? false) + var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); + foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + validateFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(child)); } - if (typename?.Equals("ProcessingTaskApproved", global::System.StringComparison.Ordinal) ?? false) + return validateFusionConfigurationCompositionErrors; + } + + private global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) + { + if (!obj.HasValue) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + throw new global::System.ArgumentNullException(); } - if (typename?.Equals("ProcessingTaskIsQueued", global::System.StringComparison.Ordinal) ?? false) + if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), queuePosition: Deserialize_NonNullableInt32(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "queuePosition"))); + throw new global::System.ArgumentNullException(); } - if (typename?.Equals("ProcessingTaskIsReady", global::System.StringComparison.Ordinal) ?? false) + var typename = obj.Value.GetProperty("__typename").GetString(); + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("ValidationInProgress", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("WaitForApproval", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } throw new global::System.NotSupportedException(); @@ -9678,44 +9726,31 @@ public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData return _stringParser.Parse(obj.Value.GetString()!); } + } - private global::ChilliCream.Nitro.Fusion.Transport.ProcessingState Deserialize_NonNullableProcessingState(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _processingStateParser.Parse(obj.Value.GetString()!); - } - - private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] + internal partial class CommitFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder + { + private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; + private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; + public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } + ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); + _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); + _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); + _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); + } - var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - fusionConfigurationPublishingErrors.Add(Deserialize_NonNullableIFusionConfigurationPublishingErrorData(child)); - } + protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - return fusionConfigurationPublishingErrors; + protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) + { + return new CommitFusionDeploymentResultInfo(Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "commitFusionConfigurationPublish"))); } - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData Deserialize_NonNullableIFusionConfigurationPublishingErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9728,56 +9763,36 @@ public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ProcessingTimeoutError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ReadyTimeoutError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData(typename, errors: Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); } throw new global::System.NotSupportedException(); } - private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) + private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { - throw new global::System.ArgumentNullException(); + return null; } if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) { - throw new global::System.ArgumentNullException(); + return null; } - var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); + var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) { - fusionConfigurationValidationErrors.Add(Deserialize_NonNullableIFusionConfigurationValidationErrorData(child)); + commitFusionConfigurationPublishErrors.Add(Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(child)); } - return fusionConfigurationValidationErrors; + return commitFusionConfigurationPublishErrors; } - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData Deserialize_NonNullableIFusionConfigurationValidationErrorData(global::System.Text.Json.JsonElement? obj) + private global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9790,40 +9805,25 @@ public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData } var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("McpFeatureCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("OpenApiCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("PersistedQueryValidationError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("SchemaVersionChangeViolationError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } - if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) + if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); + return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); } throw new global::System.NotSupportedException(); } - private global::System.Int32 Deserialize_NonNullableInt32(global::System.Text.Json.JsonElement? obj) + private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) { if (!obj.HasValue) { @@ -9835,68 +9835,31 @@ public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultData throw new global::System.ArgumentNullException(); } - return _intParser.Parse(obj.Value.GetInt32()!); + return _stringParser.Parse(obj.Value.GetString()!); } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UploadFusionSubgraphPayloadData + internal partial record StartFusionConfigurationCompositionPayloadData { - public UploadFusionSubgraphPayloadData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? fusionSubgraphVersion = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + public StartFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - FusionSubgraphVersion = fusionSubgraphVersion; Errors = errors; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? FusionSubgraphVersion { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionSubgraphVersionData - { - public FusionSubgraphVersionData(global::System.String __typename, global::System.String? id = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Id = id; - } - - public global::System.String __typename { get; init; } - public global::System.String? Id { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadFusionSubgraphErrorData - { - global::System.String __typename { get; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IErrorData + internal partial interface IApproveDeploymentErrorData { global::System.String __typename { get; } } - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidFusionSourceSchemaArchiveErrorData : IUploadFusionSubgraphErrorData, IErrorData - { - public InvalidFusionSourceSchemaArchiveErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] internal partial interface IBeginFusionConfigurationPublishErrorData @@ -9906,441 +9869,371 @@ internal partial interface IBeginFusionConfigurationPublishErrorData // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateApiKeyErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateApiKeyForApiErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateMcpFeatureCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateMockSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateOpenApiCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteApiByIdErrorData + internal partial interface ICancelDeploymentErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IForceDeleteStageByApiIdErrorData + internal partial interface ICancelFusionConfigurationCompositionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishSchemaErrorData + internal partial interface ICommitFusionConfigurationPublishErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateApiSettingsErrorData + internal partial interface ICreateAccountErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateStagesErrorData + internal partial interface ICreateApiKeyErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadSchemaErrorData + internal partial interface ICreateApiKeyForApiErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateSchemaErrorData + internal partial interface ICreateClientErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ApiNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, IDeleteApiByIdErrorData, IForceDeleteStageByApiIdErrorData, IPublishSchemaErrorData, IUpdateApiSettingsErrorData, IUpdateStagesErrorData, IUploadFusionSubgraphErrorData, IUploadSchemaErrorData, IValidateSchemaErrorData, IErrorData - { - public ApiNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUnpublishClientErrorData + internal partial interface ICreateMcpFeatureCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadClientErrorData + internal partial interface ICreateMockSchemaErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadMcpFeatureCollectionErrorData + internal partial interface ICreateOpenApiCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadOpenApiCollectionErrorData + internal partial interface ICreatePersonalAccessTokenErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionPublishErrorData + internal partial interface ICreateWorkspaceErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionPublishErrorData + internal partial interface IDeleteApiByIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationPublishingErrorData + internal partial interface IDeleteApiKeyErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionPublishErrorData + internal partial interface IDeleteClientByIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionPublishErrorData + internal partial interface IDeleteMcpFeatureCollectionByIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IProcessingErrorData + internal partial interface IDeleteMockSchemaByIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ConcurrentOperationErrorData : IUnpublishClientErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData, ISchemaVersionPublishErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionPublishErrorData, IProcessingErrorData - { - public ConcurrentOperationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IApproveDeploymentErrorData + internal partial interface IDeleteOpenApiCollectionByIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICancelDeploymentErrorData + internal partial interface IEnsureTunnelSessionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICancelFusionConfigurationCompositionErrorData + internal partial interface IForceDeleteStageByApiIdErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICommitFusionConfigurationPublishErrorData + internal partial interface IPollClientVersionPublishRequestErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateAccountErrorData + internal partial interface IPollClientVersionValidationRequestErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreatePersonalAccessTokenErrorData + internal partial interface IPollSchemaVersionPublishRequestErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateWorkspaceErrorData + internal partial interface IPollSchemaVersionValidationRequestErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteApiKeyErrorData + internal partial interface IPublishClientErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteClientByIdErrorData + internal partial interface IPublishMcpFeatureCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteMcpFeatureCollectionByIdErrorData + internal partial interface IPublishOpenApiCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteMockSchemaByIdErrorData + internal partial interface IPublishSchemaErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteOpenApiCollectionByIdErrorData + internal partial interface IPushDocumentChangesErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IEnsureTunnelSessionErrorData + internal partial interface IPushWorkspaceChangesErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollClientVersionPublishRequestErrorData + internal partial interface IRemoveWorkspaceErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollClientVersionValidationRequestErrorData + internal partial interface IRenameWorkspaceErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollSchemaVersionPublishRequestErrorData + internal partial interface IRevokePersonalAccessTokenErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollSchemaVersionValidationRequestErrorData + internal partial interface ISetActiveWorkspaceErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishClientErrorData + internal partial interface IStartFusionConfigurationCompositionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishMcpFeatureCollectionErrorData + internal partial interface IUnpublishClientErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishOpenApiCollectionErrorData + internal partial interface IUpdateApiSettingsErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPushDocumentChangesErrorData + internal partial interface IUpdateFeatureFlagsErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPushWorkspaceChangesErrorData + internal partial interface IUpdateMockSchemaErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRemoveWorkspaceErrorData + internal partial interface IUpdatePreferencesErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRenameWorkspaceErrorData + internal partial interface IUpdateStageCompositionSettingsErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRevokePersonalAccessTokenErrorData + internal partial interface IUpdateThemeSettingsErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISetActiveWorkspaceErrorData + internal partial interface IUploadClientErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IStartFusionConfigurationCompositionErrorData + internal partial interface IUploadFusionSubgraphErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateFeatureFlagsErrorData + internal partial interface IUploadMcpFeatureCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateMockSchemaErrorData + internal partial interface IUploadOpenApiCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdatePreferencesErrorData + internal partial interface IUploadSchemaErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateStageCompositionSettingsErrorData + internal partial interface IValidateClientErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateThemeSettingsErrorData + internal partial interface IValidateFusionConfigurationCompositionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateClientErrorData + internal partial interface IValidateMcpFeatureCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateFusionConfigurationCompositionErrorData + internal partial interface IValidateOpenApiCollectionErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateMcpFeatureCollectionErrorData + internal partial interface IValidateSchemaErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateOpenApiCollectionErrorData + internal partial interface IErrorData { global::System.String __typename { get; } } @@ -10361,9 +10254,9 @@ internal partial record UnauthorizedOperationData : IApproveDeploymentErrorData, // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record DuplicatedTagErrorData : IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData + internal partial record FusionConfigurationRequestNotFoundErrorData : ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData { - public DuplicatedTagErrorData(global::System.String __typename, global::System.String? message = default !) + public FusionConfigurationRequestNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10375,9 +10268,9 @@ internal partial record DuplicatedTagErrorData : IUploadClientErrorData, IUpload // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidSourceMetadataInputErrorData : IBeginFusionConfigurationPublishErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData + internal partial record InvalidProcessingStateTransitionErrorData : IBeginFusionConfigurationPublishErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData { - public InvalidSourceMetadataInputErrorData(global::System.String __typename, global::System.String? message = default !) + public InvalidProcessingStateTransitionErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10389,160 +10282,190 @@ internal partial record InvalidSourceMetadataInputErrorData : IBeginFusionConfig // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record BeginFusionConfigurationPublishPayloadData + internal partial interface IFusionConfigurationPublishingResultData { - public BeginFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.String? requestId = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record FusionConfigurationPublishingFailedData : IFusionConfigurationPublishingResultData + { + public FusionConfigurationPublishingFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - RequestId = requestId; + State = state; Errors = errors; } public global::System.String __typename { get; init; } - public global::System.String? RequestId { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StageNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, IForceDeleteStageByApiIdErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUnpublishClientErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateStagesErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData + internal partial record FusionConfigurationPublishingSuccessData : IFusionConfigurationPublishingResultData { - public StageNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) + public FusionConfigurationPublishingSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + State = state; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record SubgraphInvalidErrorData : IBeginFusionConfigurationPublishErrorData, IErrorData + internal partial record FusionConfigurationValidationFailedData : IFusionConfigurationPublishingResultData { - public SubgraphInvalidErrorData(global::System.String __typename, global::System.String? message = default !) + public FusionConfigurationValidationFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + State = state; + Errors = errors; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidProcessingStateTransitionErrorData : IBeginFusionConfigurationPublishErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData + internal partial record FusionConfigurationValidationSuccessData : IFusionConfigurationPublishingResultData { - public InvalidProcessingStateTransitionErrorData(global::System.String __typename, global::System.String? message = default !) + public FusionConfigurationValidationSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + State = state; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StartFusionConfigurationCompositionPayloadData + internal partial interface ISchemaVersionValidationResultData { - public StartFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } + global::System.String __typename { get; } + } - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface ISchemaVersionPublishResultData + { + global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationRequestNotFoundErrorData : ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData + internal partial interface IClientVersionValidationResultData { - public FusionConfigurationRequestNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } + global::System.String __typename { get; } + } - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionPublishResultData + { + global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CancelFusionConfigurationCompositionPayloadData + internal partial interface IOpenApiCollectionVersionPublishResultData { - public CancelFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } + global::System.String __typename { get; } + } - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionValidationResultData + { + global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ValidateFusionConfigurationCompositionPayloadData + internal partial interface IMcpFeatureCollectionVersionPublishResultData { - public ValidateFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionValidationResultData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record OperationInProgressData : ISchemaVersionValidationResultData, ISchemaVersionPublishResultData, IClientVersionValidationResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionPublishResultData, IMcpFeatureCollectionVersionValidationResultData + { + public OperationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; + State = state; } public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CommitFusionConfigurationPublishPayloadData + internal partial record ProcessingTaskApprovedData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData { - public CommitFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + public ProcessingTaskApprovedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; + State = state; } public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationPublishingResultData + internal partial record ProcessingTaskIsQueuedData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData { - global::System.String __typename { get; } + public ProcessingTaskIsQueuedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Int32? queuePosition = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + State = state; + QueuePosition = queuePosition; + } + + public global::System.String __typename { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.Int32? QueuePosition { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationPublishingFailedData : IFusionConfigurationPublishingResultData + internal partial record ProcessingTaskIsReadyData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData { - public FusionConfigurationPublishingFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + public ProcessingTaskIsReadyData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); State = state; - Errors = errors; } public global::System.String __typename { get; init; } public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationPublishingSuccessData : IFusionConfigurationPublishingResultData + internal partial record ValidationInProgressData : IFusionConfigurationPublishingResultData, IClientVersionValidationResultData, ISchemaVersionValidationResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionValidationResultData { - public FusionConfigurationPublishingSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + public ValidationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); State = state; @@ -10554,244 +10477,312 @@ internal partial record FusionConfigurationPublishingSuccessData : IFusionConfig // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationValidationFailedData : IFusionConfigurationPublishingResultData + internal partial record WaitForApprovalData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData { - public FusionConfigurationValidationFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + public WaitForApprovalData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); State = state; - Errors = errors; } public global::System.String __typename { get; init; } public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationValidationSuccessData : IFusionConfigurationPublishingResultData + internal partial interface ISchemaVersionPublishErrorData { - public FusionConfigurationValidationSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IClientVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IFusionConfigurationPublishingErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IOpenApiCollectionVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IMcpFeatureCollectionVersionPublishErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial interface IProcessingErrorData + { + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record ConcurrentOperationErrorData : IUnpublishClientErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData, ISchemaVersionPublishErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionPublishErrorData, IProcessingErrorData + { + public ConcurrentOperationErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionValidationResultData + internal partial interface IFusionConfigurationDeploymentErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionPublishResultData + internal partial interface ISchemaDeploymentErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionValidationResultData + internal partial interface ISchemaVersionValidationErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionPublishResultData + internal partial interface IFusionConfigurationValidationErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionPublishResultData + internal partial record InvalidGraphQLSchemaErrorData : IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData { - global::System.String __typename { get; } + public InvalidGraphQLSchemaErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionValidationResultData + internal partial interface IClientVersionValidationErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionPublishResultData + internal partial interface IOpenApiCollectionVersionValidationErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionValidationResultData + internal partial interface IMcpFeatureCollectionVersionValidationErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record OperationInProgressData : ISchemaVersionValidationResultData, ISchemaVersionPublishResultData, IClientVersionValidationResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionPublishResultData, IMcpFeatureCollectionVersionValidationResultData + internal partial record ProcessingTimeoutErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData { - public OperationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + public ProcessingTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskApprovedData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + internal partial record ReadyTimeoutErrorData : IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData { - public ProcessingTaskApprovedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + public ReadyTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskIsQueuedData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + internal partial record UnexpectedProcessingErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData { - public ProcessingTaskIsQueuedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Int32? queuePosition = default !) + public UnexpectedProcessingErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - QueuePosition = queuePosition; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Int32? QueuePosition { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskIsReadyData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + internal partial interface IMcpFeatureCollectionDeploymentErrorData { - public ProcessingTaskIsReadyData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ValidationInProgressData : IFusionConfigurationPublishingResultData, IClientVersionValidationResultData, ISchemaVersionValidationResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionValidationResultData + internal partial record McpFeatureCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IMcpFeatureCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData { - public ValidationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + public McpFeatureCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record WaitForApprovalData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData + internal partial interface IOpenApiCollectionDeploymentErrorData { - public WaitForApprovalData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) + global::System.String __typename { get; } + } + + // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator + [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] + internal partial record OpenApiCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IOpenApiCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + { + public OpenApiCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; + Message = message; } public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationDeploymentErrorData + internal partial interface IClientDeploymentErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaDeploymentErrorData + internal partial record PersistedQueryValidationErrorData : IClientDeploymentErrorData, IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData { - global::System.String __typename { get; } - } + public PersistedQueryValidationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionValidationErrorData - { - global::System.String __typename { get; } + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationValidationErrorData + internal partial record SchemaVersionChangeViolationErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData { - global::System.String __typename { get; } + public SchemaVersionChangeViolationErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidGraphQLSchemaErrorData : IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + internal partial record UploadFusionSubgraphPayloadData { - public InvalidGraphQLSchemaErrorData(global::System.String __typename, global::System.String? message = default !) + public UploadFusionSubgraphPayloadData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? fusionSubgraphVersion = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + FusionSubgraphVersion = fusionSubgraphVersion; + Errors = errors; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? FusionSubgraphVersion { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionValidationErrorData + internal partial record FusionSubgraphVersionData { - global::System.String __typename { get; } + public FusionSubgraphVersionData(global::System.String __typename, global::System.String? id = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Id = id; + } + + public global::System.String __typename { get; init; } + public global::System.String? Id { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionValidationErrorData + internal partial record InvalidFusionSourceSchemaArchiveErrorData : IUploadFusionSubgraphErrorData, IErrorData { - global::System.String __typename { get; } + public InvalidFusionSourceSchemaArchiveErrorData(global::System.String __typename, global::System.String? message = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Message = message; + } + + public global::System.String __typename { get; init; } + public global::System.String? Message { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionValidationErrorData + internal partial interface IUpdateStagesErrorData { global::System.String __typename { get; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTimeoutErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + internal partial record ApiNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, IDeleteApiByIdErrorData, IForceDeleteStageByApiIdErrorData, IPublishSchemaErrorData, IUpdateApiSettingsErrorData, IUpdateStagesErrorData, IUploadFusionSubgraphErrorData, IUploadSchemaErrorData, IValidateSchemaErrorData, IErrorData { - public ProcessingTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) + public ApiNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10803,9 +10794,9 @@ internal partial record ProcessingTimeoutErrorData : ISchemaVersionValidationErr // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ReadyTimeoutErrorData : IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + internal partial record DuplicatedTagErrorData : IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData { - public ReadyTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) + public DuplicatedTagErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10817,9 +10808,9 @@ internal partial record ReadyTimeoutErrorData : IClientVersionPublishErrorData, // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UnexpectedProcessingErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData + internal partial record InvalidSourceMetadataInputErrorData : IBeginFusionConfigurationPublishErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData { - public UnexpectedProcessingErrorData(global::System.String __typename, global::System.String? message = default !) + public InvalidSourceMetadataInputErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10831,16 +10822,25 @@ internal partial record UnexpectedProcessingErrorData : ISchemaVersionValidation // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionDeploymentErrorData + internal partial record BeginFusionConfigurationPublishPayloadData { - global::System.String __typename { get; } + public BeginFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.String? requestId = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + RequestId = requestId; + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.String? RequestId { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record McpFeatureCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IMcpFeatureCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + internal partial record StageNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, IForceDeleteStageByApiIdErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUnpublishClientErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateStagesErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData { - public McpFeatureCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) + public StageNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10852,16 +10852,9 @@ internal partial record McpFeatureCollectionValidationErrorData : IFusionConfigu // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record OpenApiCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IOpenApiCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + internal partial record SubgraphInvalidErrorData : IBeginFusionConfigurationPublishErrorData, IErrorData { - public OpenApiCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) + public SubgraphInvalidErrorData(global::System.String __typename, global::System.String? message = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); Message = message; @@ -10873,37 +10866,44 @@ internal partial record OpenApiCollectionValidationErrorData : IFusionConfigurat // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientDeploymentErrorData + internal partial record CancelFusionConfigurationCompositionPayloadData { - global::System.String __typename { get; } + public CancelFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) + { + this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); + Errors = errors; + } + + public global::System.String __typename { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record PersistedQueryValidationErrorData : IClientDeploymentErrorData, IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + internal partial record ValidateFusionConfigurationCompositionPayloadData { - public PersistedQueryValidationErrorData(global::System.String __typename, global::System.String? message = default !) + public ValidateFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + Errors = errors; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record SchemaVersionChangeViolationErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData + internal partial record CommitFusionConfigurationPublishPayloadData { - public SchemaVersionChangeViolationErrorData(global::System.String __typename, global::System.String? message = default !) + public CommitFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) { this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; + Errors = errors; } public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } + public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } } // StrawberryShake.CodeGeneration.CSharp.Generators.NoStoreAccessorGenerator @@ -10941,20 +10941,20 @@ internal static partial class FusionApiClientServiceCollectionExtensions return new ClientServiceProvider(global::Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(serviceCollection)); }); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::ChilliCream.Nitro.Fusion.Transport.State.FusionApiClientStoreAccessor()); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); return new global::StrawberryShake.ClientBuilder("FusionApiClient", services, serviceCollection); @@ -10994,6 +10994,7 @@ internal static partial class FusionApiClientServiceCollectionExtensions global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); @@ -11001,11 +11002,26 @@ internal static partial class FusionApiClientServiceCollectionExtensions global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::StrawberryShake.Serialization.SerializerResolver(global::System.Linq.Enumerable.Concat(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(parentServices), global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentResultFactory>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentBuilder>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); + global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSourceSchemaResultFactory>(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); @@ -11022,14 +11038,6 @@ internal static partial class FusionApiClientServiceCollectionExtensions global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ReleaseFusionDeploymentResultFactory>(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); @@ -11054,14 +11062,6 @@ internal static partial class FusionApiClientServiceCollectionExtensions global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); return services; diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs index cc27966bca6..58ce0a09637 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs @@ -7,7 +7,7 @@ public interface IFusionDeploymentWorkflow { /// /// Downloads an exact immutable source schema version and computes its canonical content - /// identity. + /// identity. The caller owns a non-null result and must dispose it after use. /// Task DownloadSourceSchemaAsync( FusionTarget target, @@ -27,6 +27,6 @@ Task ReconcileSourceSchemaAsync( /// Task PublishAsync( FusionPublicationRequest request, - string fusionArchivePath, + ReadOnlyMemory fusionArchive, CancellationToken cancellationToken); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql new file mode 100644 index 00000000000..3be7fb47089 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql @@ -0,0 +1,9 @@ +mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { + beginFusionConfigurationPublish(input: $input) { + requestId + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql new file mode 100644 index 00000000000..2707e57c615 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql @@ -0,0 +1,8 @@ +mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { + startFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql new file mode 100644 index 00000000000..a0c34923a19 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql @@ -0,0 +1,8 @@ +mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { + commitFusionConfigurationPublish(input: $input) { + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql deleted file mode 100644 index 57dce1b629e..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionDeployment.graphql +++ /dev/null @@ -1,65 +0,0 @@ -mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - beginFusionConfigurationPublish(input: $input) { - requestId - errors { - __typename - ...FusionError - } - } -} - -mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { - startFusionConfigurationComposition(input: $input) { - errors { - __typename - ...FusionError - } - } -} - -mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { - cancelFusionConfigurationComposition(input: $input) { - errors { - __typename - ...FusionError - } - } -} - -mutation ValidateFusionDeployment($input: ValidateFusionConfigurationCompositionInput!) { - validateFusionConfigurationComposition(input: $input) { - errors { - __typename - ...FusionError - } - } -} - -mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { - commitFusionConfigurationPublish(input: $input) { - errors { - __typename - ...FusionError - } - } -} - -subscription WatchFusionDeployment($requestId: ID!) { - onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - __typename - state - ... on ProcessingTaskIsQueued { - queuePosition - } - ... on FusionConfigurationPublishingFailed { - errors { - message - } - } - ... on FusionConfigurationValidationFailed { - errors { - message - } - } - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/Fragments.graphql rename to src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql new file mode 100644 index 00000000000..5ca2038c29b --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql @@ -0,0 +1,8 @@ +mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { + cancelFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/UploadFusionSourceSchema.graphql similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionSourceSchema.graphql rename to src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/UploadFusionSourceSchema.graphql diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql new file mode 100644 index 00000000000..40c0c6a8ca7 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql @@ -0,0 +1,8 @@ +mutation ValidateFusionDeployment($input: ValidateFusionConfigurationCompositionInput!) { + validateFusionConfigurationComposition(input: $input) { + errors { + __typename + ...FusionError + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql new file mode 100644 index 00000000000..f184504cfa2 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql @@ -0,0 +1,19 @@ +subscription WatchFusionDeployment($requestId: ID!) { + onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { + __typename + state + ... on ProcessingTaskIsQueued { + queuePosition + } + ... on FusionConfigurationPublishingFailed { + errors { + message + } + } + ... on FusionConfigurationValidationFailed { + errors { + message + } + } + } +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs index 76b1a1a682f..81abb62f7ea 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs @@ -1,9 +1,11 @@ +using System.Buffers; using System.Net; using System.Net.Http.Headers; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Microsoft.Extensions.DependencyInjection; using StrawberryShake; @@ -11,6 +13,8 @@ namespace ChilliCream.Nitro.Fusion.Transport; internal sealed class FusionApiTransportFactory : IFusionDeploymentTransportFactory { + internal const int MaxSourceSchemaArchiveSize = 128_000_000; + public ValueTask OpenAsync( FusionTarget target, CancellationToken cancellationToken) @@ -81,7 +85,10 @@ private sealed class FusionApiTransport( Uri.EscapeDataString(name), Uri.EscapeDataString(version)); using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); - using var response = await httpClient.SendAsync(request, cancellationToken); + using var response = await httpClient.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); if (response.StatusCode is HttpStatusCode.NotFound) { @@ -89,7 +96,10 @@ private sealed class FusionApiTransport( } EnsureSuccess(response); - return await response.Content.ReadAsByteArrayAsync(cancellationToken); + return await ReadSourceSchemaArchiveAsync( + response.Content, + MaxSourceSchemaArchiveSize, + cancellationToken); } public async Task UploadSourceSchemaAsync( @@ -197,15 +207,15 @@ public async Task ReleasePublishAsync( public async Task ValidatePublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken) { - await using var archive = File.OpenRead(archivePath); + await using var archiveStream = OpenRead(archive); var result = await apiClient.ValidateFusionDeployment.ExecuteAsync( new ValidateFusionConfigurationCompositionInput { RequestId = requestId, - Configuration = new Upload(archive, "gateway.far") + Configuration = new Upload(archiveStream, "gateway.far") }, cancellationToken); var data = EnsureData(result).ValidateFusionConfigurationComposition; @@ -214,21 +224,38 @@ public async Task ValidatePublishAsync( public async Task CommitPublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken) { - await using var archive = File.OpenRead(archivePath); + await using var archiveStream = OpenRead(archive); var result = await apiClient.CommitFusionDeployment.ExecuteAsync( new CommitFusionConfigurationPublishInput { RequestId = requestId, - Configuration = new Upload(archive, "gateway.far") + Configuration = new Upload(archiveStream, "gateway.far") }, cancellationToken); var data = EnsureData(result).CommitFusionConfigurationPublish; return ToCommandResult(data.Errors); } + private static Stream OpenRead(ReadOnlyMemory content) + { + if (MemoryMarshal.TryGetArray( + content, + out ArraySegment segment)) + { + return new MemoryStream( + segment.Array!, + segment.Offset, + segment.Count, + writable: false, + publiclyVisible: true); + } + + return new MemoryStream(content.ToArray(), writable: false); + } + public async ValueTask DisposeAsync() { httpClient.Dispose(); @@ -320,4 +347,115 @@ private static void EnsureSuccess(HttpResponseMessage response) } } } + + internal static Task ReadSourceSchemaArchiveAsync( + HttpContent content, + int maxArchiveSize, + CancellationToken cancellationToken) + => ReadSourceSchemaArchiveAsync( + content, + maxArchiveSize, + ArrayPool.Shared, + 81920, + cancellationToken); + + internal static async Task ReadSourceSchemaArchiveAsync( + HttpContent content, + int maxArchiveSize, + ArrayPool bufferPool, + int bufferSize, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(content); + ArgumentNullException.ThrowIfNull(bufferPool); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArchiveSize); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize); + + if (content.Headers.ContentLength is > 0 and var contentLength + && contentLength > maxArchiveSize) + { + throw ArchiveResponseTooLarge(maxArchiveSize); + } + + await using var stream = await content.ReadAsStreamAsync(cancellationToken); + var chunks = new List<(byte[] Buffer, int Length)>(); + var length = 0; + + try + { + var endOfStream = false; + while (!endOfStream) + { + var remaining = maxArchiveSize - length; + var readSize = remaining >= bufferSize + ? bufferSize + : remaining + 1; + var buffer = bufferPool.Rent(readSize); + var bufferLength = 0; + var retained = false; + + try + { + while (bufferLength < readSize) + { + var bytesRead = await stream.ReadAsync( + buffer.AsMemory(bufferLength, readSize - bufferLength), + cancellationToken); + + if (bytesRead is 0) + { + endOfStream = true; + break; + } + + bufferLength += bytesRead; + } + + if (bufferLength is 0) + { + continue; + } + + if (bufferLength > maxArchiveSize - length) + { + throw ArchiveResponseTooLarge(maxArchiveSize); + } + + length += bufferLength; + chunks.Add((buffer, bufferLength)); + retained = true; + } + finally + { + if (!retained) + { + bufferPool.Return(buffer, clearArray: true); + } + } + } + + var archive = GC.AllocateUninitializedArray(length); + var offset = 0; + foreach (var chunk in chunks) + { + chunk.Buffer.AsSpan(0, chunk.Length).CopyTo(archive.AsSpan(offset)); + offset += chunk.Length; + } + + return archive; + } + finally + { + foreach (var chunk in chunks) + { + bufferPool.Return(chunk.Buffer, clearArray: true); + } + } + } + + private static FusionDeploymentException ArchiveResponseTooLarge( + int maxArchiveSize) + => new( + "The compressed Fusion source schema archive exceeds the maximum " + + $"allowed size of {maxArchiveSize} bytes."); } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs index 09d08dcb87a..5a22f5792ed 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs @@ -9,6 +9,7 @@ ValueTask OpenAsync( internal interface IFusionDeploymentTransport : IAsyncDisposable { + // The caller owns a non-null returned buffer and must clear it after use. Task DownloadSourceSchemaAsync( string name, string version, @@ -37,12 +38,12 @@ Task ReleasePublishAsync( Task ValidatePublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken); Task CommitPublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken); } diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs index a99f8edb955..09c60ffd31d 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs @@ -422,10 +422,64 @@ await File.WriteAllTextAsync( TestContext.Current.CancellationToken)); Assert.Equal( - "The composed Fusion archive SHA-256 does not match prepared apply state.", + "The composed Fusion archive SHA-256 does not match prepared state.", exception.Message); } + [Fact] + public void BoundedMemoryStream_Should_RejectWritesBeyondConfiguredLimit() + { + using var stream = new BoundedMemoryStream(3); + + stream.Write(new byte[] { 1, 2, 3 }); + var exception = Assert.Throws( + () => stream.WriteByte(4)); + + Assert.Equal( + "The composed Fusion archive exceeds the 3-byte in-memory size limit.", + exception.Message); + Assert.Equal(3, stream.Length); + } + + [Fact] + public void BoundedMemoryStream_Should_ClearBackingBuffer_WhenDisposed() + { + var stream = new BoundedMemoryStream(3); + stream.Write(new byte[] { 1, 2, 3 }); + var buffer = stream.GetBuffer(); + + stream.Dispose(); + + Assert.Equal(new byte[buffer.Length], buffer); + } + + [Fact] + public void TransferComposition_Should_ClearArchive_When_CanceledBeforeTransfer() + { + using var state = new FusionDeploymentSessionState( + "release-1", + "https://api.chillicream.com", + "products", + []); + byte[] fusionArchive = [1, 2, 3]; + var clearedBuffers = new List(); + var executor = new FusionPipelineExecutor( + bufferCleared: clearedBuffers.Add); + using var cancellation = new CancellationTokenSource(); + cancellation.Cancel(); + + Assert.Throws( + () => executor.TransferComposition( + state, + "development", + fusionArchive, + cancellation.Token)); + + Assert.Same(fusionArchive, Assert.Single(clearedBuffers)); + Assert.Equal(new byte[3], fusionArchive); + Assert.Throws(() => state.FusionArchive); + } + [Fact] public void GetTransportEndpoint_Should_Fail_WhenProductionBindingIsMissing() { diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs index 2ecddea7454..f7c2746065a 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -5,6 +5,7 @@ using HotChocolate.Fusion; using HotChocolate.Fusion.Aspire; using HotChocolate.Fusion.Packaging; +using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -83,20 +84,23 @@ await File.WriteAllTextAsync( var runnerBProjects = await CreateAppHostProjectStubsAsync(runnerB); var runnerCProjects = await CreateAppHostProjectStubsAsync(runnerC); + var runnerBTree = SnapshotTree(runnerB); + var runnerCTree = SnapshotTree(runnerC); Directory.Delete(sourceCheckout, recursive: true); Assert.False(Directory.Exists(sourceCheckout)); + var stagingModel = CreateModel( + runnerBProjects.ProductsProjectPath, + runnerBProjects.ReviewsProjectPath, + runnerBProjects.GatewayProjectPath); var stagingContext = CreateContext( - CreateModel( - runnerBProjects.ProductsProjectPath, - runnerBProjects.ReviewsProjectPath, - runnerBProjects.GatewayProjectPath), + stagingModel, "Development", - Path.Combine(runnerB, "apply-output"), - workflow); - await executor.DownloadAsync(stagingContext); - await executor.ComposeAsync(stagingContext); - await executor.PublishAsync(stagingContext); + outputPath: null, + workflow: workflow); + using var stagingSession = new FusionPipelineSession( + stagingContext.CancellationToken); + await executor.PreflightAsync(stagingContext, stagingSession); var productionContext = CreateContext( CreateModel( @@ -104,18 +108,48 @@ await File.WriteAllTextAsync( runnerCProjects.ReviewsProjectPath, runnerCProjects.GatewayProjectPath), "Test", - Path.Combine(runnerC, "different-apply-output"), - workflow); - await executor.DownloadAsync(productionContext); - await executor.ComposeAsync(productionContext); - await executor.PublishAsync(productionContext); + outputPath: null, + workflow: workflow); + using var productionSession = new FusionPipelineSession( + productionContext.CancellationToken); + await executor.PreflightAsync(productionContext, productionSession); + + Assert.Equal(1, stagingSession.DeploymentCount); + Assert.Equal(1, productionSession.DeploymentCount); + var stagingDeployment = Assert.Single( + FusionPipeline.SelectDeployments( + stagingModel, + "Development")); + using (var lease = stagingSession.Acquire(stagingDeployment)) + { + Assert.Equal(0, lease.State.SourceArchiveBytes); + Assert.Throws( + () => lease.State.Sources); + } + + await executor.DownloadAsync(stagingContext, stagingSession); + await executor.ComposeAsync(stagingContext, stagingSession); + await executor.DownloadAsync(productionContext, productionSession); + await executor.ComposeAsync(productionContext, productionSession); + await executor.PublishAsync(stagingContext, stagingSession); + + Assert.Equal(0, stagingSession.DeploymentCount); + Assert.Equal(1, productionSession.DeploymentCount); + + await executor.PublishAsync(productionContext, productionSession); + + Assert.Equal(0, productionSession.DeploymentCount); Assert.Equal(2, workflow.Reconciliations.Count); Assert.Equal( [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("products", "release-1"), new FusionSourceSchemaVersion("products", "release-1"), new FusionSourceSchemaVersion("products", "release-1"), new FusionSourceSchemaVersion("reviews", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1"), new FusionSourceSchemaVersion("reviews", "release-1") ], workflow.Downloads.OrderBy(source => source.Name)); @@ -139,6 +173,7 @@ await File.WriteAllTextAsync( new FusionSourceSchemaVersion("reviews", "release-1") ], development.Sources); + Assert.NotEmpty(development.FusionArchive); }, test => { @@ -156,16 +191,125 @@ await File.WriteAllTextAsync( new FusionSourceSchemaVersion("reviews", "release-1") ], test.Sources); + Assert.NotEmpty(test.FusionArchive); }); + Assert.NotEqual( + Convert.ToBase64String(workflow.Publications[0].FusionArchive), + Convert.ToBase64String(workflow.Publications[1].FusionArchive)); + Assert.Empty( + Directory.GetFiles( + runnerB, + "fusion-apply.json", + SearchOption.AllDirectories)); + Assert.Empty( + Directory.GetFiles( + runnerC, + "fusion-apply.json", + SearchOption.AllDirectories)); + Assert.Equal(runnerBTree, SnapshotTree(runnerB)); + Assert.Equal(runnerCTree, SnapshotTree(runnerC)); + + using var repeatedStagingSession = new FusionPipelineSession( + stagingContext.CancellationToken); + await executor.PreflightAsync(stagingContext, repeatedStagingSession); + await executor.DownloadAsync(stagingContext, repeatedStagingSession); + await executor.ComposeAsync(stagingContext, repeatedStagingSession); + await executor.PublishAsync(stagingContext, repeatedStagingSession); + + Assert.Equal(0, repeatedStagingSession.DeploymentCount); + Assert.Equal(3, workflow.Publications.Count); + Assert.Equal( + Convert.ToBase64String( + workflow.Publications[0].FusionArchive), + Convert.ToBase64String( + workflow.Publications[2].FusionArchive)); + Assert.Equal(runnerBTree, SnapshotTree(runnerB)); + + using var failingSession = new FusionPipelineSession( + stagingContext.CancellationToken); + await executor.PreflightAsync(stagingContext, failingSession); + await executor.DownloadAsync(stagingContext, failingSession); + await executor.ComposeAsync(stagingContext, failingSession); + byte[][] sourceBuffers; + byte[] farBuffer; + using (var lease = failingSession.Acquire(stagingDeployment)) + { + sourceBuffers = lease.State.Sources + .Select(source => source.Archive) + .ToArray(); + farBuffer = lease.State.FusionArchive; + } + workflow.ThrowOnPublish = true; + + var publishException = await Assert.ThrowsAsync( + () => executor.PublishAsync(stagingContext, failingSession)); + + Assert.Equal("Injected publication failure.", publishException.Message); + Assert.Equal(0, failingSession.DeploymentCount); + foreach (var sourceBuffer in sourceBuffers) + { + Assert.Equal(new byte[sourceBuffer.Length], sourceBuffer); + } + + Assert.Equal(new byte[farBuffer.Length], farBuffer); + Assert.Equal(runnerBTree, SnapshotTree(runnerB)); + + workflow.ThrowOnPublish = false; + using var publishCancellation = new CancellationTokenSource(); + var cancelingContext = CreateContext( + stagingModel, + "Development", + outputPath: null, + workflow: workflow, + cancellationToken: publishCancellation.Token); + using var cancelingSession = new FusionPipelineSession( + publishCancellation.Token); + await executor.PreflightAsync(cancelingContext, cancelingSession); + await executor.DownloadAsync(cancelingContext, cancelingSession); + await executor.ComposeAsync(cancelingContext, cancelingSession); + byte[][] cancelingSourceBuffers; + byte[] cancelingFarBuffer; + using (var lease = cancelingSession.Acquire(stagingDeployment)) + { + cancelingSourceBuffers = lease.State.Sources + .Select(source => source.Archive) + .ToArray(); + cancelingFarBuffer = lease.State.FusionArchive; + } + + workflow.BlockNextPublication(); + var cancelingPublish = executor.PublishAsync( + cancelingContext, + cancelingSession); + await workflow.WaitForBlockedPublicationAsync(); + + publishCancellation.Cancel(); + + Assert.All( + cancelingSourceBuffers, + buffer => Assert.Contains(buffer, value => value != 0)); + Assert.Contains(cancelingFarBuffer, value => value != 0); + + workflow.ContinueBlockedPublication(); + await Assert.ThrowsAnyAsync( + () => cancelingPublish); + + Assert.True(workflow.BlockedPublicationObservedStableBytes); + Assert.Equal(0, cancelingSession.DeploymentCount); + Assert.All( + cancelingSourceBuffers, + buffer => Assert.Equal(new byte[buffer.Length], buffer)); + Assert.Equal( + new byte[cancelingFarBuffer.Length], + cancelingFarBuffer); } [Fact] - public async Task Download_Should_FailWithoutApplyState_WhenExactSourceIsMissing() + public async Task Download_Should_ClearSession_WhenExactSourceIsMissing() { using var testDirectory = new TestDirectory(); var projects = await CreateAppHostProjectStubsAsync( testDirectory.Path); - var output = Path.Combine(testDirectory.Path, "apply-output"); var workflow = new RecordingFusionDeploymentWorkflow(); var context = CreateContext( CreateModel( @@ -173,27 +317,341 @@ public async Task Download_Should_FailWithoutApplyState_WhenExactSourceIsMissing projects.ReviewsProjectPath, projects.GatewayProjectPath), "Development", - output, - workflow); + outputPath: null, + workflow: workflow); + using var session = new FusionPipelineSession( + context.CancellationToken); var exception = await Assert.ThrowsAsync( - () => FusionPipelineExecutor.Instance.DownloadAsync(context)); + () => FusionPipelineExecutor.Instance.PreflightAsync( + context, + session)); Assert.Equal( "Fusion source 'products' version 'release-1' does not exist on target 'products'.", exception.Message); - Assert.False( - File.Exists( - Path.Combine( - output, - "fusion", - "apply", - "development", - "fusion-apply.json"))); + Assert.Equal(0, session.DeploymentCount); + Assert.Empty( + Directory.GetFiles( + testDirectory.Path, + "fusion-apply.json", + SearchOption.AllDirectories)); Assert.Empty(workflow.Reconciliations); Assert.Empty(workflow.Publications); } + [Fact] + public async Task Download_Should_RejectSourceArchive_WhenPerSourceLimitIsExceeded() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var workflow = new RecordingFusionDeploymentWorkflow(); + workflow.SeedSource( + "products", + "release-1", + new byte[] { 1, 2, 3 }); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + outputPath: null, + workflow: workflow); + var limits = new FusionPipelineMemoryLimits( + SourceArchiveBytes: 2, + TotalSourceArchiveBytes: 100, + FusionArchiveBytes: 100); + var executor = new FusionPipelineExecutor(limits); + using var session = new FusionPipelineSession( + context.CancellationToken, + limits); + + var exception = await Assert.ThrowsAsync( + () => executor.PreflightAsync(context, session)); + + Assert.Equal( + "Downloaded Fusion source 'products@release-1' exceeds the " + + "2-byte per-source in-memory size limit.", + exception.Message); + Assert.Equal(0, session.DeploymentCount); + } + + [Fact] + public async Task Download_Should_RejectSourceArchives_WhenAggregateLimitIsExceeded() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var workflow = new RecordingFusionDeploymentWorkflow(); + workflow.SeedSource( + "products", + "release-1", + new byte[] { 1, 2, 3 }); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + outputPath: null, + workflow: workflow); + var limits = new FusionPipelineMemoryLimits( + SourceArchiveBytes: 100, + TotalSourceArchiveBytes: 2, + FusionArchiveBytes: 100); + var executor = new FusionPipelineExecutor(limits); + using var session = new FusionPipelineSession( + context.CancellationToken, + limits); + + var exception = await Assert.ThrowsAsync( + () => executor.PreflightAsync(context, session)); + + Assert.Equal( + "The downloaded Fusion sources exceed the 2-byte aggregate " + + "in-memory size limit.", + exception.Message); + Assert.Equal(0, session.DeploymentCount); + } + + [Fact] + public async Task Download_Should_FailBeforeComposition_WhenExactSourceChangesAfterPreflight() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var workflow = new RecordingFusionDeploymentWorkflow(); + workflow.SeedSource( + await CreateSourceDownloadAsync( + "products", + "Product")); + workflow.SeedSource( + await CreateSourceDownloadAsync( + "reviews", + "Review")); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + outputPath: null, + workflow: workflow); + using var session = new FusionPipelineSession( + context.CancellationToken); + var executor = FusionPipelineExecutor.Instance; + await executor.PreflightAsync(context, session); + workflow.OverrideSource( + await CreateSourceDownloadAsync( + "products", + "ChangedProduct")); + + var exception = await Assert.ThrowsAsync( + () => executor.DownloadAsync(context, session)); + + Assert.Equal( + "Fusion source 'products@release-1' changed between preflight and composition.", + exception.Message); + Assert.Equal(0, session.DeploymentCount); + Assert.Empty(workflow.Publications); + } + + [Fact] + public async Task Download_Should_ClearPartialSources_WhenLaterSourceIsMissing() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var workflow = new RecordingFusionDeploymentWorkflow(); + workflow.SeedSource( + await CreateSourceDownloadAsync( + "products", + "Product")); + workflow.SeedSource( + await CreateSourceDownloadAsync( + "reviews", + "Review")); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + outputPath: null, + workflow: workflow); + using var session = new FusionPipelineSession( + context.CancellationToken); + await FusionPipelineExecutor.Instance.PreflightAsync( + context, + session); + workflow.RemoveSource("reviews", "release-1"); + var clearedBuffers = new List(); + var executor = new FusionPipelineExecutor( + bufferCleared: clearedBuffers.Add); + + var exception = await Assert.ThrowsAsync( + () => executor.DownloadAsync(context, session)); + + Assert.Equal( + "Fusion source 'reviews' version 'release-1' does not exist on target 'products'.", + exception.Message); + var clearedBuffer = Assert.Single(clearedBuffers); + Assert.Equal(new byte[clearedBuffer.Length], clearedBuffer); + Assert.Equal(0, session.DeploymentCount); + } + + [Fact] + public async Task Download_Should_ClearCurrentSource_WhenCanonicalDigestIsInvalid() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var workflow = new RecordingFusionDeploymentWorkflow(); + var products = await CreateSourceDownloadAsync( + "products", + "Product"); + var invalidProducts = new FusionSourceSchemaDownload( + products.Name, + products.Version, + products.Archive.ToArray(), + new string('0', 64)); + workflow.SeedSource(products); + workflow.SeedSource( + await CreateSourceDownloadAsync( + "reviews", + "Review")); + var context = CreateContext( + CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath), + "Development", + outputPath: null, + workflow: workflow); + using var session = new FusionPipelineSession( + context.CancellationToken); + await FusionPipelineExecutor.Instance.PreflightAsync( + context, + session); + workflow.OverrideSource( + invalidProducts); + var clearedBuffers = new List(); + var executor = new FusionPipelineExecutor( + bufferCleared: clearedBuffers.Add); + + var exception = await Assert.ThrowsAsync( + () => executor.DownloadAsync(context, session)); + + Assert.Equal( + "Downloaded Fusion source 'products@release-1' content does not match its " + + "canonical digest.", + exception.Message); + var clearedBuffer = Assert.Single(clearedBuffers); + Assert.Equal(new byte[clearedBuffer.Length], clearedBuffer); + Assert.Equal(0, session.DeploymentCount); + } + + [Fact] + public async Task Session_Should_RejectAndClearState_WhenCancellationPrecedesTransfer() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var model = CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath); + var deployment = Assert.Single( + FusionPipeline.SelectDeployments(model, "Development")); + using var cancellationSource = new CancellationTokenSource(); + using var session = new FusionPipelineSession( + cancellationSource.Token); + var sourceArchive = new byte[] { 1, 2, 3 }; + var state = new FusionDeploymentSessionState( + "release-1", + "https://api.chillicream.com", + "products", + [ + new FusionSessionSourceIdentity( + "products", + "release-1", + "digest") + ]); + state.SetSources( + [ + new FusionSessionSource( + "products", + "release-1", + sourceArchive, + "digest") + ]); + + cancellationSource.Cancel(); + + Assert.Throws( + () => session.SetAll([(deployment, state)])); + Assert.Equal(0, session.DeploymentCount); + Assert.Equal(new byte[3], sourceArchive); + } + + [Fact] + public async Task Session_Should_ClearOwnedBuffers_WhenCanceledAfterTransfer() + { + using var testDirectory = new TestDirectory(); + var projects = await CreateAppHostProjectStubsAsync( + testDirectory.Path); + var model = CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath); + var deployment = Assert.Single( + FusionPipeline.SelectDeployments(model, "Development")); + using var cancellationSource = new CancellationTokenSource(); + using var session = new FusionPipelineSession( + cancellationSource.Token); + var sourceArchive = new byte[] { 1, 2, 3 }; + var fusionArchive = new byte[] { 4, 5, 6 }; + var state = new FusionDeploymentSessionState( + "release-1", + "https://api.chillicream.com", + "products", + [ + new FusionSessionSourceIdentity( + "products", + "release-1", + "digest") + ]); + state.SetSources( + [ + new FusionSessionSource( + "products", + "release-1", + sourceArchive, + "digest") + ]); + state.SetComposition( + "development", + fusionArchive, + "far-digest"); + session.SetAll([(deployment, state)]); + + var lease = session.Acquire(deployment); + + cancellationSource.Cancel(); + + Assert.Equal(1, session.DeploymentCount); + Assert.Equal(new byte[] { 1, 2, 3 }, sourceArchive); + Assert.Equal(new byte[] { 4, 5, 6 }, fusionArchive); + + lease.Dispose(); + + Assert.Equal(0, session.DeploymentCount); + Assert.Equal(new byte[3], sourceArchive); + Assert.Equal(new byte[3], fusionArchive); + } + private static DistributedApplicationModel CreateModel( string productsProjectPath, string reviewsProjectPath, @@ -240,17 +698,20 @@ private static DistributedApplicationModel CreateModel( private static PipelineStepContext CreateContext( DistributedApplicationModel model, string environmentName, - string outputPath, - IFusionDeploymentWorkflow workflow) + string? outputPath, + IFusionDeploymentWorkflow workflow, + CancellationToken? cancellationToken = null) { var services = new ServiceCollection() .AddSingleton( new TestHostEnvironment(environmentName)) - .AddSingleton( - new TestPipelineOutputService(outputPath)) .AddSingleton(workflow) .AddSingleton( new ConfigurationBuilder().Build()); + services.AddSingleton( + outputPath is null + ? new ThrowingPipelineOutputService() + : new TestPipelineOutputService(outputPath)); var schemaCompositionType = typeof(GraphQLCompositionSettings).Assembly.GetType( "HotChocolate.Fusion.Aspire.SchemaComposition", @@ -268,7 +729,8 @@ private static PipelineStepContext CreateContext( DistributedApplicationOperation.Publish), serviceProvider, NullLogger.Instance, - TestContext.Current.CancellationToken); + cancellationToken + ?? TestContext.Current.CancellationToken); return new PipelineStepContext { @@ -332,6 +794,53 @@ await File.WriteAllTextAsync( return projectPath; } + private static async Task + CreateSourceDownloadAsync( + string sourceName, + string typeName) + { + await using var stream = new MemoryStream(); + using (var archive = FusionSourceSchemaArchive.Create( + stream, + leaveOpen: true)) + { + await archive.SetArchiveMetadataAsync( + new HotChocolate.Fusion.SourceSchema.Packaging.ArchiveMetadata(), + TestContext.Current.CancellationToken); + await archive.SetSchemaAsync( + System.Text.Encoding.UTF8.GetBytes( + $"type Query {{ value: {typeName} }} type {typeName} {{ id: ID! }}"), + TestContext.Current.CancellationToken); + using var settings = System.Text.Json.JsonDocument.Parse( + $$""" + { + "name": "{{sourceName}}", + "transports": { + "http": { + "url": "https://{{sourceName}}.example.com/graphql" + } + } + } + """); + await archive.SetSettingsAsync( + settings, + TestContext.Current.CancellationToken); + await archive.CommitAsync( + TestContext.Current.CancellationToken); + } + + var archiveBytes = stream.ToArray(); + var digest = await FusionSourceSchemaContent.ComputeSha256Async( + archiveBytes, + sourceName, + TestContext.Current.CancellationToken); + return new FusionSourceSchemaDownload( + sourceName, + "release-1", + archiveBytes, + digest); + } + private static async Task<( string ProductsProjectPath, string ReviewsProjectPath, @@ -394,12 +903,23 @@ await File.WriteAllTextAsync( gatewayProjectPath); } + private static IReadOnlyList SnapshotTree(string path) + => Directory.EnumerateFileSystemEntries( + path, + "*", + SearchOption.AllDirectories) + .Select(entry => Path.GetRelativePath(path, entry)) + .Order(StringComparer.Ordinal) + .ToArray(); + private sealed class RecordingFusionDeploymentWorkflow : IFusionDeploymentWorkflow { private readonly Dictionary< (string CloudUrl, string ApiId, string Name, string Version), - FusionSourceSchemaDownload> _sources = []; + StoredSourceDownload> _sources = []; + private TaskCompletionSource? _publishStarted; + private TaskCompletionSource? _continuePublish; public List Reconciliations { get; } = []; @@ -407,6 +927,80 @@ private readonly Dictionary< public List Publications { get; } = []; + public bool ThrowOnPublish { get; set; } + + public bool BlockedPublicationObservedStableBytes { get; private set; } + + public void BlockNextPublication() + { + _publishStarted = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + _continuePublish = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + BlockedPublicationObservedStableBytes = false; + } + + public Task WaitForBlockedPublicationAsync() + => _publishStarted?.Task + ?? throw new InvalidOperationException( + "No publication is blocked."); + + public void ContinueBlockedPublication() + => (_continuePublish + ?? throw new InvalidOperationException( + "No publication is blocked.")) + .TrySetResult(); + + public void SeedSource( + string name, + string version, + byte[] archive) + { + var target = new FusionTarget( + new Uri("https://api.chillicream.com"), + "products", + "test-api-key"); + _sources.Add( + CreateKey(target, name, version), + new StoredSourceDownload( + name, + version, + archive, + "unused-before-bound-validation")); + } + + public void SeedSource(FusionSourceSchemaDownload source) + { + var target = CreateTestTarget(); + _sources.Add( + CreateKey(target, source.Name, source.Version), + TakeOwnership(source)); + } + + public void OverrideSource(FusionSourceSchemaDownload source) + { + var target = CreateTestTarget(); + var key = CreateKey(target, source.Name, source.Version); + var replacement = TakeOwnership(source); + if (_sources.TryGetValue(key, out var previous)) + { + Array.Clear(previous.Archive); + } + + _sources[key] = replacement; + } + + public void RemoveSource(string name, string version) + { + var target = CreateTestTarget(); + if (_sources.Remove( + CreateKey(target, name, version), + out var removed)) + { + Array.Clear(removed.Archive); + } + } + public async Task ReconcileSourceSchemaAsync( FusionTarget target, FusionSourceSchemaUpload source, @@ -422,7 +1016,7 @@ await FusionSourceSchemaContent.ComputeSha256Async( cancellationToken); _sources.Add( CreateKey(target, source.Name, source.Version), - new FusionSourceSchemaDownload( + new StoredSourceDownload( source.Name, source.Version, archive, @@ -439,15 +1033,56 @@ await FusionSourceSchemaContent.ComputeSha256Async( _sources.TryGetValue( CreateKey(target, source.Name, source.Version), out var download); - return Task.FromResult(download); + return Task.FromResult( + download is null + ? null + : new FusionSourceSchemaDownload( + download.Name, + download.Version, + download.Archive.ToArray(), + download.ContentSha256)); + } + + private static StoredSourceDownload TakeOwnership( + FusionSourceSchemaDownload source) + { + using (source) + { + return new StoredSourceDownload( + source.Name, + source.Version, + source.Archive.ToArray(), + source.ContentSha256); + } } public async Task PublishAsync( FusionPublicationRequest request, - string fusionArchivePath, + ReadOnlyMemory fusionArchive, CancellationToken cancellationToken) { - using var archive = FusionArchive.Open(fusionArchivePath); + if (ThrowOnPublish) + { + throw new InvalidOperationException( + "Injected publication failure."); + } + + if (_publishStarted is not null + && _continuePublish is not null) + { + var beforeCancellation = fusionArchive.ToArray(); + _publishStarted.TrySetResult(); + await _continuePublish.Task; + BlockedPublicationObservedStableBytes = + fusionArchive.Span.SequenceEqual(beforeCancellation); + cancellationToken.ThrowIfCancellationRequested(); + } + + var fusionArchiveBytes = fusionArchive.ToArray(); + await using var fusionArchiveStream = new MemoryStream( + fusionArchiveBytes, + writable: false); + using var archive = FusionArchive.Open(fusionArchiveStream); using var configuration = await archive.TryGetGatewayConfigurationAsync( WellKnownVersions.LatestGatewayFormatVersion, @@ -471,7 +1106,8 @@ await archive.TryGetGatewayConfigurationAsync( new RecordedPublication( request.Stage, request.SourceSchemas.ToArray(), - sourceUrls)); + sourceUrls, + fusionArchiveBytes)); } private static ( @@ -487,12 +1123,42 @@ private static ( target.ApiId, name, version); + + private static FusionTarget CreateTestTarget() + => new( + new Uri("https://api.chillicream.com"), + "products", + "test-api-key"); + + private sealed record StoredSourceDownload( + string Name, + string Version, + byte[] Archive, + string ContentSha256); } private sealed record RecordedPublication( string Stage, IReadOnlyList Sources, - IReadOnlyDictionary SourceUrls); + IReadOnlyDictionary SourceUrls, + byte[] FusionArchive); + + private sealed class ThrowingPipelineOutputService + : IPipelineOutputService + { + private static InvalidOperationException CreateException() + => new("Publish-only Fusion steps must not resolve pipeline output directories."); + + public string GetOutputDirectory() => throw CreateException(); + + public string GetOutputDirectory(IResource resource) + => throw CreateException(); + + public string GetTempDirectory() => throw CreateException(); + + public string GetTempDirectory(IResource resource) + => throw CreateException(); + } private sealed class TestPipelineOutputService(string outputPath) : IPipelineOutputService diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs new file mode 100644 index 00000000000..1983162b686 --- /dev/null +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs @@ -0,0 +1,224 @@ +using System.Buffers; +using ChilliCream.Nitro.Fusion.Transport; + +namespace ChilliCream.Nitro.Fusion; + +public sealed class FusionApiTransportFactoryTests +{ + [Fact] + public async Task ReadSourceSchemaArchiveAsync_Should_RejectBody_When_DeclaredLengthIsTooLarge() + { + using var content = new ByteArrayContent([1]); + content.Headers.ContentLength = 17; + + var exception = await Assert.ThrowsAsync( + () => FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + content, + 16, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The compressed Fusion source schema archive exceeds the maximum " + + "allowed size of 16 bytes.", + exception.Message); + } + + [Fact] + public async Task ReadSourceSchemaArchiveAsync_Should_RejectBody_When_ChunkedBodyIsTooLarge() + { + await using var stream = new NonSeekableReadStream(new byte[17]); + using var content = new StreamContent(stream); + var bufferPool = new TrackingArrayPool(); + Assert.Null(content.Headers.ContentLength); + + var exception = await Assert.ThrowsAsync( + () => FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + content, + 16, + bufferPool, + 4, + TestContext.Current.CancellationToken)); + + Assert.Equal( + "The compressed Fusion source schema archive exceeds the maximum " + + "allowed size of 16 bytes.", + exception.Message); + AssertAllBuffersCleared(bufferPool); + } + + [Fact] + public async Task ReadSourceSchemaArchiveAsync_Should_Cancel_When_ReadIsCanceled() + { + await using var stream = new BlockingReadStream(); + using var content = new StreamContent(stream); + using var cancellation = new CancellationTokenSource(); + var bufferPool = new TrackingArrayPool(); + var readTask = FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + content, + 16, + bufferPool, + 4, + cancellation.Token); + + await stream.ReadStarted.Task.WaitAsync( + TimeSpan.FromSeconds(5), + TestContext.Current.CancellationToken); + await cancellation.CancelAsync(); + + await Assert.ThrowsAnyAsync(() => readTask); + AssertAllBuffersCleared(bufferPool); + } + + [Fact] + public async Task ReadSourceSchemaArchiveAsync_Should_ClearEveryRentedBuffer_When_BodyGrows() + { + byte[] expected = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + await using var stream = new NonSeekableReadStream(expected); + using var content = new StreamContent(stream); + var bufferPool = new TrackingArrayPool(); + + var archive = await FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + content, + 16, + bufferPool, + 4, + TestContext.Current.CancellationToken); + + Assert.Equal(expected, archive); + Assert.Equal(3, bufferPool.Rented.Count); + AssertAllBuffersCleared(bufferPool); + } + + private static void AssertAllBuffersCleared(TrackingArrayPool bufferPool) + { + Assert.Equal(bufferPool.Rented.Count, bufferPool.Returned.Count); + Assert.All(bufferPool.Returned, returned => Assert.True(returned.ClearArray)); + Assert.All( + bufferPool.Rented, + buffer => Assert.Equal(new byte[buffer.Length], buffer)); + } + + private sealed class NonSeekableReadStream(byte[] content) : Stream + { + private readonly MemoryStream _inner = new(content, writable: false); + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + => _inner.Read(buffer, offset, count); + + public override ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default) + => _inner.ReadAsync(buffer, cancellationToken); + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _inner.Dispose(); + } + + base.Dispose(disposing); + } + } + + private sealed class BlockingReadStream : Stream + { + public TaskCompletionSource ReadStarted { get; } = + new(TaskCreationOptions.RunContinuationsAsynchronously); + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + public override async ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default) + { + ReadStarted.TrySetResult(); + await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); + return 0; + } + + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); + + public override void SetLength(long value) + => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + } + + private sealed class TrackingArrayPool : ArrayPool + { + public List Rented { get; } = []; + + public List<(byte[] Buffer, bool ClearArray)> Returned { get; } = []; + + public override byte[] Rent(int minimumLength) + { + var buffer = new byte[minimumLength]; + Rented.Add(buffer); + return buffer; + } + + public override void Return(byte[] array, bool clearArray = false) + { + if (Returned.Any(returned => ReferenceEquals(returned.Buffer, array))) + { + throw new InvalidOperationException("The buffer was returned twice."); + } + + Returned.Add((array, clearArray)); + + if (clearArray) + { + array.AsSpan().Clear(); + } + } + } +} diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs index b31e09897eb..4b7ae155e97 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs +++ b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs @@ -37,6 +37,40 @@ public void Assembly_Should_ExposeOnlyWorkflowContract_When_Inspected() """); } + [Fact] + public void PublishAsync_Should_AcceptFusionArchiveFromMemory_When_Inspected() + { + typeof(IFusionDeploymentWorkflow) + .GetMethod(nameof(IFusionDeploymentWorkflow.PublishAsync))! + .ToString() + .MatchInlineSnapshot( + """ + System.Threading.Tasks.Task PublishAsync(ChilliCream.Nitro.Fusion.FusionPublicationRequest, System.ReadOnlyMemory`1[System.Byte], System.Threading.CancellationToken) + """); + } + + [Fact] + public void Dispose_Should_ClearOwnedArchiveAndRejectAccess_When_Called() + { + byte[] archive = [1, 2, 3]; + var download = new FusionSourceSchemaDownload( + "products", + "20260730", + archive, + new string('A', 64)); + + Assert.Equal(new byte[] { 1, 2, 3 }, download.Archive.ToArray()); + + download.Dispose(); + + Assert.Equal(new byte[3], archive); + Assert.Throws(() => download.Archive); + + archive[0] = 9; + download.Dispose(); + Assert.Equal(new byte[] { 9, 0, 0 }, archive); + } + [Fact] public async Task ComputeSha256Async_Should_ReturnSameDigest_When_ContentIsNormalized() { @@ -106,7 +140,7 @@ type Query { }; var workflow = CreateWorkflow(transport); - var result = await workflow.DownloadSourceSchemaAsync( + using var result = await workflow.DownloadSourceSchemaAsync( CreateTarget(), new FusionSourceSchemaVersion("products", "20260730"), TestContext.Current.CancellationToken); @@ -120,6 +154,11 @@ type Query { Assert.Equal(remoteArchive, result.Archive.ToArray()); Assert.Equal("products", transport.LastDownloadName); Assert.Equal("20260730", transport.LastDownloadVersion); + + var ownedArchive = Assert.Single(transport.ReturnedArchives); + result.Dispose(); + Assert.Equal(new byte[ownedArchive.Length], ownedArchive); + Assert.Throws(() => result.Archive); } finally { @@ -173,6 +212,8 @@ await CreateArchiveAsync( Assert.Equal( "The source schema settings name must exactly match 'products'.", exception.Message); + var returnedArchive = Assert.Single(transport.ReturnedArchives); + Assert.Equal(new byte[returnedArchive.Length], returnedArchive); } finally { @@ -206,6 +247,8 @@ public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_Decompress "The Fusion source schema archive is invalid.", exception.Message); Assert.IsType(exception.InnerException); + var returnedArchive = Assert.Single(transport.ReturnedArchives); + Assert.Equal(new byte[returnedArchive.Length], returnedArchive); } finally { @@ -250,6 +293,8 @@ await CreateUploadAsync(localPath), Assert.Equal(0, transport.UploadCount); Assert.Equal(1, transport.DownloadCount); + var returnedArchive = Assert.Single(transport.ReturnedArchives); + Assert.Equal(new byte[returnedArchive.Length], returnedArchive); } finally { @@ -295,6 +340,8 @@ await CreateArchiveAsync( + "with different normalized schema, settings, or extensions.", exception.Message); Assert.Equal(0, transport.UploadCount); + var returnedArchive = Assert.Single(transport.ReturnedArchives); + Assert.Equal(new byte[returnedArchive.Length], returnedArchive); } finally { @@ -341,124 +388,90 @@ await CreateUploadAsync(archivePath), [Fact] public async Task PublishAsync_Should_Commit_When_ValidationSucceeds() { - var directory = CreateTemporaryDirectory(); - try + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var transport = new FakeTransport { - var fusionArchivePath = Path.Combine(directory, "gateway.far"); - await File.WriteAllTextAsync( - fusionArchivePath, - "fusion archive", - TestContext.Current.CancellationToken); - var transport = new FakeTransport - { - Events = - [ - new(FusionRemoteEventKind.Ready, []), - new(FusionRemoteEventKind.ValidationSucceeded, []), - new(FusionRemoteEventKind.PublishingSucceeded, []) - ] - }; - var workflow = CreateWorkflow(transport); + Events = + [ + new(FusionRemoteEventKind.Ready, []), + new(FusionRemoteEventKind.ValidationSucceeded, []), + new(FusionRemoteEventKind.PublishingSucceeded, []) + ] + }; + var workflow = CreateWorkflow(transport); - await workflow.PublishAsync( - CreatePublicationRequest(force: false), - fusionArchivePath, - TestContext.Current.CancellationToken); + await workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchive, + TestContext.Current.CancellationToken); - Assert.Equal( - ["begin", "watch", "claim", "validate", "commit"], - transport.Calls); - } - finally - { - Directory.Delete(directory, recursive: true); - } + Assert.Equal( + ["begin", "watch", "claim", "validate", "commit"], + transport.Calls); + Assert.Equal(fusionArchive, transport.ValidatedArchive); + Assert.Equal(fusionArchive, transport.CommittedArchive); } [Fact] public async Task PublishAsync_Should_ReleaseAndFail_When_ValidationFailsWithoutForce() { - var directory = CreateTemporaryDirectory(); - try + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var transport = new FakeTransport { - var fusionArchivePath = Path.Combine(directory, "gateway.far"); - await File.WriteAllTextAsync( - fusionArchivePath, - "fusion archive", - TestContext.Current.CancellationToken); - var transport = new FakeTransport - { - Events = - [ - new(FusionRemoteEventKind.Ready, []), - new( - FusionRemoteEventKind.ValidationFailed, - ["Breaking schema change."]) - ] - }; - var workflow = CreateWorkflow(transport); + Events = + [ + new(FusionRemoteEventKind.Ready, []), + new( + FusionRemoteEventKind.ValidationFailed, + ["Breaking schema change."]) + ] + }; + var workflow = CreateWorkflow(transport); - var exception = await Assert.ThrowsAsync( - () => workflow.PublishAsync( - CreatePublicationRequest(force: false), - fusionArchivePath, - TestContext.Current.CancellationToken)); + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchive, + TestContext.Current.CancellationToken)); - Assert.Equal( - "Nitro rejected the Fusion configuration validation. " - + "Breaking schema change.", - exception.Message); - Assert.Equal( - ["begin", "watch", "claim", "validate", "release"], - transport.Calls); - } - finally - { - Directory.Delete(directory, recursive: true); - } + Assert.Equal( + "Nitro rejected the Fusion configuration validation. " + + "Breaking schema change.", + exception.Message); + Assert.Equal( + ["begin", "watch", "claim", "validate", "release"], + transport.Calls); } [Fact] public async Task PublishAsync_Should_Commit_When_ValidationFailsWithForce() { - var directory = CreateTemporaryDirectory(); - try + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var transport = new FakeTransport { - var fusionArchivePath = Path.Combine(directory, "gateway.far"); - await File.WriteAllTextAsync( - fusionArchivePath, - "fusion archive", - TestContext.Current.CancellationToken); - var transport = new FakeTransport - { - Events = - [ - new( - FusionRemoteEventKind.Ready, - []), - new( - FusionRemoteEventKind.ValidationFailed, - ["Breaking schema change."]), - new( - FusionRemoteEventKind.PublishingSucceeded, - []) - ] - }; - var workflow = CreateWorkflow(transport); + Events = + [ + new( + FusionRemoteEventKind.Ready, + []), + new( + FusionRemoteEventKind.ValidationFailed, + ["Breaking schema change."]), + new( + FusionRemoteEventKind.PublishingSucceeded, + []) + ] + }; + var workflow = CreateWorkflow(transport); - await workflow.PublishAsync( - CreatePublicationRequest(force: true), - fusionArchivePath, - TestContext.Current.CancellationToken); + await workflow.PublishAsync( + CreatePublicationRequest(force: true), + fusionArchive, + TestContext.Current.CancellationToken); - Assert.Equal( - ["begin", "watch", "claim", "validate", "commit"], - transport.Calls); - } - finally - { - Directory.Delete(directory, recursive: true); - } + Assert.Equal( + ["begin", "watch", "claim", "validate", "commit"], + transport.Calls); } private static FusionDeploymentWorkflow CreateWorkflow( @@ -582,8 +595,14 @@ private sealed class FakeTransport : IFusionDeploymentTransport public string? LastDownloadVersion { get; private set; } + public List ReturnedArchives { get; } = []; + public List Calls { get; } = []; + public byte[]? ValidatedArchive { get; private set; } + + public byte[]? CommittedArchive { get; private set; } + public Task DownloadSourceSchemaAsync( string name, string version, @@ -592,10 +611,17 @@ private sealed class FakeTransport : IFusionDeploymentTransport DownloadCount++; LastDownloadName = name; LastDownloadVersion = version; - return Task.FromResult( - DownloadCount > 1 && RemoteArchiveAfterUpload is not null - ? RemoteArchiveAfterUpload - : RemoteArchive); + var archive = DownloadCount > 1 && RemoteArchiveAfterUpload is not null + ? RemoteArchiveAfterUpload + : RemoteArchive; + if (archive is null) + { + return Task.FromResult(null); + } + + var ownedArchive = archive.ToArray(); + ReturnedArchives.Add(ownedArchive); + return Task.FromResult(ownedArchive); } public Task UploadSourceSchemaAsync( @@ -646,15 +672,21 @@ public Task ReleasePublishAsync( public Task ValidatePublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken) - => Success("validate"); + { + ValidatedArchive = archive.ToArray(); + return Success("validate"); + } public Task CommitPublishAsync( string requestId, - string archivePath, + ReadOnlyMemory archive, CancellationToken cancellationToken) - => Success("commit"); + { + CommittedArchive = archive.ToArray(); + return Success("commit"); + } public ValueTask DisposeAsync() => ValueTask.CompletedTask; From 8dba8e48b46676bfc671f3392f09a7cbf681715a Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:54:20 +0200 Subject: [PATCH 06/11] Add an example project --- ...PLOY.md => FUSION_ASPIRE_PUBLISH_DEPLOY.md | 0 ...SION_PUBLISHING.md => FUSION_PUBLISHING.md | 0 .../.github/workflows/deploy.yml | 150 +++++ examples/FusionReleasePipeline/.gitignore | 6 + .../Directory.Build.props | 9 + .../Directory.Packages.props | 13 + .../FusionReleasePipeline.slnx | 8 + examples/FusionReleasePipeline/README.md | 111 +++ .../src/AppHost/AppHost.csproj | 25 + .../src/AppHost/Program.cs | 87 +++ .../AppHost/Properties/launchSettings.json | 20 + .../src/Gateway/Gateway.csproj | 11 + .../src/Gateway/Program.cs | 50 ++ .../Gateway/Properties/launchSettings.json | 14 + .../src/Products/Products.csproj | 9 + .../src/Products/Program.cs | 32 + .../Products/Properties/launchSettings.json | 14 + .../src/Products/schema-settings.json | 19 + .../src/Products/schema.graphqls | 14 + .../src/Reviews/Program.cs | 33 + .../Reviews/Properties/launchSettings.json | 14 + .../src/Reviews/Reviews.csproj | 9 + .../src/Reviews/schema-settings.json | 19 + .../src/Reviews/schema.graphqls | 15 + .../ASPIRE_PUBLISH_AND_DEPLOY.md | 637 ++++++++++++++++++ 25 files changed, 1319 insertions(+) rename src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md => FUSION_ASPIRE_PUBLISH_DEPLOY.md (100%) rename src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md => FUSION_PUBLISHING.md (100%) create mode 100644 examples/FusionReleasePipeline/.github/workflows/deploy.yml create mode 100644 examples/FusionReleasePipeline/.gitignore create mode 100644 examples/FusionReleasePipeline/Directory.Build.props create mode 100644 examples/FusionReleasePipeline/Directory.Packages.props create mode 100644 examples/FusionReleasePipeline/FusionReleasePipeline.slnx create mode 100644 examples/FusionReleasePipeline/README.md create mode 100644 examples/FusionReleasePipeline/src/AppHost/AppHost.csproj create mode 100644 examples/FusionReleasePipeline/src/AppHost/Program.cs create mode 100644 examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json create mode 100644 examples/FusionReleasePipeline/src/Gateway/Gateway.csproj create mode 100644 examples/FusionReleasePipeline/src/Gateway/Program.cs create mode 100644 examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json create mode 100644 examples/FusionReleasePipeline/src/Products/Products.csproj create mode 100644 examples/FusionReleasePipeline/src/Products/Program.cs create mode 100644 examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json create mode 100644 examples/FusionReleasePipeline/src/Products/schema-settings.json create mode 100644 examples/FusionReleasePipeline/src/Products/schema.graphqls create mode 100644 examples/FusionReleasePipeline/src/Reviews/Program.cs create mode 100644 examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json create mode 100644 examples/FusionReleasePipeline/src/Reviews/Reviews.csproj create mode 100644 examples/FusionReleasePipeline/src/Reviews/schema-settings.json create mode 100644 examples/FusionReleasePipeline/src/Reviews/schema.graphqls create mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/FUSION_ASPIRE_PUBLISH_DEPLOY.md similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Client/FUSION_ASPIRE_PUBLISH_DEPLOY.md rename to FUSION_ASPIRE_PUBLISH_DEPLOY.md diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md b/FUSION_PUBLISHING.md similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FUSION_PUBLISHING.md rename to FUSION_PUBLISHING.md diff --git a/examples/FusionReleasePipeline/.github/workflows/deploy.yml b/examples/FusionReleasePipeline/.github/workflows/deploy.yml new file mode 100644 index 00000000000..c8016312279 --- /dev/null +++ b/examples/FusionReleasePipeline/.github/workflows/deploy.yml @@ -0,0 +1,150 @@ +name: Deploy Fusion release + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: read + +env: + DEMO_ROOT: examples/FusionReleasePipeline + APPHOST_PROJECT: examples/FusionReleasePipeline/src/AppHost/AppHost.csproj + RELEASE_TAG: ${{ github.sha }} + +jobs: + fusion-build: + name: Build and upload Fusion release + runs-on: ubuntu-latest + permissions: + contents: read + concurrency: + group: fusion-nitro-example-invalid-replace-with-nitro-api-id-upload + cancel-in-progress: false + env: + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 10.0.x + 11.0.100-preview.6.26359.118 + + - name: Install Aspire CLI + run: dotnet tool install --global Aspire.Cli --version 13.4.6 + + - name: Restore + run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" + + - name: Build + run: dotnet build "$DEMO_ROOT/FusionReleasePipeline.slnx" --no-restore + + - name: Upload immutable Fusion sources + run: >- + aspire do fusion-upload + --apphost "$APPHOST_PROJECT" + --environment Development + --non-interactive + + deploy-development: + name: Deploy Development + needs: fusion-build + runs-on: ubuntu-latest + environment: Development + permissions: + contents: read + id-token: write + concurrency: + group: fusion-nitro-example-invalid-replace-with-nitro-api-id-replace-with-development-stage + cancel-in-progress: false + env: + Azure__CredentialSource: AzureCli + Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} + Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} + Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} + Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 10.0.x + 11.0.100-preview.6.26359.118 + + - name: Install Aspire CLI + run: dotnet tool install --global Aspire.Cli --version 13.4.6 + + - name: Sign in to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.DEMO_AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.DEMO_AZURE_TENANT_ID }} + subscription-id: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + + - name: Restore + run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" + + - name: Deploy Development + run: >- + aspire do fusion-publish + --apphost "$APPHOST_PROJECT" + --environment Development + --non-interactive + + deploy-test: + name: Deploy Test + needs: + - fusion-build + - deploy-development + runs-on: ubuntu-latest + environment: Test + permissions: + contents: read + id-token: write + concurrency: + group: fusion-nitro-example-invalid-replace-with-nitro-api-id-replace-with-test-stage + cancel-in-progress: false + env: + Azure__CredentialSource: AzureCli + Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} + Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} + Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + Parameters__tag: ${{ env.RELEASE_TAG }} + Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} + Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 10.0.x + 11.0.100-preview.6.26359.118 + + - name: Install Aspire CLI + run: dotnet tool install --global Aspire.Cli --version 13.4.6 + + - name: Sign in to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.DEMO_AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.DEMO_AZURE_TENANT_ID }} + subscription-id: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + + - name: Restore + run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" + + - name: Deploy Test + run: >- + aspire do fusion-publish + --apphost "$APPHOST_PROJECT" + --environment Test + --non-interactive diff --git a/examples/FusionReleasePipeline/.gitignore b/examples/FusionReleasePipeline/.gitignore new file mode 100644 index 00000000000..676d86a1620 --- /dev/null +++ b/examples/FusionReleasePipeline/.gitignore @@ -0,0 +1,6 @@ +**/bin/ +**/obj/ +.aspire/ +aspire-output/ +aspire.config.json +src/Gateway/gateway.far diff --git a/examples/FusionReleasePipeline/Directory.Build.props b/examples/FusionReleasePipeline/Directory.Build.props new file mode 100644 index 00000000000..989f54b608f --- /dev/null +++ b/examples/FusionReleasePipeline/Directory.Build.props @@ -0,0 +1,9 @@ + + + net10.0 + enable + enable + true + NU1901;NU1902;NU1903;NU1904 + + diff --git a/examples/FusionReleasePipeline/Directory.Packages.props b/examples/FusionReleasePipeline/Directory.Packages.props new file mode 100644 index 00000000000..4fdb9eb3261 --- /dev/null +++ b/examples/FusionReleasePipeline/Directory.Packages.props @@ -0,0 +1,13 @@ + + + true + + + + + + + + + + diff --git a/examples/FusionReleasePipeline/FusionReleasePipeline.slnx b/examples/FusionReleasePipeline/FusionReleasePipeline.slnx new file mode 100644 index 00000000000..23d71096442 --- /dev/null +++ b/examples/FusionReleasePipeline/FusionReleasePipeline.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/FusionReleasePipeline/README.md b/examples/FusionReleasePipeline/README.md new file mode 100644 index 00000000000..95a35f7e960 --- /dev/null +++ b/examples/FusionReleasePipeline/README.md @@ -0,0 +1,111 @@ +# Fusion release pipeline example + +This sample contains two Hot Chocolate source schemas, a Fusion gateway, an Aspire AppHost, and a +GitHub Actions workflow using the split Fusion release commands. + +The AppHost and services reference the current `graphql-platform` checkout. Checked-in +`schema.graphqls` and `schema-settings.json` files make the upload input deterministic. + +## Before a real deployment + +Every remote value is intentionally fake. Replace: + +- `https://nitro.example.invalid`; +- `replace-with-nitro-api-id`; +- `replace-with-development-stage`; +- `replace-with-test-stage`; and +- the Development and Test source URLs in both `schema-settings.json` files. + +Provide `DEMO_NITRO_API_KEY` as a repository or organization secret for the upload job. Create the +GitHub environments `Development` and `Test`, then configure these environment secrets: + +- `DEMO_NITRO_GATEWAY_API_KEY`; +- `DEMO_AZURE_CLIENT_ID`; +- `DEMO_AZURE_TENANT_ID`; and +- `DEMO_AZURE_SUBSCRIPTION_ID`. + +Configure `DEMO_AZURE_LOCATION` and `DEMO_AZURE_RESOURCE_GROUP` as environment variables. The +sample uses Azure Container Apps because it contributes the `DeployCompute` steps needed to prove +source and gateway ordering. Development and Test should normally use distinct resource groups. + +The sources and gateway use external HTTP ingress. The deployment runner must reach the configured +source URLs for readiness polling. The committed `.invalid` URLs deliberately fail a real release. + +The nested `.github/workflows/deploy.yml` is documentation while this example is in the monorepo. +GitHub discovers workflows only from the repository-root `.github/workflows` directory. + +## Run locally + +From the repository root: + +```shell +dotnet restore examples/FusionReleasePipeline/FusionReleasePipeline.slnx +dotnet build examples/FusionReleasePipeline/FusionReleasePipeline.slnx --no-restore +dotnet run --project examples/FusionReleasePipeline/src/AppHost/AppHost.csproj +``` + +Run mode composes `examples/FusionReleasePipeline/src/Gateway/gateway.far` with the `local` +settings environment and starts: + +- gateway at `http://localhost:5100/graphql`; +- products at `http://localhost:5101/graphql`; and +- reviews at `http://localhost:5102/graphql`. + +The AppHost derives the gateway Nitro stage from the already selected composition environment. +Run mode maps to `local`, so it injects no `NITRO_*` variables and the gateway uses its local FAR. + +## Release flow + +All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`. The build job selects +the real Development declaration and uploads both sources as exact immutable versions: + +```shell +export Parameters__tag="$RELEASE_TAG" +export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" + +aspire do fusion-upload \ + --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ + --environment Development \ + --non-interactive +``` + +Development and Test share the same Nitro cloud URL, API ID, source set, and tag, so this single +upload serves both. A real AppHost with distinct Nitro API targets must run `fusion-upload` once per +distinct target using a matching selected environment. + +The deployment job checks out the same revision and publishes without a manifest or CI artifact: + +```shell +export Parameters__tag="$RELEASE_TAG" +export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" +export Parameters__nitroGatewayApiKey="$DEMO_NITRO_GATEWAY_API_KEY" +export Azure__CredentialSource=AzureCli +export Azure__SubscriptionId="$DEMO_AZURE_SUBSCRIPTION_ID" +export Azure__ResourceGroup="$DEMO_AZURE_RESOURCE_GROUP" +export Azure__Location="$DEMO_AZURE_LOCATION" + +aspire do fusion-publish \ + --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ + --environment Development \ + --non-interactive +``` + +`fusion-publish` infers `products` and `reviews` from the AppHost, downloads the exact source +versions `products@RELEASE_TAG` and `reviews@RELEASE_TAG` as a metadata-only preflight. After source +deployment it downloads them again, verifies the same canonical digests, and composes the +Development endpoints. + +The Test job uses the same tag with `--environment Test`, which composes the Test endpoints. + +There is no artifact upload/download between jobs. The Fusion-specific publish steps never export +schemas, upload source versions, write Fusion apply-state files, or resolve Aspire's output-path +service. Exact archives and the FAR remain in bounded invocation memory and are cleared after +completion. Azure deployment dependencies can still write target artifacts and Aspire deployment +state according to the provider configuration. The safe order is exact preflight, source +deployment, exact re-download and composition, source readiness, internal Nitro stage publication, +gateway deployment, then terminal public `fusion-publish`. + +The build job has a stable concurrency key for the Nitro API. Each deployment job has a stable key +for its Nitro stage and Azure target. `cancel-in-progress: false` queues writers. Add external +locking when another repository or deployment system can write the same Nitro stage or compute +target. diff --git a/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj b/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj new file mode 100644 index 00000000000..b498c1b5670 --- /dev/null +++ b/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj @@ -0,0 +1,25 @@ + + + + Exe + true + FusionReleasePipeline.AppHost + + + + + + + + + + + + + diff --git a/examples/FusionReleasePipeline/src/AppHost/Program.cs b/examples/FusionReleasePipeline/src/AppHost/Program.cs new file mode 100644 index 00000000000..06d3c831257 --- /dev/null +++ b/examples/FusionReleasePipeline/src/AppHost/Program.cs @@ -0,0 +1,87 @@ +using ChilliCream.Nitro.Aspire; +using HotChocolate.Fusion.Aspire; + +const string nitroCloudUrl = "https://nitro.example.invalid"; +const string nitroApiId = "replace-with-nitro-api-id"; +const string developmentStage = "replace-with-development-stage"; +const string testStage = "replace-with-test-stage"; + +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddNitro(); +builder.AddAzureContainerAppEnvironment("demo-aca"); + +var compositionEnvironment = builder.ExecutionContext.IsRunMode + ? "local" + : builder.Environment.EnvironmentName switch + { + "Development" => "development", + "Test" => "test", + _ => "local" + }; + +var products = builder + .AddProject("products") + .WithExternalHttpEndpoints() + .WithGraphQLSchemaFile(); + +var reviews = builder + .AddProject("reviews") + .WithExternalHttpEndpoints() + .WithGraphQLSchemaFile(); + +var gateway = builder + .AddProject("gateway") + .WithExternalHttpEndpoints() + .WithGraphQLSchemaComposition( + settings: new GraphQLCompositionSettings + { + EnvironmentName = compositionEnvironment + }) + .WithReference(products) + .WithReference(reviews); + +var tag = builder.AddParameter("tag"); +var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); + +var nitro = builder + .AddNitroTarget("nitro") + .WithCloudUrl(nitroCloudUrl) + .WithApiId(nitroApiId) + .WithApiKey(nitroApiKey); + +nitro + .AddFusionDeployment("fusion-development") + .ForEnvironment("Development") + .ToStage(developmentStage) + .WithCompositionEnvironment("development") + .WithConfigurationTag(tag); + +nitro + .AddFusionDeployment("fusion-test") + .ForEnvironment("Test") + .ToStage(testStage) + .WithCompositionEnvironment("test") + .WithConfigurationTag(tag); + +var stage = compositionEnvironment switch +{ + "development" => developmentStage, + "test" => testStage, + _ => null +}; + +if (stage is not null) +{ + var nitroGatewayApiKey = builder.AddParameter( + "nitroGatewayApiKey", + secret: true); + + gateway + .WithEnvironment("NITRO_URL", nitroCloudUrl) + .WithEnvironment("NITRO_API_ID", nitroApiId) + .WithEnvironment("NITRO_STAGE", stage) + .WithEnvironment("NITRO_API_KEY", nitroGatewayApiKey); +} + +builder.Build().Run(); diff --git a/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json new file mode 100644 index 00000000000..8a07dc9196a --- /dev/null +++ b/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15210", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19210", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20210", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true", + "Parameters__tag": "local-release", + "Parameters__nitroApiKey": "replace-with-a-real-nitro-api-key" + } + } + } +} diff --git a/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj b/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj new file mode 100644 index 00000000000..fc8dcaf8fb3 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj @@ -0,0 +1,11 @@ + + + FusionReleasePipeline.Gateway + + + + + + + + diff --git a/examples/FusionReleasePipeline/src/Gateway/Program.cs b/examples/FusionReleasePipeline/src/Gateway/Program.cs new file mode 100644 index 00000000000..6a542f62613 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Gateway/Program.cs @@ -0,0 +1,50 @@ +using ChilliCream.Nitro; +using ChilliCream.Nitro.Fusion; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHttpClient("Fusion"); + +var nitroApiId = builder.Configuration["NITRO_API_ID"]; +var nitroApiKey = builder.Configuration["NITRO_API_KEY"]; +var nitroStage = builder.Configuration["NITRO_STAGE"]; +var nitroUrl = builder.Configuration["NITRO_URL"]; +var useNitro = !string.IsNullOrWhiteSpace(nitroApiId) + || !string.IsNullOrWhiteSpace(nitroApiKey) + || !string.IsNullOrWhiteSpace(nitroStage) + || !string.IsNullOrWhiteSpace(nitroUrl); + +if (useNitro) +{ + if (string.IsNullOrWhiteSpace(nitroApiId) + || string.IsNullOrWhiteSpace(nitroApiKey) + || string.IsNullOrWhiteSpace(nitroStage) + || string.IsNullOrWhiteSpace(nitroUrl)) + { + throw new InvalidOperationException( + "NITRO_URL, NITRO_API_ID, NITRO_API_KEY, and NITRO_STAGE " + + "must all be configured for a deployed gateway."); + } + + builder.Services + .AddNitro(options => + { + options.ApiId = nitroApiId; + options.ApiKey = nitroApiKey; + options.Stage = nitroStage; + options.ServerUrl = nitroUrl; + }) + .AddFusion(); +} +else +{ + builder + .AddGraphQLGateway() + .AddFileSystemConfiguration("gateway.far"); +} + +var app = builder.Build(); + +app.MapGraphQLHttp(); + +app.Run(); diff --git a/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json new file mode 100644 index 00000000000..411cd328b7c --- /dev/null +++ b/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5100", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/FusionReleasePipeline/src/Products/Products.csproj b/examples/FusionReleasePipeline/src/Products/Products.csproj new file mode 100644 index 00000000000..76ce2bdf02f --- /dev/null +++ b/examples/FusionReleasePipeline/src/Products/Products.csproj @@ -0,0 +1,9 @@ + + + FusionReleasePipeline.Products + + + + + + diff --git a/examples/FusionReleasePipeline/src/Products/Program.cs b/examples/FusionReleasePipeline/src/Products/Program.cs new file mode 100644 index 00000000000..08bd0e6ec6e --- /dev/null +++ b/examples/FusionReleasePipeline/src/Products/Program.cs @@ -0,0 +1,32 @@ +using HotChocolate.Types.Relay; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddGraphQLServer() + .AddQueryType(); + +var app = builder.Build(); + +app.MapGraphQL(); + +return await app.RunWithGraphQLCommandsAsync(args); + +public sealed class Query +{ + private static readonly Product[] s_products = + [ + new("p-1", "Mechanical Keyboard", 149.00), + new("p-2", "GraphQL Mug", 18.50) + ]; + + public IReadOnlyList GetProducts() => s_products; + + public Product? GetProduct([ID] string id) + => s_products.FirstOrDefault(product => product.Id == id); +} + +public sealed record Product( + [property: ID] string Id, + string Name, + double Price); diff --git a/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json new file mode 100644 index 00000000000..a8c43d30861 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/FusionReleasePipeline/src/Products/schema-settings.json b/examples/FusionReleasePipeline/src/Products/schema-settings.json new file mode 100644 index 00000000000..c9e7cec8217 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Products/schema-settings.json @@ -0,0 +1,19 @@ +{ + "name": "products", + "transports": { + "http": { + "url": "{{PRODUCTS_URL}}/graphql" + } + }, + "environments": { + "local": { + "PRODUCTS_URL": "http://localhost:5101" + }, + "development": { + "PRODUCTS_URL": "https://products.development.example.invalid" + }, + "test": { + "PRODUCTS_URL": "https://products.test.example.invalid" + } + } +} diff --git a/examples/FusionReleasePipeline/src/Products/schema.graphqls b/examples/FusionReleasePipeline/src/Products/schema.graphqls new file mode 100644 index 00000000000..85c74ceb631 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Products/schema.graphqls @@ -0,0 +1,14 @@ +schema { + query: Query +} + +type Product { + id: ID! + name: String! + price: Float! +} + +type Query { + product(id: ID!): Product + products: [Product!]! +} diff --git a/examples/FusionReleasePipeline/src/Reviews/Program.cs b/examples/FusionReleasePipeline/src/Reviews/Program.cs new file mode 100644 index 00000000000..20fc476e8f6 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Reviews/Program.cs @@ -0,0 +1,33 @@ +using HotChocolate.Types.Relay; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddGraphQLServer() + .AddQueryType(); + +var app = builder.Build(); + +app.MapGraphQL(); + +return await app.RunWithGraphQLCommandsAsync(args); + +public sealed class Query +{ + private static readonly Review[] s_reviews = + [ + new("r-1", "p-1", 5, "Excellent for long coding sessions."), + new("r-2", "p-2", 4, "A dependable mug with a good handle.") + ]; + + public IReadOnlyList GetReviews() => s_reviews; + + public Review? GetReview([ID] string id) + => s_reviews.FirstOrDefault(review => review.Id == id); +} + +public sealed record Review( + [property: ID] string Id, + [property: ID] string ProductId, + int Stars, + string Commentary); diff --git a/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json new file mode 100644 index 00000000000..8e46e0dbcc7 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5102", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj b/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj new file mode 100644 index 00000000000..ff93d35d023 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj @@ -0,0 +1,9 @@ + + + FusionReleasePipeline.Reviews + + + + + + diff --git a/examples/FusionReleasePipeline/src/Reviews/schema-settings.json b/examples/FusionReleasePipeline/src/Reviews/schema-settings.json new file mode 100644 index 00000000000..b0e6d519388 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Reviews/schema-settings.json @@ -0,0 +1,19 @@ +{ + "name": "reviews", + "transports": { + "http": { + "url": "{{REVIEWS_URL}}/graphql" + } + }, + "environments": { + "local": { + "REVIEWS_URL": "http://localhost:5102" + }, + "development": { + "REVIEWS_URL": "https://reviews.development.example.invalid" + }, + "test": { + "REVIEWS_URL": "https://reviews.test.example.invalid" + } + } +} diff --git a/examples/FusionReleasePipeline/src/Reviews/schema.graphqls b/examples/FusionReleasePipeline/src/Reviews/schema.graphqls new file mode 100644 index 00000000000..db15f7a4282 --- /dev/null +++ b/examples/FusionReleasePipeline/src/Reviews/schema.graphqls @@ -0,0 +1,15 @@ +schema { + query: Query +} + +type Query { + review(id: ID!): Review + reviews: [Review!]! +} + +type Review { + commentary: String! + id: ID! + productId: ID! + stars: Int! +} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md b/src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md new file mode 100644 index 00000000000..a7d9b0c6566 --- /dev/null +++ b/src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md @@ -0,0 +1,637 @@ +# Aspire publish and deploy + +Research date: 2026-07-30. Version reviewed: Aspire 13.4.6, the latest stable +release on the research date. + +## Executive summary + +Aspire deployment is driven by the application model in the AppHost. A hosting +integration provides the API for adding a compute-environment resource, such as +Docker Compose, Kubernetes, Azure Kubernetes Service (AKS), Azure Container +Apps, or Azure App Service. That resource contributes target-specific steps to +Aspire's deployment pipeline. + +- `aspire publish` runs the pipeline's publish entry point and writes + target-specific artifacts for another tool, CI stage, GitOps system, or person + to apply later. In the documented built-in targets, unresolved parameter + requirements, including secrets, remain represented as target-specific + placeholders. +- `aspire deploy` runs the deploy entry point and its dependencies. Aspire + resolves parameters, generates any target output it needs, and applies the + deployment itself. +- `aspire deploy` does **not** consume artifacts from an earlier + `aspire publish`. They are separate pipeline entry points. +- `aspire do ` is the lower-level option for workflows that need to split + building, pushing, publishing, and deployment across stages. + +These semantics and the no-op behavior when no target contributes a matching +step are documented in [How Aspire deployment works](https://aspire.dev/deployment/deploy-with-aspire/). + +## Version and stability status + +The official release page identifies +[Aspire 13.4.6](https://github.com/microsoft/aspire/releases/tag/v13.4.6) as the +latest stable release. It was released on 2026-06-20. + +There are two distinct stability statements to keep in mind: + +1. Aspire 13.4.6 is a stable Aspire release. +2. Aspire 13.4 made `aspire publish` and `aspire deploy` generally available, + supported workflows, according to the + [Aspire 13.4 release notes](https://aspire.dev/whats-new/aspire-13-4/#deployment-commands-are-generally-available). + Some current reference navigation still displays "Preview" badges for these + commands. The lower-level `aspire do` command and pipeline extension APIs + remain preview/experimental, and the pipeline system is described as + experimental in Aspire 13 by the + [pipeline documentation](https://aspire.dev/deployment/pipelines/). + +Production automation should pin and test the CLI/SDK versions and treat custom +pipeline APIs, in particular, as capable of changing in a future release. + +Target integrations have their own stability and package versions, independent +of the core commands. On the research date, the official packages for +[Docker](https://www.nuget.org/packages/Aspire.Hosting.Docker/13.4.6) and +[Azure Container Apps](https://www.nuget.org/packages/Aspire.Hosting.Azure.AppContainers/13.4.6) +were stable 13.4.6 packages. The +[Kubernetes](https://www.nuget.org/packages/Aspire.Hosting.Kubernetes/13.4.6-preview.1.26319.6) +and +[AKS](https://www.nuget.org/packages/Aspire.Hosting.Azure.Kubernetes/13.4.6-preview.1.26319.6) +packages were prerelease `13.4.6-preview.1.26319.6` packages. The +[App Service deployment guide](https://aspire.dev/deployment/azure/app-service/) +explicitly marks that target as Preview, even though its package version is +13.4.6. + +### Current terminology: pipelines and compute environments + +Older Aspire 9.x articles refer to top-level "publishers" and APIs such as +`AddDockerComposePublisher` or `IDistributedApplicationPublisher`. Those are not +the current Aspire 13 model. Aspire 13.0 replaced publishing callbacks and the +publisher interface with pipeline steps. Current applications add a hosting +integration and a compute-environment resource, and resources contribute +pipeline steps. See the official +[Aspire 13 migration section](https://aspire.dev/deployment/pipelines/#migrating-from-publishing-callbacks). + +Methods whose names start with `PublishAs`, such as +`PublishAsDockerComposeService` or `PublishAsAzureContainerApp`, still exist for +target-specific customization. They customize a generated resource; they do not +replace adding the target environment itself. + +## How the pipeline works + +The AppHost is both the local orchestration model and the source of deployment +behavior. For a publish or deploy operation, the CLI: + +1. Finds the AppHost from `--apphost`, a rooted `aspire.config.json`, or a search + below the current directory, in that order. +2. Records the selected AppHost in the rooted `aspire.config.json`. +3. Verifies Aspire's local hosting certificates. +4. Builds and starts the AppHost and its resources. +5. Runs the requested pipeline entry point and its dependency graph. +6. Prints a hierarchical step summary with status and duration. + +The exact command lifecycle is in the +[`aspire publish` reference](https://aspire.dev/reference/cli/commands/aspire-publish/) +and +[`aspire deploy` reference](https://aspire.dev/reference/cli/commands/aspire-deploy/). + +Independent pipeline steps may run concurrently, while declared dependencies +determine ordering. Hosting integrations typically contribute the steps for +infrastructure provisioning, container builds and pushes, artifact generation, +and applying the target deployment. Applications can add their own steps, but +the pipeline API is experimental. + +### No target means no work + +If the AppHost has no resource that contributes work to the selected entry +point, the current CLI completes successfully as a no-op. A successful +`aspire publish` or `aspire deploy` with an almost empty step summary therefore +does not prove that anything was generated or deployed. Add the appropriate +target integration and environment, then inspect the plan: + +```shell +aspire publish --list-steps +aspire deploy --list-steps +``` + +## `aspire publish` versus `aspire deploy` + +| Concern | `aspire publish` | `aspire deploy` | +| --- | --- | --- | +| Intent | One-way artifact handoff | Aspire-managed end-to-end deployment | +| Pipeline entry point | `publish` | `deploy` plus its dependent steps, which may include `publish` | +| Parameters | Preserves requirements as target-specific placeholders in emitted artifacts | Resolves required values before applying changes | +| Side effects on target | Does not apply the emitted artifacts | Provisions or updates infrastructure and workloads as the target integration defines | +| Reuse of prior output | Output is for external consumers | Does not read an earlier publish output as its input | +| Default artifact directory | `/aspire-output` | `/aspire-output` when the target emits deployment artifacts | +| Default Aspire environment | `Production` | `Production` | +| Best fit | Reviewable/promotable artifacts, GitOps, separate approval/apply stages | Local or CI stage has target credentials and Aspire should own the complete operation | + +The +[deployment model](https://aspire.dev/deployment/deploy-with-aspire/#pipeline-entry-points) +defines the command distinction. The command references document +`--output-path`, `--environment`, `--apphost`, `--list-steps`, `--no-build`, +logging options, and `--non-interactive`. + +Useful current command forms are: + +```shell +aspire publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Staging \ + --output-path ./artifacts + +aspire deploy --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Production \ + --non-interactive +``` + +Arguments after `--` are passed to the AppHost: + +```shell +aspire publish --apphost ./MyApp.AppHost/MyApp.AppHost.csproj -- --my-apphost-option +``` + +`--apphost` is the current documented option name. The +[tagged 13.4.6 CLI source](https://github.com/microsoft/aspire/blob/v13.4.6/src/Aspire.Cli/Commands/PipelineCommandBase.cs) +retains `--project` as a legacy alias, but new scripts should use `--apphost`. +`--publisher` belongs to the removed top-level publisher workflow and should not +be used in an Aspire 13.4 workflow. + +## Built-in deployment targets + +The current built-in capability matrix in +[How Aspire deployment works](https://aspire.dev/deployment/deploy-with-aspire/#built-in-target-capabilities) +is: + +| Hosting integration | Reviewed version/status | AppHost environment | Publish result | Deploy behavior | +| --- | --- | --- | --- | --- | +| `Aspire.Hosting.Docker` | 13.4.6 | `AddDockerComposeEnvironment` | Docker Compose files and parameter placeholders | Builds images and starts the generated Compose application | +| `Aspire.Hosting.Kubernetes` | 13.4.6 prerelease | `AddKubernetesEnvironment` | Helm chart for an existing Kubernetes cluster | Installs through Helm into the current `kubectl` context | +| `Aspire.Hosting.Azure.Kubernetes` | 13.4.6 prerelease | `AddAzureKubernetesEnvironment` | Helm chart plus Azure Bicep infrastructure | Provisions AKS/ACR and Azure dependencies, pushes images, and installs the chart | +| `Aspire.Hosting.Azure.AppContainers` | 13.4.6 | `AddAzureContainerAppEnvironment` | Azure target artifacts, including generated provisioning resources | Provisions or attaches the Container Apps environment and ACR, pushes images, and deploys Container Apps | +| `Aspire.Hosting.Azure.AppService` | Preview target, 13.4.6 package | `AddAzureAppServiceEnvironment` | Azure target artifacts | Provisions or attaches the App Service plan and ACR, pushes images, and deploys supported websites | + +Compute resources automatically attach when exactly one compatible environment +exists. If multiple compatible compute environments exist, use +`WithComputeEnvironment` to make the target explicit. This also enables hybrid +deployments in which resources from one AppHost go to different targets. + +### Generated artifact details + +Artifact contents are target-specific: + +- Docker Compose writes `docker-compose.yaml`, an unfilled `.env` from + `aspire publish`, an environment-specific `.env.{environment}` from prepare or + deploy, and per-resource Dockerfiles when the resource uses an existing or + generated Dockerfile build context. See + [Docker Compose output artifacts](https://aspire.dev/deployment/docker-compose/#output-artifacts). +- Kubernetes turns projects and containers into Deployments or StatefulSets, + endpoints into Services, configuration into ConfigMaps and Secrets, and + volumes into persistent-volume resources. Publish produces a Helm chart. See + [Deploy to Kubernetes](https://aspire.dev/deployment/kubernetes/). +- AKS publish produces Helm and Bicep artifacts. Direct deploy additionally + provisions the cluster, ACR, managed identity, and referenced Azure services. + See + [Publish AKS artifacts](https://aspire.dev/deployment/kubernetes/aks/#publish-aks-artifacts). +- Azure integrations generate provisioning resources from the AppHost. For + example, the default Container Apps environment model generates Bicep for the + managed environment, ACR, managed identity, Log Analytics, role assignments, + and Aspire Dashboard. See + [Configure Azure Container Apps environments](https://aspire.dev/integrations/cloud/azure/configure-container-apps/). + +Generated output should be reviewed as target configuration, not assumed to be a +portable intermediate representation. Each integration owns its output shape. + +## Prerequisites + +### Base prerequisites + +For a C# AppHost in Aspire 13.4, the official prerequisites require: + +- .NET 10 SDK for the AppHost. Aspire can still orchestrate applications that + target .NET 8 or later. +- The Aspire CLI. +- An OCI-compliant container runtime where the selected workflow builds or runs + containers. Docker Desktop is the recommended default; Podman is supported. + +The current details, including TypeScript AppHost runtime requirements, are in +[Aspire prerequisites](https://aspire.dev/get-started/prerequisites/). + +### Target-specific prerequisites + +- Docker Compose: Docker or Podman. Podman must be 5.0 or later for the current + Compose deployment support. Aspire can auto-detect the runtime or use + `ASPIRE_CONTAINER_RUNTIME=docker|podman`. +- Existing Kubernetes cluster: `kubectl` on `PATH`, Helm 4.2.0 or later, and a + valid current `kubectl` context. +- AKS: the Kubernetes tools above, Azure CLI, an Azure account, and an active + subscription. +- Azure Container Apps or App Service: an Azure account/subscription and, for the + default local credential source, Azure CLI on `PATH` followed by `az login`. + +Use +[Deploy to Docker Compose](https://aspire.dev/deployment/docker-compose/), +[Deploy to Kubernetes](https://aspire.dev/deployment/kubernetes/), and +[Deploy to Azure](https://aspire.dev/deployment/azure/) for the target-specific +checks. The deployment identity also needs the target-specific permissions +required for resources that the deployment provisions, updates, or attaches. + +## Configuration, parameters, and secrets + +### Parameter declaration and resolution + +An external value is modeled as a parameter resource: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var apiKey = builder.AddParameter("apiKey", secret: true); + +builder.AddDockerComposeEnvironment("compose"); + +builder.AddProject("api") + .WithEnvironment("API_KEY", apiKey); + +builder.Build().Run(); +``` + +The official +[external parameters guide](https://aspire.dev/fundamentals/external-parameters/) +documents this resolution order: + +1. Environment variables named `Parameters__*`. +2. AppHost configuration sources, including `appsettings.json` and .NET user + secrets. +3. An interactive prompt when no value is available. + +For example, `apiKey` can be supplied in automation as: + +```shell +export Parameters__apiKey='value-from-the-ci-secret-store' +``` + +For a parameter containing dashes, the documented environment-variable mapping +uses a single underscore for each dash. `registry-endpoint` therefore becomes +`Parameters__registry_endpoint`. + +`secret: true` is a hint to deployment integrations that the value should be +treated as sensitive; it is not a guarantee that every storage path is +encrypted. In the publish handoff, the value remains a placeholder. For the +Docker Compose example above, the generated shape is conceptually: + +```yaml +# docker-compose.yaml +services: + api: + environment: + API_KEY: ${APIKEY} +``` + +```dotenv +# .env +APIKEY= +``` + +`API_KEY` is the variable received by the application. `APIKEY` is the +target-generated placeholder in the Compose artifacts. Placeholder naming is +target-specific and should not be inferred from the application variable name. + +### Aspire environment versus compute environment + +`--environment Staging` sets the Aspire environment used while evaluating the +AppHost and scopes deployment state. It is not the compute environment resource. +It also does not automatically set `DOTNET_ENVIRONMENT`, `ASPNETCORE_ENVIRONMENT`, +or `NODE_ENV` in child applications. Set those on the child resources when +needed. See [Aspire environments](https://aspire.dev/deployment/environments/). + +### Deployment state and its secret-handling consequence + +`aspire deploy` manages environment-scoped deployment state and can cache +provisioning inputs and parameter values per AppHost and environment under: + +```text +~/.aspire/deployments/{AppHostSha}/{environment}.json +``` + +The official +[deployment state caching guide](https://aspire.dev/deployment/deployment-state-caching/) +warns that these files can include secrets and are stored in plain text outside +the repository, following the same security model as .NET user secrets. Any +process running as the same OS user can read them. + +Consequences: + +- Do not commit, publish, or indiscriminately cache this directory. +- Protect CI caches and restrict who can restore them. +- Prefer the CI platform's secret store plus `Parameters__*` variables for + non-interactive deployments. +- Use `aspire deploy --clear-cache` when values need to be re-entered. This + clears the selected environment's cache and does not save the newly prompted + values after that deployment. + +### Azure authentication and settings + +Local Azure deploys use Azure CLI credentials by default: + +```shell +az login +``` + +The shared Azure inputs can be supplied using configuration or environment +variables: + +| Setting | Environment variable | +| --- | --- | +| `Azure:SubscriptionId` | `Azure__SubscriptionId` | +| `Azure:Location` | `Azure__Location` | +| `Azure:ResourceGroup` | `Azure__ResourceGroup` | +| `Azure:CredentialSource` | `Azure__CredentialSource` | + +Supported credential sources and CI guidance are in +[Deploy to Azure](https://aspire.dev/deployment/azure/). For GitHub Actions and +Azure DevOps, the official guidance prefers workload identity federation over +long-lived client secrets. + +Before using `--non-interactive`, provide all required target settings and +`Parameters__*` values. The flag disables prompts; it does not invent defaults +for unresolved inputs. + +## Practical examples from the official guides + +The examples below use the current C# AppHost APIs and Aspire 13.4 CLI forms. + +### 1. Publish or directly deploy Docker Compose + +Add a Compose environment. With a single compatible environment, compute +resources are included automatically: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddDockerComposeEnvironment("compose"); +builder.AddProject("api"); + +builder.Build().Run(); +``` + +Generate handoff artifacts without building container images: + +```shell +aspire publish --output-path ./aspire-output +``` + +The result includes `docker-compose.yaml` and an unfilled `.env`. A later stage +fills the placeholders and applies the Compose definition. Alternatively, let +Aspire perform the entire local Compose deployment: + +```shell +aspire deploy --environment Staging +``` + +For this target, direct deploy generates the Compose and filled +`.env.Staging`, builds images, and runs +`docker compose up -d --remove-orphans`. The complete progressive workflow is in +the official +[Docker Compose deployment guide](https://aspire.dev/deployment/docker-compose/#publishing-and-deployment-workflow). + +### 2. Publish a Helm chart or deploy to an existing Kubernetes cluster + +Install the integration and add a Kubernetes environment: + +```shell +aspire add kubernetes +``` + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +#pragma warning disable ASPIRECOMPUTE003 +var registry = builder.AddContainerRegistry( + "registry", + "myregistry.example.com:5000"); + +var k8s = builder.AddKubernetesEnvironment("k8s") + .WithContainerRegistry(registry); +#pragma warning restore ASPIRECOMPUTE003 + +builder.AddProject("api") + .WithComputeEnvironment(k8s) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); +``` + +For an artifact/GitOps workflow: + +```shell +aspire publish --output-path ./k8s-artifacts +``` + +For direct deployment to the current `kubectl` context: + +```shell +aspire deploy --environment Production +``` + +The registry must be reachable from both the machine running Aspire and the +cluster, and the deployment environment must have the required registry +credentials. The container-registry API is Preview and currently requires the +`ASPIRECOMPUTE003` warning suppression shown above. The Kubernetes target +publishes a Helm chart; direct deploy builds and pushes project images, then +installs the chart with Helm. Verify the current context before invoking deploy. +Also configure the target's ingress or Gateway API support if external endpoints +must be reachable from outside the cluster. See the official +[cluster deployment guide](https://aspire.dev/deployment/kubernetes/clusters/) +for registry, Helm, context, and exposure details. + +### 3. Directly deploy to Azure Container Apps + +Install the target integration: + +```shell +aspire add azure-appcontainers +``` + +Add the environment and an externally reachable workload: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("aca-env"); + +builder.AddDockerfile("web", "./web") + .WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http") + .WithExternalHttpEndpoints(); + +builder.Build().Run(); +``` + +Authenticate and deploy interactively: + +```shell +az login +aspire deploy +``` + +Aspire provisions or attaches the Container Apps environment, ACR, identity and +supporting Azure resources, builds and pushes the image, and deploys the +Container App. This example and the target's endpoint constraints are in +[Deploy to Azure Container Apps](https://aspire.dev/deployment/azure/container-apps/). + +A CI deployment supplies Azure authentication, the three shared Azure settings, +and all AppHost parameters before running: + +```shell +aspire deploy \ + --apphost ./MyApp.AppHost/MyApp.AppHost.csproj \ + --environment Production \ + --non-interactive +``` + +### 4. Publish reviewable AKS infrastructure and workload artifacts + +The current Kubernetes and AKS hosting integrations are prerelease packages, +even though the core `aspire publish` and `aspire deploy` commands are GA. + +Configure an AKS environment: + +```shell +aspire add azure-kubernetes +``` + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureKubernetesEnvironment("aks"); +builder.AddProject("api"); + +builder.Build().Run(); +``` + +Publish without applying: + +```shell +aspire publish --output-path ./aks-artifacts +``` + +The result contains Helm charts and Bicep infrastructure templates for review +or use by a separate CI/CD or GitOps workflow. Running `aspire deploy` instead +provisions AKS, ACR, managed identity and referenced Azure resources, pushes the +images, and installs the Helm chart. See the official +[AKS deployment guide](https://aspire.dev/deployment/kubernetes/aks/). + +### 5. Target Azure App Service (Preview) + +The official guide marks this deployment target as Preview. For public websites +and APIs that fit App Service's single-public-endpoint model: + +```shell +aspire add azure-appservice +``` + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureAppServiceEnvironment("app-service-env"); + +builder.AddDockerfile("web", "./web") + .WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http") + .WithExternalHttpEndpoints(); + +builder.Build().Run(); +``` + +```shell +az login +aspire deploy +``` + +Aspire provisions the App Service plan, ACR and managed identity, then builds, +pushes and deploys supported website images. Arbitrary infrastructure containers +and internal-only or multi-port workloads do not fit this target; use managed +backing services or a different compute environment. See +[Deploy to Azure App Service](https://aspire.dev/deployment/azure/app-service/). + +## CI/CD workflow choices + +The +[Aspire CI/CD overview](https://aspire.dev/deployment/ci-cd/) recommends keeping +application topology and target behavior in the AppHost while the CI system owns +checkout, tests, approvals, credentials, artifact retention, and promotion. + +- Artifact-first: run `aspire publish`, retain the output, review or promote it, + then use the target's normal tool in a later stage. +- Direct deployment: authenticate the runner and run `aspire deploy + --environment --non-interactive`. +- Split workflow: use the Preview `aspire do` command for `build`, `push`, named + target steps, or custom AppHost steps when build, push, and apply must cross + stage boundaries. The command and custom pipeline APIs are not GA. + +Useful safeguards: + +```shell +# Show the resolved step graph without executing it. +aspire deploy --list-steps + +# Skip the AppHost project build and restore if that project is already built. +# Deployment steps can still build workload images. +aspire deploy --no-build --non-interactive + +# Increase pipeline diagnostics while troubleshooting. +aspire deploy --pipeline-log-level debug --include-exception-details +``` + +The Aspire environment should be passed explicitly in automation because it +affects AppHost evaluation and deployment-state scope: + +```shell +aspire publish --environment Staging --output-path ./artifacts +aspire deploy --environment Production --non-interactive +``` + +## Common mistakes + +1. **Running a successful no-op.** A target environment must contribute publish + or deploy steps. Use `--list-steps` before trusting the command. +2. **Treating deploy as "apply my published folder."** Deploy regenerates what it + needs and does not consume a previous publish result. +3. **Using Aspire 9.x publisher examples.** Current Aspire uses compute + environments and pipeline steps, not `Add*Publisher`, + `IDistributedApplicationPublisher`, `--publisher`, or publishing callbacks. +4. **Assuming placeholders and application variables have identical names.** + Placeholder naming belongs to the target integration. +5. **Assuming `--environment` propagates to child processes.** Explicitly set + framework runtime environment variables on child resources. +6. **Using `--non-interactive` without complete inputs.** Supply credentials, + target settings, and `Parameters__*` variables first. +7. **Treating the deployment cache as encrypted secret storage.** It can contain + plain-text parameter values and needs OS- and CI-level protection. +8. **Deploying to an unintended Kubernetes cluster.** The non-Azure Kubernetes + target uses the current `kubectl` context. +9. **Omitting a registry for direct Kubernetes deployment.** Project images + need a registry that is reachable from the deployment machine and cluster. +10. **Copying old CLI flags.** Current AppHost selection uses `--apphost`. + Current parameter input uses AppHost configuration or `Parameters__*`; + current command references do not list old flags such as `--parameter` or + `--deployment-params-file`. + +## Primary official sources + +- [Aspire 13.4.6 release](https://github.com/microsoft/aspire/releases/tag/v13.4.6) +- [What's new in Aspire 13.4](https://aspire.dev/whats-new/aspire-13-4/) +- [Aspire prerequisites](https://aspire.dev/get-started/prerequisites/) +- [Aspire deployment overview](https://aspire.dev/deployment/) +- [How Aspire deployment works](https://aspire.dev/deployment/deploy-with-aspire/) +- [Pipelines and app topology](https://aspire.dev/deployment/pipelines/) +- [`aspire publish` command](https://aspire.dev/reference/cli/commands/aspire-publish/) +- [`aspire deploy` command](https://aspire.dev/reference/cli/commands/aspire-deploy/) +- [External parameters](https://aspire.dev/fundamentals/external-parameters/) +- [Environments](https://aspire.dev/deployment/environments/) +- [Deployment state caching](https://aspire.dev/deployment/deployment-state-caching/) +- [CI/CD overview](https://aspire.dev/deployment/ci-cd/) +- [Docker Compose deployment](https://aspire.dev/deployment/docker-compose/) +- [Kubernetes deployment](https://aspire.dev/deployment/kubernetes/) +- [Kubernetes cluster deployment](https://aspire.dev/deployment/kubernetes/clusters/) +- [AKS deployment](https://aspire.dev/deployment/kubernetes/aks/) +- [Azure deployment overview](https://aspire.dev/deployment/azure/) +- [Azure Container Apps deployment](https://aspire.dev/deployment/azure/container-apps/) +- [Azure App Service deployment](https://aspire.dev/deployment/azure/app-service/) From 6d2d86eb56d0db87c2bd255ee1804a487b9078f4 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:39:05 +0200 Subject: [PATCH 07/11] Fix --- .../src/AppHost/AppHost.csproj | 4 - .../src/AppHost/Program.cs | 9 +- src/All.slnx | 4 - .../ASPIRE_PUBLISH_AND_DEPLOY.md | 0 .../src/Fusion.Aspire}/BoundedMemoryStream.cs | 2 +- .../src/Fusion.Aspire/FUSION_PUBLISHING.md | 31 +- .../FUSION_PUBLISH_DEPLOY_DESIGN.md | 12 +- .../FusionDeploymentResource.cs | 6 +- .../src/Fusion.Aspire}/FusionPipeline.cs | 40 +- .../Fusion.Aspire}/FusionPipelineExecutor.cs | 145 +- .../Fusion.Aspire}/FusionPipelineResource.cs | 2 +- .../Fusion.Aspire}/FusionPipelineSession.cs | 91 +- .../HotChocolate.Fusion.Aspire.csproj | 17 +- .../Nitro}/FusionArchiveContent.cs | 4 +- .../Nitro}/FusionDeploymentException.cs | 4 +- .../Nitro}/FusionDeploymentWorkflow.cs | 71 +- .../FusionIdentityCollisionException.cs | 4 +- .../FusionIndeterminateStateException.cs | 4 +- .../Nitro}/FusionPublicationRequest.cs | 4 +- .../Nitro}/FusionSourceSchemaContent.cs | 4 +- .../Nitro}/FusionSourceSchemaDownload.cs | 4 +- .../Nitro}/FusionSourceSchemaUpload.cs | 4 +- .../Nitro/FusionSourceSchemaVersion.cs | 6 + .../src/Fusion.Aspire/Nitro}/FusionTarget.cs | 4 +- .../src/Fusion.Aspire/Nitro/NitroFusionApi.cs | 699 + .../src/Fusion.Aspire/Nitro/NitroGraphQL.cs | 405 + .../Fusion.Aspire/Nitro/NitroGraphQLResult.cs | 32 + .../Nitro/NitroOperationDocuments.cs | 123 +- .../Nitro/NitroSchemaValidator.cs | 280 +- .../Nitro/NitroSeedCoordinator.cs | 6 +- .../Operations/BeginFusionDeployment.graphql | 2 +- .../BeginFusionDeployment.graphql.sha256 | 1 + .../Operations/ClaimFusionDeployment.graphql | 2 +- .../ClaimFusionDeployment.graphql.sha256 | 1 + .../Operations/CommitFusionDeployment.graphql | 2 +- .../CommitFusionDeployment.graphql.sha256 | 1 + .../PollNitroSchemaValidation.graphql | 153 - .../PollNitroSchemaValidation.graphql.sha256 | 1 - .../ReleaseFusionDeployment.graphql | 2 +- .../ReleaseFusionDeployment.graphql.sha256 | 1 + .../UploadFusionSourceSchema.graphql | 2 +- .../UploadFusionSourceSchema.graphql.sha256 | 1 + .../ValidateFusionDeployment.graphql | 2 +- .../ValidateFusionDeployment.graphql.sha256 | 1 + .../Operations/WatchFusionDeployment.graphql | 0 .../WatchFusionDeployment.graphql.sha256 | 1 + .../WatchNitroSchemaValidation.graphql | 145 + .../WatchNitroSchemaValidation.graphql.sha256 | 1 + .../src/Fusion.Aspire/NitroExtensions.cs | 364 +- .../NitroPublishTargetResource.cs | 15 + .../FusionPipelineTests.cs | 105 +- .../FusionReleaseAcceptanceTests.cs | 1151 +- .../Nitro/FusionDeploymentWorkflowTests.cs | 1091 ++ .../Nitro/NitroFusionApiTests.cs} | 13 +- .../Nitro/NitroOperationDocumentsTests.cs | 170 +- .../Nitro/NitroSchemaCompositionTests.cs | 1 - .../Nitro/NitroSchemaValidatorTests.cs | 236 +- .../ChilliCream.Nitro.Aspire.csproj | 34 - .../IFusionPipelineExecutor.cs | 34 - .../ChilliCream.Nitro.Aspire/NitroResource.cs | 15 - .../NitroResourceBuilderExtensions.cs | 249 - .../src/ChilliCream.Nitro.Aspire/README.md | 43 - .../ChilliCream.Nitro.Fusion/.graphqlrc.json | 25 - .../ChilliCream.Nitro.Fusion.csproj | 33 - .../FusionSourceSchemaVersion.cs | 6 - .../Generated/FusionApiClient.Client.cs | 11092 ---------------- .../IFusionDeploymentWorkflow.cs | 32 - .../NitroFusionServiceCollectionExtensions.cs | 27 - .../Operations/FusionError.graphql | 3 - .../src/ChilliCream.Nitro.Fusion/README.md | 19 - .../Transport/FusionApiTransportFactory.cs | 461 - .../Transport/IFusionDeploymentTransport.cs | 79 - .../ChilliCream.Nitro.Aspire.Tests.csproj | 15 - .../ChilliCream.Nitro.Fusion.Tests.csproj | 15 - .../FusionDeploymentWorkflowTests.cs | 699 - 75 files changed, 4092 insertions(+), 14275 deletions(-) rename src/{Nitro/Common/src/ChilliCream.Nitro.Client => HotChocolate/Fusion/src/Fusion.Aspire}/ASPIRE_PUBLISH_AND_DEPLOY.md (100%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/BoundedMemoryStream.cs (98%) rename FUSION_PUBLISHING.md => src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md (87%) rename FUSION_ASPIRE_PUBLISH_DEPLOY.md => src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md (98%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/FusionDeploymentResource.cs (84%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/FusionPipeline.cs (93%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/FusionPipelineExecutor.cs (92%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/FusionPipelineResource.cs (75%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Aspire => HotChocolate/Fusion/src/Fusion.Aspire}/FusionPipelineSession.cs (79%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionArchiveContent.cs (98%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionDeploymentException.cs (77%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionDeploymentWorkflow.cs (91%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionIdentityCollisionException.cs (61%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionIndeterminateStateException.cs (83%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionPublicationRequest.cs (77%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionSourceSchemaContent.cs (95%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionSourceSchemaDownload.cs (95%) rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionSourceSchemaUpload.cs (66%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaVersion.cs rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/FusionTarget.cs (61%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/BeginFusionDeployment.graphql (89%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/ClaimFusionDeployment.graphql (89%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/CommitFusionDeployment.graphql (89%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 delete mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql delete mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/ReleaseFusionDeployment.graphql (89%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/UploadFusionSourceSchema.graphql (90%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/ValidateFusionDeployment.graphql (90%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 rename src/{Nitro/Common/src/ChilliCream.Nitro.Fusion => HotChocolate/Fusion/src/Fusion.Aspire/Nitro}/Operations/WatchFusionDeployment.graphql (100%) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql.sha256 create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql.sha256 create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs rename src/{Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests => HotChocolate/Fusion/test/Fusion.Aspire.Tests}/FusionPipelineTests.cs (88%) rename src/{Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests => HotChocolate/Fusion/test/Fusion.Aspire.Tests}/FusionReleaseAcceptanceTests.cs (50%) create mode 100644 src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/FusionDeploymentWorkflowTests.cs rename src/{Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs => HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroFusionApiTests.cs} (93%) delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs delete mode 100644 src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs delete mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj delete mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj delete mode 100644 src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs diff --git a/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj b/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj index b498c1b5670..6ae8786528e 100644 --- a/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj +++ b/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj @@ -10,10 +10,6 @@ - - - - - diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/ASPIRE_PUBLISH_AND_DEPLOY.md similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Client/ASPIRE_PUBLISH_AND_DEPLOY.md rename to src/HotChocolate/Fusion/src/Fusion.Aspire/ASPIRE_PUBLISH_AND_DEPLOY.md diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs similarity index 98% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs index 5322523cad6..9a0560c88d7 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/BoundedMemoryStream.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs @@ -1,4 +1,4 @@ -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; internal sealed class BoundedMemoryStream(int maximumLength) : MemoryStream diff --git a/FUSION_PUBLISHING.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md similarity index 87% rename from FUSION_PUBLISHING.md rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md index 9f15d066fd7..2fe93700c2c 100644 --- a/FUSION_PUBLISHING.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md @@ -17,10 +17,10 @@ Both invocations must evaluate the same AppHost composition and use the same `ta var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); -var nitro = builder.AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products-fusion") - .WithApiKey(nitroApiKey); +var nitro = builder.AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products-fusion") + .WithNitroApiKey(nitroApiKey); nitro.AddFusionDeployment("development") .ForEnvironment("Development") @@ -28,17 +28,28 @@ nitro.AddFusionDeployment("development") .WithCompositionEnvironment("development") .WithConfigurationTag(tag); -nitro.AddFusionDeployment("test") - .ForEnvironment("Test") - .ToStage("test") - .WithCompositionEnvironment("test") - .WithConfigurationTag(tag); +nitro.AddFusionDeployment("production") + .ForEnvironment("Production") + .ToStage("production") + .WithCompositionEnvironment("production") + .WithConfigurationTag(tag) + .WithApproval(waitForApproval: true) + .WithForce(false) + .WithTimeouts( + operation: TimeSpan.FromMinutes(15), + approval: TimeSpan.FromHours(2)); ``` `ForEnvironment` selects the Aspire invocation. `ToStage` selects the Nitro stage. `WithCompositionEnvironment` selects the environment block in each source's `schema-settings.json`. `WithConfigurationTag` supplies both the source version and Fusion -configuration tag. +configuration tag. `WithApproval`, `WithForce`, and `WithTimeouts` configure the publication +itself. + +`WithNitroCloudUrl` and `WithNitroApiId` default to the `Nitro:CloudUrl` and `Nitro:ApiId` +configuration values, or to the `NITRO_CLOUD_URL` and `NITRO_API_ID` environment variables. When +`WithNitroApiKey` is not configured, the credential is resolved from `NITRO_API_KEY` or the Nitro +CLI session. The effective source name is `SourceSchemaName` when explicitly declared, otherwise the Aspire resource name. Effective names must be unique and portable path segments. diff --git a/FUSION_ASPIRE_PUBLISH_DEPLOY.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md similarity index 98% rename from FUSION_ASPIRE_PUBLISH_DEPLOY.md rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md index b331e4d4dc0..7e503201707 100644 --- a/FUSION_ASPIRE_PUBLISH_DEPLOY.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md @@ -45,10 +45,10 @@ var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); var nitro = builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products-fusion") - .WithApiKey(nitroApiKey); + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products-fusion") + .WithNitroApiKey(nitroApiKey); nitro .AddFusionDeployment("production") @@ -110,8 +110,8 @@ never written to output or logs. | Concern | Recommended input | | --- | --- | -| Cloud URL | Explicit `.WithCloudUrl(...)` HTTPS origin | -| API ID | Explicit `.WithApiId(...)` | +| Cloud URL | Explicit `.WithNitroCloudUrl(...)` HTTPS origin | +| API ID | Explicit `.WithNitroApiId(...)` | | API key | Secret `ParameterResource` | | Aspire environment | Explicit `.ForEnvironment(...)` | | Nitro stage | Explicit `.ToStage(...)` | diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs similarity index 84% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs index 849ca7e2ae2..d02386bf199 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionDeploymentResource.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs @@ -1,16 +1,16 @@ using Aspire.Hosting.ApplicationModel; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; /// /// Represents an environment-specific Fusion deployment to Nitro. /// public sealed class FusionDeploymentResource( string name, - NitroResource nitro) + NitroPublishTargetResource nitro) : Resource(name) { - internal NitroResource Nitro { get; } = nitro; + internal NitroPublishTargetResource Nitro { get; } = nitro; internal string? EnvironmentName { get; set; } diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs similarity index 93% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs index 5c081fc6b23..566b7b3fe61 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipeline.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; #pragma warning disable ASPIREPIPELINES001 @@ -81,7 +81,7 @@ internal static IResourceWithEndpoints GetCompositionResource( }; } - private static IEnumerable CreateSteps( + internal static IEnumerable CreateSteps( PipelineStepFactoryContext context, FusionPipelineTopology topology) { @@ -102,13 +102,6 @@ private static IEnumerable CreateSteps( session); } - internal static PipelineStep[] CreateStepDefinitionsForTest( - IResource resource) - => CreateStepDefinitions( - resource, - new FusionPipelineTopology(), - new FusionPipelineSession()); - private static PipelineStep[] CreateStepDefinitions( IResource resource, FusionPipelineTopology topology, @@ -322,20 +315,20 @@ internal static PipelineStep[] SelectResourceDeploymentSteps( } private static Task ExecuteArtifactsAsync(PipelineStepContext context) - => GetExecutor(context).CreateArtifactsAsync(context); + => FusionPipelineExecutor.Instance.CreateArtifactsAsync(context); private static async Task ExecuteSessionStepAsync( PipelineStepContext context, FusionPipelineSession session, Func< - IFusionPipelineExecutor, + FusionPipelineExecutor, PipelineStepContext, FusionPipelineSession, Task> execute) { try { - await execute(GetExecutor(context), context, session); + await execute(FusionPipelineExecutor.Instance, context, session); } catch { @@ -356,12 +349,7 @@ internal static void EnsureResourceDeploymentOrdering( } private static Task ExecuteUploadAsync(PipelineStepContext context) - => GetExecutor(context).UploadAsync(context); - - private static IFusionPipelineExecutor GetExecutor( - PipelineStepContext context) - => context.Services.GetService() - ?? FusionPipelineExecutor.Instance; + => FusionPipelineExecutor.Instance.UploadAsync(context); private static void ValidateDeclaration( FusionDeploymentResource deployment) @@ -417,14 +405,6 @@ private static void ValidateDeclaration( } } - private sealed class FusionPipelineTopology - { - public bool HasDeployments { get; set; } - - public HashSet ResourcesWithoutCompute { get; } = - new(StringComparer.Ordinal); - } - private sealed class FusionDeploymentKeyComparer : IEqualityComparer<(string? CloudUrl, string? ApiId, string? StageName)> { @@ -446,4 +426,12 @@ public int GetHashCode( } } +internal sealed class FusionPipelineTopology +{ + public bool HasDeployments { get; set; } + + public HashSet ResourcesWithoutCompute { get; } = + new(StringComparer.Ordinal); +} + #pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs similarity index 92% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs index 36d7e792ae1..09eeee7bcfb 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineExecutor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs @@ -3,22 +3,21 @@ using System.Text.Json; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; -using ChilliCream.Nitro.Fusion; -using HotChocolate.Fusion; -using HotChocolate.Fusion.Aspire; +using HotChocolate.Fusion.Aspire.Nitro; using HotChocolate.Fusion.Packaging; using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using IOPath = System.IO.Path; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; #pragma warning disable ASPIREPIPELINES001 #pragma warning disable ASPIREPIPELINES004 -internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor +internal sealed class FusionPipelineExecutor { private static readonly TimeSpan s_readinessRetryDelay = TimeSpan.FromSeconds(1); @@ -29,17 +28,14 @@ internal sealed class FusionPipelineExecutor : IFusionPipelineExecutor }; private readonly FusionPipelineMemoryLimits _memoryLimits; - private readonly Action? _bufferCleared; - internal FusionPipelineExecutor( - FusionPipelineMemoryLimits? memoryLimits = null, - Action? bufferCleared = null) + internal FusionPipelineExecutor(FusionPipelineMemoryLimits memoryLimits) { - _memoryLimits = memoryLimits ?? FusionPipelineMemoryLimits.Default; - _bufferCleared = bufferCleared; + _memoryLimits = memoryLimits; } - public static FusionPipelineExecutor Instance { get; } = new(); + public static FusionPipelineExecutor Instance { get; } = + new(FusionPipelineMemoryLimits.Default); public async Task CreateArtifactsAsync(PipelineStepContext context) { @@ -110,8 +106,7 @@ public async Task VerifyReadinessAsync( { foreach (var deployment in deployments) { - using var lease = session.Acquire(deployment); - var state = lease.State; + var state = session.GetState(deployment); await ValidateSessionStateAsync( state, deployment, @@ -167,7 +162,7 @@ public async Task UploadAsync(PipelineStepContext context) return; } - var workflow = context.Services.GetRequiredService(); + var workflow = context.Services.GetRequiredService(); foreach (var group in artifacts.GroupBy(artifact => artifact.Deployment)) { @@ -206,7 +201,7 @@ public async Task PreflightAsync( } var workflow = context.Services - .GetRequiredService(); + .GetRequiredService(); var sourceNames = GetSourceNames(context.Model); var preparedStates = new List<( FusionDeploymentResource Deployment, @@ -262,7 +257,7 @@ await ValidateCanonicalDigestAsync( { if (archive is not null) { - ClearOwnedBuffer(archive); + Array.Clear(archive); } } } @@ -312,15 +307,14 @@ public async Task DownloadAsync( } var workflow = context.Services - .GetRequiredService(); + .GetRequiredService(); long totalSourceBytes = 0; try { foreach (var deployment in deployments) { - using var lease = session.Acquire(deployment); - var state = lease.State; + var state = session.GetState(deployment); await ValidatePreflightStateAsync( state, deployment, @@ -382,7 +376,7 @@ await ValidateCanonicalDigestAsync( { if (archive is not null) { - ClearOwnedBuffer(archive); + Array.Clear(archive); } } } @@ -397,7 +391,7 @@ await ValidateCanonicalDigestAsync( { foreach (var source in sources) { - ClearOwnedBuffer(source.Archive); + Array.Clear(source.Archive); } } } @@ -434,8 +428,7 @@ public async Task ComposeAsync( { foreach (var deployment in deployments) { - using var lease = session.Acquire(deployment); - var state = lease.State; + var state = session.GetState(deployment); await ValidateSessionStateAsync( state, deployment, @@ -495,7 +488,7 @@ public async Task PublishAsync( } var workflow = context.Services - .GetRequiredService(); + .GetRequiredService(); var compositionResource = FusionPipeline.GetCompositionResource( context.Model); var composition = GraphQLResourceModel.GetComposition( @@ -505,8 +498,7 @@ public async Task PublishAsync( { foreach (var deployment in deployments) { - using var lease = session.Acquire(deployment); - var state = lease.State; + var state = session.GetState(deployment); await ValidateSessionStateAsync( state, deployment, @@ -662,22 +654,22 @@ internal async Task> MaterializeArchivesAsyn deployment, context.CancellationToken); var deploymentDirectory = GetDeploymentDirectory(output, deployment); - var materializedDirectory = Path.Combine( + var materializedDirectory = IOPath.Combine( deploymentDirectory, "materialized"); Directory.CreateDirectory(materializedDirectory); foreach (var sourceDirectory in Directory.EnumerateDirectories( - Path.Combine(deploymentDirectory, "sources")) - .OrderBy(Path.GetFileName, StringComparer.Ordinal)) + IOPath.Combine(deploymentDirectory, "sources")) + .OrderBy(IOPath.GetFileName, StringComparer.Ordinal)) { - var name = Path.GetFileName(sourceDirectory); + var name = IOPath.GetFileName(sourceDirectory); var sourceVersion = tag; ValidatePathSegment(sourceVersion, "source version"); var schema = await File.ReadAllBytesAsync( - Path.Combine(sourceDirectory, "schema.graphqls"), + IOPath.Combine(sourceDirectory, "schema.graphqls"), context.CancellationToken); - var settingsPath = Path.Combine( + var settingsPath = IOPath.Combine( sourceDirectory, "schema-settings.template.json"); using var settings = JsonDocument.Parse( @@ -694,7 +686,7 @@ await File.ReadAllTextAsync( var endpoint = GetTransportEndpoint(resolvedSettings); RejectLoopbackEndpoint(endpoint); - var archivePath = Path.Combine( + var archivePath = IOPath.Combine( materializedDirectory, $"{name}-{sourceVersion}.zip"); await CreateArchiveAsync( @@ -727,16 +719,17 @@ private static async Task CreateDeploymentArtifactsAsync( CancellationToken cancellationToken) { var deploymentDirectory = GetDeploymentDirectory(output, deployment); - var fusionDirectory = Path.GetDirectoryName(deploymentDirectory)!; + var fusionDirectory = IOPath.GetDirectoryName(deploymentDirectory)!; Directory.CreateDirectory(fusionDirectory); - var temporaryDirectory = Path.Combine( + var temporaryDirectory = IOPath.Combine( fusionDirectory, $".{deployment.Name}.{Guid.NewGuid():N}.tmp"); try { - var sourcesDirectory = Path.Combine(temporaryDirectory, "sources"); + var sourcesDirectory = IOPath.Combine(temporaryDirectory, "sources"); Directory.CreateDirectory(sourcesDirectory); + var exportDirectory = IOPath.Combine(temporaryDirectory, "export"); var sourceNames = new List(sources.Count); foreach (var source in sources) @@ -745,9 +738,16 @@ private static async Task CreateDeploymentArtifactsAsync( await CreateSourceArtifactsAsync( source, sourcesDirectory, + exportDirectory, cancellationToken)); } + // The export scratch space must not become part of the published output. + if (Directory.Exists(exportDirectory)) + { + Directory.Delete(exportDirectory, recursive: true); + } + var template = new FusionDeploymentTemplate( FormatVersion: 1, CloudUrl: deployment.Nitro.CloudUrl!, @@ -760,7 +760,7 @@ await CreateSourceArtifactsAsync( Sources: sourceNames.Order(StringComparer.Ordinal).ToArray()); await WriteJsonAtomicallyAsync( - Path.Combine(temporaryDirectory, "nitro-deployment-template.json"), + IOPath.Combine(temporaryDirectory, "nitro-deployment-template.json"), template, cancellationToken); @@ -784,13 +784,13 @@ internal static void ReplaceDirectoryAtomically( $"Replacement directory '{sourceDirectory}' does not exist."); } - var destinationParent = Path.GetDirectoryName(destinationDirectory) + var destinationParent = IOPath.GetDirectoryName(destinationDirectory) ?? throw new InvalidOperationException( $"Replacement destination '{destinationDirectory}' has no parent directory."); Directory.CreateDirectory(destinationParent); - var backupDirectory = Path.Combine( + var backupDirectory = IOPath.Combine( destinationParent, - $".{Path.GetFileName(destinationDirectory)}.{Guid.NewGuid():N}.bak"); + $".{IOPath.GetFileName(destinationDirectory)}.{Guid.NewGuid():N}.bak"); var movedDestination = false; try @@ -830,6 +830,7 @@ internal static void ReplaceDirectoryAtomically( private static async Task CreateSourceArtifactsAsync( GraphQLSourceSchemaResource sourceSchema, string sourcesDirectory, + string exportDirectory, CancellationToken cancellationToken) { var source = sourceSchema.Resource; @@ -842,7 +843,6 @@ private static async Task CreateSourceArtifactsAsync( string configuration; string? targetFramework; string? runtimeIdentifier; - using var temporaryExportDirectory = new TemporaryDirectoryScope(); switch (declaration.Location) { @@ -860,12 +860,10 @@ private static async Task CreateSourceArtifactsAsync( break; case SourceSchemaLocationType.CommandLineExport: - var exportDirectory = - temporaryExportDirectory.Create(source.Name); var export = await CommandLineSchemaExporter.ExportAsync( source, declaration, - exportDirectory, + IOPath.Combine(exportDirectory, source.Name), cancellationToken); schemaPath = export.SchemaPath; settingsPath = export.SettingsPath; @@ -921,10 +919,10 @@ private static async Task CreateSourceArtifactsAsync( schema, extensions); - var sourceDirectory = Path.Combine(sourcesDirectory, name); + var sourceDirectory = IOPath.Combine(sourcesDirectory, name); Directory.CreateDirectory(sourceDirectory); - var destinationSchema = Path.Combine(sourceDirectory, "schema.graphqls"); - var destinationSettings = Path.Combine( + var destinationSchema = IOPath.Combine(sourceDirectory, "schema.graphqls"); + var destinationSettings = IOPath.Combine( sourceDirectory, "schema-settings.template.json"); await File.WriteAllTextAsync( @@ -939,7 +937,7 @@ await File.WriteAllTextAsync( if (extensions is not null) { await File.WriteAllTextAsync( - Path.Combine(sourceDirectory, "schema-extensions.graphqls"), + IOPath.Combine(sourceDirectory, "schema-extensions.graphqls"), extensions, cancellationToken); } @@ -958,10 +956,10 @@ await File.WriteAllTextAsync( TargetFramework: targetFramework, RuntimeIdentifier: runtimeIdentifier, LaunchProfile: false, - WorkingDirectory: Path.GetDirectoryName(projectPath)!); + WorkingDirectory: IOPath.GetDirectoryName(projectPath)!); await WriteJsonAtomicallyAsync( - Path.Combine(sourceDirectory, "provenance.json"), + IOPath.Combine(sourceDirectory, "provenance.json"), provenance, cancellationToken); @@ -1082,7 +1080,7 @@ internal static IReadOnlyList GetSourceNames( private static async Task DownloadExactSourceAsync( - IFusionDeploymentWorkflow workflow, + FusionDeploymentWorkflow workflow, FusionTarget target, FusionDeploymentResource deployment, string sourceName, @@ -1113,12 +1111,6 @@ private static async Task return download; } - private void ClearOwnedBuffer(byte[] buffer) - { - Array.Clear(buffer); - _bufferCleared?.Invoke(buffer); - } - private void AddSourceBytes( int sourceBytes, string sourceName, @@ -1375,7 +1367,7 @@ private static void ValidateSettingsName( private static string? GetExtensionsPath(string sourceDirectory) { - var path = Path.Combine( + var path = IOPath.Combine( sourceDirectory, "schema-extensions.graphqls"); return File.Exists(path) ? path : null; @@ -1448,7 +1440,7 @@ internal void TransferComposition( { if (!transferred) { - ClearOwnedBuffer(fusionArchive); + Array.Clear(fusionArchive); } } } @@ -1493,7 +1485,7 @@ private static async Task WriteJsonAtomicallyAsync( T value, CancellationToken cancellationToken) { - Directory.CreateDirectory(Path.GetDirectoryName(path)!); + Directory.CreateDirectory(IOPath.GetDirectoryName(path)!); var temporaryPath = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try @@ -1521,7 +1513,7 @@ await JsonSerializer.SerializeAsync( private static string GetDeploymentDirectory( string output, FusionDeploymentResource deployment) - => Path.Combine(output, "fusion", deployment.Name); + => IOPath.Combine(output, "fusion", deployment.Name); private static void DeleteDirectoryBestEffort(string path) { @@ -1539,35 +1531,6 @@ private static void DeleteDirectoryBestEffort(string path) { } } - - private sealed class TemporaryDirectoryScope : IDisposable - { - private string? _path; - - public string Create(string resourceName) - { - _path = Path.Combine( - Path.GetTempPath(), - "chilicream-nitro-aspire", - Guid.NewGuid().ToString("N"), - resourceName); - return _path; - } - - public void Dispose() - { - try - { - if (_path is not null && Directory.Exists(_path)) - { - Directory.Delete(_path, recursive: true); - } - } - catch (IOException) - { - } - } - } } internal sealed record FusionSourceArtifact( diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineResource.cs similarity index 75% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineResource.cs index fac5c024f31..19fe0f4ec66 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineResource.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineResource.cs @@ -1,5 +1,5 @@ using Aspire.Hosting.ApplicationModel; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; internal sealed class FusionPipelineResource(string name) : Resource(name); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs similarity index 79% rename from src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs index 16fb74e3a70..4f1987bedbc 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/FusionPipelineSession.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs @@ -1,22 +1,19 @@ -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; internal sealed class FusionPipelineSession : IDisposable { private readonly object _sync = new(); private readonly Dictionary< FusionDeploymentResource, - FusionDeploymentSessionState> _deployments = new( - ReferenceEqualityComparer.Instance); + FusionDeploymentSessionState> _deployments = []; private readonly FusionPipelineMemoryLimits _memoryLimits; private readonly CancellationToken _cancellationToken; private readonly CancellationTokenRegistration _cancellationRegistration; - private int _activeLeases; - private bool _clearRequested; private bool _canceled; private bool _disposed; public FusionPipelineSession( - CancellationToken cancellationToken = default, + CancellationToken cancellationToken, FusionPipelineMemoryLimits? memoryLimits = null) { _memoryLimits = memoryLimits ?? FusionPipelineMemoryLimits.Default; @@ -44,8 +41,7 @@ public void SetAll( { ArgumentNullException.ThrowIfNull(deployments); - var uniqueDeployments = new HashSet( - ReferenceEqualityComparer.Instance); + var uniqueDeployments = new HashSet(); foreach (var (deployment, _) in deployments) { if (!uniqueDeployments.Add(deployment)) @@ -74,13 +70,7 @@ public void SetAll( throw new OperationCanceledException(_cancellationToken); } - if (_clearRequested || _activeLeases > 0) - { - throw new InvalidOperationException( - "Fusion pipeline state cannot be replaced while it is in use."); - } - - ClearCore(); + Clear(); foreach (var (deployment, state) in deployments) { _deployments.Add(deployment, state); @@ -88,7 +78,7 @@ public void SetAll( } } - public FusionPipelineSessionLease Acquire( + public FusionDeploymentSessionState GetState( FusionDeploymentResource deployment) { ArgumentNullException.ThrowIfNull(deployment); @@ -101,20 +91,13 @@ public FusionPipelineSessionLease Acquire( throw new OperationCanceledException(_cancellationToken); } - if (_clearRequested) - { - throw new InvalidOperationException( - "Fusion pipeline state is being cleared."); - } - if (!_deployments.TryGetValue(deployment, out var state)) { throw new InvalidOperationException( $"Fusion sources for deployment '{deployment.Name}' have not been downloaded."); } - _activeLeases++; - return new FusionPipelineSessionLease(this, state); + return state; } } @@ -122,7 +105,8 @@ public void Clear() { lock (_sync) { - RequestClear(); + DisposeStates(_deployments.Values); + _deployments.Clear(); } } @@ -131,45 +115,10 @@ private void Cancel() lock (_sync) { _canceled = true; - RequestClear(); - } - } - - private void RequestClear() - { - _clearRequested = true; - if (_activeLeases == 0) - { - ClearCore(); - if (!_disposed && !_canceled) - { - _clearRequested = false; - } + Clear(); } } - private void Release() - { - lock (_sync) - { - _activeLeases--; - if (_activeLeases == 0 && _clearRequested) - { - ClearCore(); - if (!_disposed && !_canceled) - { - _clearRequested = false; - } - } - } - } - - private void ClearCore() - { - DisposeStates(_deployments.Values); - _deployments.Clear(); - } - private static void DisposeStates( IEnumerable states) { @@ -189,26 +138,12 @@ public void Dispose() } _disposed = true; - RequestClear(); } + // Disposed outside the lock: it waits for a running cancellation + // callback, which itself needs the lock. _cancellationRegistration.Dispose(); - } - - internal sealed class FusionPipelineSessionLease( - FusionPipelineSession session, - FusionDeploymentSessionState state) - : IDisposable - { - private FusionPipelineSession? _session = session; - - public FusionDeploymentSessionState State { get; } = state; - - public void Dispose() - { - var current = Interlocked.Exchange(ref _session, null); - current?.Release(); - } + Clear(); } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj b/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj index 1344b5e4f7c..7a1c3f20279 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/HotChocolate.Fusion.Aspire.csproj @@ -13,7 +13,6 @@ - @@ -33,21 +32,13 @@ - - - - - - + + - - - - - - + + diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionArchiveContent.cs similarity index 98% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionArchiveContent.cs index 359a616d298..0d1053ca765 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionArchiveContent.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionArchiveContent.cs @@ -6,7 +6,7 @@ using HotChocolate.Fusion.SourceSchema.Packaging; using HotChocolate.Language; -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; internal sealed record FusionArchiveContent( string Schema, @@ -14,7 +14,7 @@ internal sealed record FusionArchiveContent( string Settings) { private static ReadOnlySpan DigestDomain - => "ChilliCream.Nitro.Fusion.SourceSchemaContent.v1"u8; + => "HotChocolate.Fusion.Aspire.SourceSchemaContent.v1"u8; public static async Task ReadAsync( string archivePath, diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentException.cs similarity index 77% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentException.cs index 73ab3035a91..3958b825f90 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentException.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentException.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// The base exception for failures reported by the Fusion deployment workflow. /// -public class FusionDeploymentException : Exception +internal class FusionDeploymentException : Exception { public FusionDeploymentException(string message) : base(message) diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs similarity index 91% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs index 1d30d75f281..2a2cca4948b 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionDeploymentWorkflow.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs @@ -1,12 +1,16 @@ using System.Security.Cryptography; -using ChilliCream.Nitro.Fusion.Transport; -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; -internal sealed class FusionDeploymentWorkflow( - IFusionDeploymentTransportFactory transportFactory) - : IFusionDeploymentWorkflow +/// +/// Reconciles Fusion source schema versions and publishes Fusion configurations. +/// +internal sealed class FusionDeploymentWorkflow(NitroFusionApi api) { + /// + /// Downloads an exact immutable source schema version and computes its canonical content + /// identity. The caller owns a non-null result and must dispose it after use. + /// public async Task DownloadSourceSchemaAsync( FusionTarget target, FusionSourceSchemaVersion source, @@ -15,10 +19,8 @@ internal sealed class FusionDeploymentWorkflow( ValidateTarget(target); ValidateSourceVersion(source); - await using var transport = await transportFactory.OpenAsync( + var archive = await api.DownloadSourceSchemaAsync( target, - cancellationToken); - var archive = await transport.DownloadSourceSchemaAsync( source.Name, source.Version, cancellationToken); @@ -54,6 +56,9 @@ internal sealed class FusionDeploymentWorkflow( } } + /// + /// Ensures that an immutable source schema version exists with the expected content. + /// public async Task ReconcileSourceSchemaAsync( FusionTarget target, FusionSourceSchemaUpload source, @@ -69,10 +74,8 @@ public async Task ReconcileSourceSchemaAsync( var localContentSha256 = localContent.ComputeSha256(cancellationToken); await VerifySha256Async(source, cancellationToken); - await using var transport = await transportFactory.OpenAsync( + var remoteArchive = await api.DownloadSourceSchemaAsync( target, - cancellationToken); - var remoteArchive = await transport.DownloadSourceSchemaAsync( source.Name, source.Version, cancellationToken); @@ -90,7 +93,8 @@ await EnsureContentMatchesAndClearAsync( FusionRemoteCommandResult uploadResult; try { - uploadResult = await transport.UploadSourceSchemaAsync( + uploadResult = await api.UploadSourceSchemaAsync( + target, source.Version, source.ArchivePath, cancellationToken); @@ -103,7 +107,8 @@ await EnsureContentMatchesAndClearAsync( catch (Exception exception) { await ReconcileAfterUncertainUploadAsync( - transport, + api, + target, source, localContentSha256, exception, @@ -118,7 +123,8 @@ await ReconcileAfterUncertainUploadAsync( if (uploadResult.IsDuplicate) { - var racedArchive = await transport.DownloadSourceSchemaAsync( + var racedArchive = await api.DownloadSourceSchemaAsync( + target, source.Name, source.Version, cancellationToken); @@ -143,6 +149,9 @@ await EnsureContentMatchesAndClearAsync( uploadResult.Errors); } + /// + /// Publishes a locally composed Fusion archive and waits for a verified terminal result. + /// public async Task PublishAsync( FusionPublicationRequest request, ReadOnlyMemory fusionArchive, @@ -158,14 +167,11 @@ public async Task PublishAsync( try { - await using var transport = await transportFactory.OpenAsync( - request.Target, - operationToken); FusionRemoteBeginResult begin; try { - begin = await transport.BeginPublishAsync( + begin = await api.BeginPublishAsync( request, operationToken); } @@ -193,7 +199,8 @@ public async Task PublishAsync( + "returning a request identifier."); } - await using var events = transport.WatchPublishAsync( + await using var events = api.WatchPublishAsync( + request.Target, requestId, operationToken) .GetAsyncEnumerator(operationToken); @@ -208,14 +215,18 @@ public async Task PublishAsync( } await EnsureRemoteCommandSucceededAsync( - () => transport.ClaimPublishAsync(requestId, operationToken), + () => api.ClaimPublishAsync( + request.Target, + requestId, + operationToken), "claim the Fusion publication slot", requestId); if (!request.WaitForApproval) { await EnsureRemoteCommandSucceededAsync( - () => transport.ValidatePublishAsync( + () => api.ValidatePublishAsync( + request.Target, requestId, fusionArchive, operationToken), @@ -229,7 +240,8 @@ await EnsureRemoteCommandSucceededAsync( if (validation is { Succeeded: false } && !request.Force) { await ReleaseKnownFailedValidationAsync( - transport, + api, + request.Target, requestId, operationToken); throw CreateRemoteFailure( @@ -239,7 +251,8 @@ await ReleaseKnownFailedValidationAsync( } await EnsureRemoteCommandSucceededAsync( - () => transport.CommitPublishAsync( + () => api.CommitPublishAsync( + request.Target, requestId, fusionArchive, operationToken), @@ -264,7 +277,8 @@ await WaitForTerminalPublishAsync( } private static async Task ReconcileAfterUncertainUploadAsync( - IFusionDeploymentTransport transport, + NitroFusionApi api, + FusionTarget target, FusionSourceSchemaUpload source, string localContentSha256, Exception uploadException, @@ -273,7 +287,8 @@ private static async Task ReconcileAfterUncertainUploadAsync( byte[]? remoteArchive; try { - remoteArchive = await transport.DownloadSourceSchemaAsync( + remoteArchive = await api.DownloadSourceSchemaAsync( + target, source.Name, source.Version, cancellationToken); @@ -521,11 +536,13 @@ private static async Task EnsureRemoteCommandSucceededAsync( } private static async Task ReleaseKnownFailedValidationAsync( - IFusionDeploymentTransport transport, + NitroFusionApi api, + FusionTarget target, string requestId, CancellationToken cancellationToken) { - var result = await transport.ReleasePublishAsync( + var result = await api.ReleasePublishAsync( + target, requestId, cancellationToken); if (!result.Succeeded) diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIdentityCollisionException.cs similarity index 61% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIdentityCollisionException.cs index ab12e8eb981..4cd0969a578 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIdentityCollisionException.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIdentityCollisionException.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// A source schema name and version already exist with different content. /// -public sealed class FusionIdentityCollisionException : FusionDeploymentException +internal sealed class FusionIdentityCollisionException : FusionDeploymentException { public FusionIdentityCollisionException(string message) : base(message) diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIndeterminateStateException.cs similarity index 83% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIndeterminateStateException.cs index fff49777d30..da4d0b021bf 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionIndeterminateStateException.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionIndeterminateStateException.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// A remote operation may have changed state, but its result could not be verified. /// -public sealed class FusionIndeterminateStateException : FusionDeploymentException +internal sealed class FusionIndeterminateStateException : FusionDeploymentException { public FusionIndeterminateStateException(string message, string? requestId = null) : base(message) diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionPublicationRequest.cs similarity index 77% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionPublicationRequest.cs index f0eb321f6c3..b931ecec6b2 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionPublicationRequest.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionPublicationRequest.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// Describes a Fusion configuration publication. /// -public sealed record FusionPublicationRequest( +internal sealed record FusionPublicationRequest( FusionTarget Target, string Stage, string ConfigurationTag, diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaContent.cs similarity index 95% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaContent.cs index 16f086ba048..97aada0b786 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaContent.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaContent.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// Computes the canonical content identity of a Fusion source schema archive. /// -public static class FusionSourceSchemaContent +internal static class FusionSourceSchemaContent { /// /// Computes a SHA-256 over the normalized schema, settings, and extensions. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaDownload.cs similarity index 95% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaDownload.cs index 94e94316498..45db0d066c3 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaDownload.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaDownload.cs @@ -1,4 +1,4 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// Owns an exact immutable source schema archive downloaded from Nitro together with its @@ -8,7 +8,7 @@ namespace ChilliCream.Nitro.Fusion; /// The caller must dispose this instance after consuming . Disposal clears /// the owned archive buffer. /// -public sealed class FusionSourceSchemaDownload : IDisposable +internal sealed class FusionSourceSchemaDownload : IDisposable { private byte[]? _archive; diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaUpload.cs similarity index 66% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaUpload.cs index a06cd096c9a..6fcadf59422 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaUpload.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaUpload.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// Describes an immutable Fusion source schema archive to reconcile. /// -public sealed record FusionSourceSchemaUpload( +internal sealed record FusionSourceSchemaUpload( string Name, string Version, string ArchivePath, diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaVersion.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaVersion.cs new file mode 100644 index 00000000000..9e188fe4d02 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionSourceSchemaVersion.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// Selects one immutable source schema version for a Fusion publication. +/// +internal sealed record FusionSourceSchemaVersion(string Name, string Version); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionTarget.cs similarity index 61% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionTarget.cs index cddc1edc94b..e98a918ddbe 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionTarget.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionTarget.cs @@ -1,9 +1,9 @@ -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; /// /// Identifies a Nitro API and the credentials used to access it. /// -public sealed record FusionTarget(Uri CloudUrl, string ApiId, string ApiKey) +internal sealed record FusionTarget(Uri CloudUrl, string ApiId, string ApiKey) { /// public override string ToString() => $"{CloudUrl} (API {ApiId})"; diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs new file mode 100644 index 00000000000..28188154bcb --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs @@ -0,0 +1,699 @@ +using System.Buffers; +using System.Net; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json; +using HotChocolate.Transport; +using HotChocolate.Transport.Http; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// Sends the Nitro API operations of the Fusion deployment workflow. Every operation is bounded +/// by the cancellation token of its caller, and results are read as JSON without an intermediate +/// object model. +/// +internal sealed class NitroFusionApi : IDisposable +{ + internal const int MaxSourceSchemaArchiveSize = 128_000_000; + + private const string SourceSchemaArchiveFileName = "source-schema.zip"; + private const string ValidatedConfigurationFileName = "gateway.fgp"; + private const string CommittedConfigurationFileName = "gateway.far"; + private const string ArchiveContentType = "application/zip"; + + private readonly HttpClient _httpClient; + private readonly bool _disposeHttpClient; + private readonly GraphQLHttpClient _client; + + /// + /// Initializes the Nitro API over an existing HTTP client. + /// + /// + /// The HTTP client that sends the requests. It must not impose a timeout of its own, because + /// a publication is observed over a long-running subscription. + /// + /// + /// Specifies whether is disposed with this instance. + /// + public NitroFusionApi(HttpClient httpClient, bool disposeHttpClient) + { + ArgumentNullException.ThrowIfNull(httpClient); + + _httpClient = httpClient; + _disposeHttpClient = disposeHttpClient; + _client = GraphQLHttpClient.Create(httpClient, disposeHttpClient: false); + } + + /// + /// Creates the Nitro API with an HTTP client that it owns. + /// + public static NitroFusionApi Create() + { + var httpClient = new HttpClient + { + // A publication is observed over a subscription that stays open for as long as Nitro + // takes to reach a terminal state, so every bound comes from a cancellation token. + Timeout = Timeout.InfiniteTimeSpan, + DefaultRequestVersion = new Version(2, 0), + DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower + }; + + return new NitroFusionApi(httpClient, disposeHttpClient: true); + } + + /// + /// Downloads an exact immutable source schema version, or returns null when the + /// version does not exist. The caller owns a non-null result and must clear it after use. + /// + public async Task DownloadSourceSchemaAsync( + FusionTarget target, + string name, + string version, + CancellationToken cancellationToken) + { + var requestUri = new Uri( + target.CloudUrl, + $"/api/v1/apis/{Uri.EscapeDataString(target.ApiId)}" + + $"/fusion-subgraphs/{Uri.EscapeDataString(name)}" + + $"/versions/{Uri.EscapeDataString(version)}/download"); + using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + NitroRequestHeaders.Apply(request, CreateCredential(target)); + + using var response = await _httpClient.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + if (response.StatusCode is HttpStatusCode.NotFound) + { + return null; + } + + EnsureSuccess(response); + + return await ReadSourceSchemaArchiveAsync( + response.Content, + MaxSourceSchemaArchiveSize, + cancellationToken); + } + + /// + /// Uploads an immutable source schema version. + /// + public async Task UploadSourceSchemaAsync( + FusionTarget target, + string version, + string archivePath, + CancellationToken cancellationToken) + { + var variables = new Dictionary + { + ["input"] = new Dictionary + { + ["apiId"] = target.ApiId, + ["tag"] = version, + ["archive"] = new FileReference( + () => File.OpenRead(archivePath), + SourceSchemaArchiveFileName, + ArchiveContentType) + } + }; + + var payload = await SendAsync( + target, + NitroOperationDocuments.UploadSourceSchemaOperationName, + "uploadFusionSubgraph", + variables, + enableFileUploads: true, + cancellationToken); + var errors = GetErrors(payload); + var hasVersion = + payload.TryGetProperty("fusionSubgraphVersion", out var uploadedVersion) + && uploadedVersion.ValueKind is JsonValueKind.Object; + + return new FusionRemoteCommandResult( + hasVersion && errors.Count is 0, + HasError(payload, "DuplicatedTagError"), + errors); + } + + /// + /// Opens a publication request. + /// + public async Task BeginPublishAsync( + FusionPublicationRequest request, + CancellationToken cancellationToken) + { + var variables = new Dictionary + { + ["input"] = new Dictionary + { + ["apiId"] = request.Target.ApiId, + ["stageName"] = request.Stage, + ["tag"] = request.ConfigurationTag, + ["waitForApproval"] = request.WaitForApproval, + ["subgraphs"] = request.SourceSchemas + .Select(source => new Dictionary + { + ["name"] = source.Name, + ["tag"] = source.Version + }) + .ToArray() + } + }; + + var payload = await SendAsync( + request.Target, + NitroOperationDocuments.BeginDeploymentOperationName, + "beginFusionConfigurationPublish", + variables, + enableFileUploads: false, + cancellationToken); + + return new FusionRemoteBeginResult( + GetString(payload, "requestId"), + GetErrors(payload)); + } + + /// + /// Observes the state of a publication request until Nitro ends the subscription. + /// + public async IAsyncEnumerable WatchPublishAsync( + FusionTarget target, + string requestId, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var request = CreateRequest( + target, + NitroOperationDocuments.WatchDeploymentOperationName, + new Dictionary { ["requestId"] = requestId }, + enableFileUploads: false); + + var events = NitroGraphQL.StreamAsync( + _client, + request, + ReadRemoteEvent, + cancellationToken); + + await foreach (var remoteEvent in events.WithCancellation(cancellationToken)) + { + yield return remoteEvent; + } + } + + /// + /// Claims the publication slot of a publication request. + /// + public async Task ClaimPublishAsync( + FusionTarget target, + string requestId, + CancellationToken cancellationToken) + => ToCommandResult( + await SendAsync( + target, + NitroOperationDocuments.ClaimDeploymentOperationName, + "startFusionConfigurationComposition", + CreateRequestIdVariables(requestId), + enableFileUploads: false, + cancellationToken)); + + /// + /// Releases the publication slot of a publication request. + /// + public async Task ReleasePublishAsync( + FusionTarget target, + string requestId, + CancellationToken cancellationToken) + => ToCommandResult( + await SendAsync( + target, + NitroOperationDocuments.ReleaseDeploymentOperationName, + "cancelFusionConfigurationComposition", + CreateRequestIdVariables(requestId), + enableFileUploads: false, + cancellationToken)); + + /// + /// Hands the composed configuration to Nitro for validation. + /// + public async Task ValidatePublishAsync( + FusionTarget target, + string requestId, + ReadOnlyMemory archive, + CancellationToken cancellationToken) + => ToCommandResult( + await SendAsync( + target, + NitroOperationDocuments.ValidateDeploymentOperationName, + "validateFusionConfigurationComposition", + CreateConfigurationVariables( + requestId, + archive, + ValidatedConfigurationFileName), + enableFileUploads: true, + cancellationToken)); + + /// + /// Commits the composed configuration. + /// + public async Task CommitPublishAsync( + FusionTarget target, + string requestId, + ReadOnlyMemory archive, + CancellationToken cancellationToken) + => ToCommandResult( + await SendAsync( + target, + NitroOperationDocuments.CommitDeploymentOperationName, + "commitFusionConfigurationPublish", + CreateConfigurationVariables( + requestId, + archive, + CommittedConfigurationFileName), + enableFileUploads: true, + cancellationToken)); + + /// + public void Dispose() + { + _client.Dispose(); + + if (_disposeHttpClient) + { + _httpClient.Dispose(); + } + } + + private async Task SendAsync( + FusionTarget target, + string operationName, + string fieldName, + Dictionary variables, + bool enableFileUploads, + CancellationToken cancellationToken) + { + var result = await NitroGraphQL.SendWithRetryAsync( + _client, + CreateRequest(target, operationName, variables, enableFileUploads), + // A publication operation is not safe to send more than once. + retryTransientFailures: false, + // The publication as a whole is bounded by the cancellation token of the caller. + Timeout.InfiniteTimeSpan, + cancellationToken); + + if (result.Failure is not null) + { + throw new FusionDeploymentException(result.Failure); + } + + if (result.Data.ValueKind is not JsonValueKind.Object + || !result.Data.TryGetProperty(fieldName, out var payload) + || payload.ValueKind is not JsonValueKind.Object) + { + throw new FusionDeploymentException( + "Nitro GraphQL request returned no data."); + } + + return payload; + } + + private static GraphQLHttpRequest CreateRequest( + FusionTarget target, + string operationName, + Dictionary variables, + bool enableFileUploads) + { + return new GraphQLHttpRequest( + CreateBody(operationName, variables), + NitroApiUrl.CreateGraphQLEndpoint(target.CloudUrl)) + { + EnableFileUploads = enableFileUploads, + OnMessageCreated = (_, requestMessage, _) => + NitroRequestHeaders.Apply(requestMessage, CreateCredential(target)) + }; + } + + private static OperationRequest CreateBody( + string operationName, + Dictionary variables) + { +#if NITRO_PERSISTED_OPERATIONS + var operationId = operationName switch + { + NitroOperationDocuments.UploadSourceSchemaOperationName + => NitroOperationDocuments.GetUploadSourceSchemaOperationId(), + NitroOperationDocuments.BeginDeploymentOperationName + => NitroOperationDocuments.GetBeginDeploymentOperationId(), + NitroOperationDocuments.ClaimDeploymentOperationName + => NitroOperationDocuments.GetClaimDeploymentOperationId(), + NitroOperationDocuments.ValidateDeploymentOperationName + => NitroOperationDocuments.GetValidateDeploymentOperationId(), + NitroOperationDocuments.CommitDeploymentOperationName + => NitroOperationDocuments.GetCommitDeploymentOperationId(), + NitroOperationDocuments.ReleaseDeploymentOperationName + => NitroOperationDocuments.GetReleaseDeploymentOperationId(), + NitroOperationDocuments.WatchDeploymentOperationName + => NitroOperationDocuments.GetWatchDeploymentOperationId(), + _ => throw UnknownOperation(operationName) + }; + + return new OperationRequest( + id: operationId, + operationName: operationName, + variables: variables); +#else + var document = operationName switch + { + NitroOperationDocuments.UploadSourceSchemaOperationName + => NitroOperationDocuments.GetUploadSourceSchemaDocument(), + NitroOperationDocuments.BeginDeploymentOperationName + => NitroOperationDocuments.GetBeginDeploymentDocument(), + NitroOperationDocuments.ClaimDeploymentOperationName + => NitroOperationDocuments.GetClaimDeploymentDocument(), + NitroOperationDocuments.ValidateDeploymentOperationName + => NitroOperationDocuments.GetValidateDeploymentDocument(), + NitroOperationDocuments.CommitDeploymentOperationName + => NitroOperationDocuments.GetCommitDeploymentDocument(), + NitroOperationDocuments.ReleaseDeploymentOperationName + => NitroOperationDocuments.GetReleaseDeploymentDocument(), + NitroOperationDocuments.WatchDeploymentOperationName + => NitroOperationDocuments.GetWatchDeploymentDocument(), + _ => throw UnknownOperation(operationName) + }; + + return new OperationRequest( + document, + operationName: operationName, + variables: variables); +#endif + } + + private static InvalidOperationException UnknownOperation(string operationName) + => new($"The Nitro operation '{operationName}' is unknown."); + + private static Dictionary CreateRequestIdVariables(string requestId) + => new() + { + ["input"] = new Dictionary + { + ["requestId"] = requestId + } + }; + + private static Dictionary CreateConfigurationVariables( + string requestId, + ReadOnlyMemory archive, + string fileName) + => new() + { + ["input"] = new Dictionary + { + ["requestId"] = requestId, + ["configuration"] = new FileReference( + () => OpenRead(archive), + fileName, + ArchiveContentType) + } + }; + + private static NitroCredential CreateCredential(FusionTarget target) + => NitroCredential.FromApiKey(target.ApiKey); + + private static FusionRemoteEvent ReadRemoteEvent(OperationResult result) + { + if (result.Data.ValueKind is not JsonValueKind.Object + || !result.Data.TryGetProperty( + "onFusionConfigurationPublishingTaskChanged", + out var value) + || value.ValueKind is not JsonValueKind.Object) + { + throw new FusionDeploymentException( + "Nitro GraphQL request returned no data."); + } + + var typeName = GetString(value, "__typename"); + var kind = typeName switch + { + "ProcessingTaskIsReady" => FusionRemoteEventKind.Ready, + "ProcessingTaskIsQueued" => FusionRemoteEventKind.Queued, + "FusionConfigurationValidationSuccess" + => FusionRemoteEventKind.ValidationSucceeded, + "FusionConfigurationValidationFailed" + => FusionRemoteEventKind.ValidationFailed, + "FusionConfigurationPublishingSuccess" + => FusionRemoteEventKind.PublishingSucceeded, + "FusionConfigurationPublishingFailed" + => FusionRemoteEventKind.PublishingFailed, + "WaitForApproval" => FusionRemoteEventKind.WaitingForApproval, + "ProcessingTaskApproved" => FusionRemoteEventKind.Approved, + "OperationInProgress" or "ValidationInProgress" + => FusionRemoteEventKind.InProgress, + _ => FusionRemoteEventKind.Unknown + }; + IReadOnlyList errors = kind is FusionRemoteEventKind.PublishingFailed + or FusionRemoteEventKind.ValidationFailed + ? GetErrors(value) + : []; + + return new FusionRemoteEvent(kind, errors); + } + + private static FusionRemoteCommandResult ToCommandResult(JsonElement payload) + { + var errors = GetErrors(payload); + + return new FusionRemoteCommandResult(errors.Count is 0, false, errors); + } + + private static List GetErrors(JsonElement payload) + { + var messages = new List(); + + if (!payload.TryGetProperty("errors", out var errors) + || errors.ValueKind is not JsonValueKind.Array) + { + return messages; + } + + foreach (var error in errors.EnumerateArray()) + { + if (GetString(error, "message") is { } message) + { + messages.Add(message); + } + } + + return messages; + } + + private static bool HasError(JsonElement payload, string typeName) + { + if (!payload.TryGetProperty("errors", out var errors) + || errors.ValueKind is not JsonValueKind.Array) + { + return false; + } + + foreach (var error in errors.EnumerateArray()) + { + if (string.Equals(GetString(error, "__typename"), typeName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + private static string? GetString(JsonElement value, string propertyName) + => value.ValueKind is JsonValueKind.Object + && value.TryGetProperty(propertyName, out var property) + && property.ValueKind is JsonValueKind.String + ? property.GetString() + : null; + + private static Stream OpenRead(ReadOnlyMemory content) + { + if (MemoryMarshal.TryGetArray(content, out ArraySegment segment)) + { + return new MemoryStream( + segment.Array!, + segment.Offset, + segment.Count, + writable: false, + publiclyVisible: true); + } + + return new MemoryStream(content.ToArray(), writable: false); + } + + private static void EnsureSuccess(HttpResponseMessage response) + { + if (response.StatusCode is HttpStatusCode.Unauthorized + or HttpStatusCode.Forbidden) + { + throw new FusionDeploymentException( + "Nitro rejected the supplied API key."); + } + + if (!response.IsSuccessStatusCode) + { + throw new FusionDeploymentException( + $"Nitro returned HTTP {(int)response.StatusCode} " + + $"({response.ReasonPhrase})."); + } + } + + internal static Task ReadSourceSchemaArchiveAsync( + HttpContent content, + int maxArchiveSize, + CancellationToken cancellationToken) + => ReadSourceSchemaArchiveAsync( + content, + maxArchiveSize, + ArrayPool.Shared, + 81920, + cancellationToken); + + internal static async Task ReadSourceSchemaArchiveAsync( + HttpContent content, + int maxArchiveSize, + ArrayPool bufferPool, + int bufferSize, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(content); + ArgumentNullException.ThrowIfNull(bufferPool); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArchiveSize); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize); + + if (content.Headers.ContentLength is > 0 and var contentLength + && contentLength > maxArchiveSize) + { + throw ArchiveResponseTooLarge(maxArchiveSize); + } + + await using var stream = await content.ReadAsStreamAsync(cancellationToken); + var chunks = new List<(byte[] Buffer, int Length)>(); + var length = 0; + + try + { + var endOfStream = false; + while (!endOfStream) + { + var remaining = maxArchiveSize - length; + var readSize = remaining >= bufferSize + ? bufferSize + : remaining + 1; + var buffer = bufferPool.Rent(readSize); + var bufferLength = 0; + var retained = false; + + try + { + while (bufferLength < readSize) + { + var bytesRead = await stream.ReadAsync( + buffer.AsMemory(bufferLength, readSize - bufferLength), + cancellationToken); + + if (bytesRead is 0) + { + endOfStream = true; + break; + } + + bufferLength += bytesRead; + } + + if (bufferLength is 0) + { + continue; + } + + if (bufferLength > maxArchiveSize - length) + { + throw ArchiveResponseTooLarge(maxArchiveSize); + } + + length += bufferLength; + chunks.Add((buffer, bufferLength)); + retained = true; + } + finally + { + if (!retained) + { + bufferPool.Return(buffer, clearArray: true); + } + } + } + + var archive = GC.AllocateUninitializedArray(length); + var offset = 0; + foreach (var chunk in chunks) + { + chunk.Buffer.AsSpan(0, chunk.Length).CopyTo(archive.AsSpan(offset)); + offset += chunk.Length; + } + + return archive; + } + finally + { + foreach (var chunk in chunks) + { + bufferPool.Return(chunk.Buffer, clearArray: true); + } + } + } + + private static FusionDeploymentException ArchiveResponseTooLarge(int maxArchiveSize) + => new( + "The compressed Fusion source schema archive exceeds the maximum " + + $"allowed size of {maxArchiveSize} bytes."); +} + +/// +/// The result of a Nitro operation that either succeeds or reports why it did not. +/// +internal sealed record FusionRemoteCommandResult( + bool Succeeded, + bool IsDuplicate, + IReadOnlyList Errors) +{ + public static FusionRemoteCommandResult Success { get; } = new(true, false, []); +} + +/// +/// The result of opening a publication request. +/// +internal sealed record FusionRemoteBeginResult( + string? RequestId, + IReadOnlyList Errors); + +/// +/// A state change of a publication request. +/// +internal sealed record FusionRemoteEvent( + FusionRemoteEventKind Kind, + IReadOnlyList Errors); + +/// +/// The kind of state change that Nitro reported for a publication request. +/// +internal enum FusionRemoteEventKind +{ + Ready, + Queued, + ValidationSucceeded, + ValidationFailed, + PublishingSucceeded, + PublishingFailed, + InProgress, + WaitingForApproval, + Approved, + Unknown +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs new file mode 100644 index 00000000000..e6c67e687a2 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs @@ -0,0 +1,405 @@ +using System.Net; +using System.Runtime.CompilerServices; +using System.Text.Json; +using HotChocolate.Transport; +using HotChocolate.Transport.Http; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// Sends the GraphQL requests of the Nitro integration. Requests are bounded in time and in size, +/// transient transport failures are retried, and subscriptions are consumed over server-sent +/// events until the caller recognizes a terminal result. +/// +internal static class NitroGraphQL +{ + private const int MaxAttempts = 3; + private const int MaxResponseBytes = 2 * 1024 * 1024; + private const int MaxSubscriptionEvents = 1000; + private static readonly TimeSpan s_requestTimeout = TimeSpan.FromSeconds(15); + + /// + /// The time a single request may take when the caller has no bound of its own. + /// + public static TimeSpan DefaultRequestTimeout => s_requestTimeout; + + /// + /// Sends a request and reads its single result. + /// + /// + /// The GraphQL client that sends the request. + /// + /// + /// The request to send. + /// + /// + /// Specifies whether a transient transport failure is retried. Only requests that are safe to + /// send more than once can be retried. + /// + /// + /// The time a single attempt may take. Use when the + /// request is bounded by alone. + /// + /// + /// The cancellation token. + /// + public static async Task SendWithRetryAsync( + GraphQLHttpClient client, + GraphQLHttpRequest request, + bool retryTransientFailures, + TimeSpan requestTimeout, + CancellationToken cancellationToken) + { + for (var attempt = 1; ; attempt++) + { + try + { + using var attemptTimeout = + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + attemptTimeout.CancelAfter(requestTimeout); + + using var response = await client.SendAsync(request, attemptTimeout.Token); + if (!response.IsSuccessStatusCode) + { + if (retryTransientFailures + && IsTransient(response.StatusCode) + && attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + continue; + } + + return NitroGraphQLResult.Failed( + $"Nitro returned HTTP {(int)response.StatusCode} ({response.StatusCode})."); + } + + if (response.ContentHeaders.ContentLength is > MaxResponseBytes) + { + return NitroGraphQLResult.Failed( + "Nitro returned a schema validation response that exceeded the size limit."); + } + + using var document = await ReadBoundedJsonAsync( + response.HttpResponseMessage, + attemptTimeout.Token); + var root = document.RootElement; + if (root.TryGetProperty("errors", out var errors) + && errors.ValueKind is JsonValueKind.Array + && errors.GetArrayLength() > 0) + { + return NitroGraphQLResult.Failed(DescribeErrors(errors)); + } + + if (!root.TryGetProperty("data", out var data)) + { + return NitroGraphQLResult.Failed("Nitro returned a malformed GraphQL response."); + } + + return NitroGraphQLResult.Success(data.Clone()); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (OperationCanceledException) when ( + retryTransientFailures && attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + } + catch (HttpRequestException) when ( + retryTransientFailures && attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + } + catch (IOException) when ( + retryTransientFailures && attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + } + } + } + + /// + /// Subscribes to a Nitro subscription and waits for the terminal result of the operation. The + /// subscription is consumed over server-sent events and every event is handed to + /// , which returns null for an event that the + /// operation has not completed with. + /// + /// + /// The GraphQL client that sends the subscription. + /// + /// + /// The subscription request to send. + /// + /// + /// Reads the terminal result of the operation from an event, or returns null when the + /// operation is still running. The values of an event are only valid for the duration of the + /// call, so everything that outlives the call has to be copied. + /// + /// + /// The cancellation token. It bounds the subscription as a whole. + /// + /// + /// The terminal result of the operation, or the reason why none was received. A subscription + /// that ends without a terminal result is a failure. + /// + public static async Task<(T? Value, string? Failure)> SubscribeAsync( + GraphQLHttpClient client, + GraphQLHttpRequest request, + Func readTerminalResult, + CancellationToken cancellationToken) + where T : class + { + var (response, failure) = await ConnectAsync(client, request, cancellationToken); + + if (response is null) + { + return (null, failure); + } + + using (response) + { + // Once the first event can arrive the subscription is no longer safe to send again, + // so the stream itself is never retried. + return await ReadSubscriptionAsync(response, readTerminalResult, cancellationToken); + } + } + + /// + /// Subscribes to a Nitro subscription and reads every event of the subscription until it + /// ends. The subscription is consumed over server-sent events. + /// + /// + /// The GraphQL client that sends the subscription. + /// + /// + /// The subscription request to send. + /// + /// + /// Reads an event of the subscription. The values of an event are only valid for the duration + /// of the call, so everything that outlives the call has to be copied. + /// + /// + /// The cancellation token. It bounds the subscription as a whole. + /// + /// + /// The subscription could not be established, or Nitro ended it with an error. + /// + public static async IAsyncEnumerable StreamAsync( + GraphQLHttpClient client, + GraphQLHttpRequest request, + Func readEvent, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var (response, failure) = await ConnectAsync(client, request, cancellationToken); + + if (response is null) + { + throw new NitroSubscriptionException(failure!); + } + + using (response) + { + var eventCount = 0; + + await foreach (var result in response.ReadAsResultStreamAsync() + .WithCancellation(cancellationToken)) + { + using (result) + { + if (++eventCount > MaxSubscriptionEvents) + { + throw new NitroSubscriptionException( + "Nitro sent too many subscription events."); + } + + if (result.Errors.ValueKind is JsonValueKind.Array + && result.Errors.GetArrayLength() > 0) + { + throw new NitroSubscriptionException(DescribeErrors(result.Errors)); + } + + yield return readEvent(result); + } + } + } + } + + /// + /// Establishes a subscription over server-sent events. The returned response is owned by the + /// caller and no event has been read from it yet. + /// + private static async Task<(GraphQLHttpResponse? Response, string? Failure)> ConnectAsync( + GraphQLHttpClient client, + GraphQLHttpRequest request, + CancellationToken cancellationToken) + { + request.Accept = GraphQLHttpRequest.GraphQLOverSse; + + for (var attempt = 1; ; attempt++) + { + GraphQLHttpResponse response; + + try + { + response = await SubscribeCoreAsync(client, request, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (OperationCanceledException) when (attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + continue; + } + catch (HttpRequestException) when (attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + continue; + } + catch (IOException) when (attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + continue; + } + + if (!response.IsSuccessStatusCode) + { + var statusCode = response.StatusCode; + response.Dispose(); + + if (IsTransient(statusCode) && attempt < MaxAttempts) + { + await DelayRetryAsync(attempt, cancellationToken); + continue; + } + + return (null, $"Nitro returned HTTP {(int)statusCode} ({statusCode})."); + } + + // A server that ignores the pinned Accept header answers with a single result + // instead of a stream, which would end the subscription after one event. That is + // a protocol failure and not the end of a subscription. + var mediaType = response.ContentHeaders.ContentType?.MediaType; + if (!string.Equals(mediaType, ContentType.EventStream, StringComparison.OrdinalIgnoreCase)) + { + response.Dispose(); + + return ( + null, + "Nitro answered the subscription with the content type " + + $"'{mediaType ?? "none"}' instead of {ContentType.EventStream}."); + } + + return (response, null); + } + } + + private static async Task SubscribeCoreAsync( + GraphQLHttpClient client, + GraphQLHttpRequest request, + CancellationToken cancellationToken) + { + // The request timeout only covers the subscribe call, which completes once the response + // headers have been read. The stream that follows is bounded by the cancellation token of + // the caller, so the timeout is released before the first event is read. + using var requestTimeout = + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + requestTimeout.CancelAfter(s_requestTimeout); + + return await client.SendAsync(request, requestTimeout.Token); + } + + private static async Task<(T? Value, string? Failure)> ReadSubscriptionAsync( + GraphQLHttpResponse response, + Func readTerminalResult, + CancellationToken cancellationToken) + where T : class + { + var eventCount = 0; + + await foreach (var result in response.ReadAsResultStreamAsync() + .WithCancellation(cancellationToken)) + { + using (result) + { + if (++eventCount > MaxSubscriptionEvents) + { + return (null, "Nitro sent too many subscription events."); + } + + if (result.Errors.ValueKind is JsonValueKind.Array + && result.Errors.GetArrayLength() > 0) + { + return (null, DescribeErrors(result.Errors)); + } + + var value = readTerminalResult(result); + if (value is not null) + { + return (value, null); + } + } + } + + return (null, "Nitro ended the subscription without a result."); + } + + private static string DescribeErrors(JsonElement errors) + { + var error = errors[0]; + + return error.ValueKind is JsonValueKind.Object + && error.TryGetProperty("message", out var message) + && message.ValueKind is JsonValueKind.String + ? $"Nitro returned GraphQL errors: {message.GetString()}" + : "Nitro returned GraphQL errors."; + } + + private static async Task ReadBoundedJsonAsync( + HttpResponseMessage response, + CancellationToken cancellationToken) + { + await using var source = await response.Content.ReadAsStreamAsync(cancellationToken); + await using var destination = new MemoryStream(); + var buffer = new byte[16 * 1024]; + + while (true) + { + var read = await source.ReadAsync(buffer, cancellationToken); + if (read == 0) + { + break; + } + + if (destination.Length + read > MaxResponseBytes) + { + throw new NitroResponseTooLargeException( + "Nitro returned a schema validation response that exceeded the size limit."); + } + + destination.Write(buffer, 0, read); + } + + destination.Position = 0; + return await JsonDocument.ParseAsync(destination, default, cancellationToken); + } + + private static async Task DelayRetryAsync(int attempt, CancellationToken cancellationToken) + => await Task.Delay( + TimeSpan.FromMilliseconds(250 * (1 << (attempt - 1))), + cancellationToken); + + private static bool IsTransient(HttpStatusCode statusCode) + => statusCode is HttpStatusCode.RequestTimeout + or HttpStatusCode.TooManyRequests + || statusCode >= HttpStatusCode.InternalServerError; + + private sealed class NitroResponseTooLargeException(string message) : Exception(message); +} + +/// +/// A Nitro subscription could not be established, or Nitro ended it with an error. +/// +internal sealed class NitroSubscriptionException(string message) : Exception(message); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs new file mode 100644 index 00000000000..d55d41fbd59 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs @@ -0,0 +1,32 @@ +using System.Text.Json; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// The result of a single Nitro GraphQL request. Either the data of the response or the reason +/// why Nitro was unavailable. +/// +internal sealed class NitroGraphQLResult +{ + private NitroGraphQLResult(JsonElement data, string? failure) + { + Data = data; + Failure = failure; + } + + /// + /// Gets the data of the response. + /// + public JsonElement Data { get; } + + /// + /// Gets the reason why Nitro was unavailable, or null when the request succeeded. + /// + public string? Failure { get; } + + public static NitroGraphQLResult Success(JsonElement data) + => new(data, null); + + public static NitroGraphQLResult Failed(string failure) + => new(default, failure); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs index c94089384bb..bf23e168582 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs @@ -13,19 +13,47 @@ internal static class NitroOperationDocuments #if NITRO_PERSISTED_OPERATIONS private const string ResolveApiNameHashFile = "ResolveNitroApiName.graphql.sha256"; private const string ValidateSchemaHashFile = "ValidateNitroSchema.graphql.sha256"; - private const string PollSchemaValidationHashFile = "PollNitroSchemaValidation.graphql.sha256"; + private const string WatchSchemaValidationHashFile = "WatchNitroSchemaValidation.graphql.sha256"; + private const string UploadSourceSchemaHashFile = "UploadFusionSourceSchema.graphql.sha256"; + private const string BeginDeploymentHashFile = "BeginFusionDeployment.graphql.sha256"; + private const string ClaimDeploymentHashFile = "ClaimFusionDeployment.graphql.sha256"; + private const string ValidateDeploymentHashFile = "ValidateFusionDeployment.graphql.sha256"; + private const string CommitDeploymentHashFile = "CommitFusionDeployment.graphql.sha256"; + private const string ReleaseDeploymentHashFile = "ReleaseFusionDeployment.graphql.sha256"; + private const string WatchDeploymentHashFile = "WatchFusionDeployment.graphql.sha256"; private static string? s_resolveApiNameHash; private static string? s_validateSchemaHash; - private static string? s_pollSchemaValidationHash; + private static string? s_watchSchemaValidationHash; + private static string? s_uploadSourceSchemaHash; + private static string? s_beginDeploymentHash; + private static string? s_claimDeploymentHash; + private static string? s_validateDeploymentHash; + private static string? s_commitDeploymentHash; + private static string? s_releaseDeploymentHash; + private static string? s_watchDeploymentHash; #else private const string ResolveApiNameFile = "ResolveNitroApiName.graphql"; private const string ValidateSchemaFile = "ValidateNitroSchema.graphql"; - private const string PollSchemaValidationFile = "PollNitroSchemaValidation.graphql"; + private const string WatchSchemaValidationFile = "WatchNitroSchemaValidation.graphql"; + private const string UploadSourceSchemaFile = "UploadFusionSourceSchema.graphql"; + private const string BeginDeploymentFile = "BeginFusionDeployment.graphql"; + private const string ClaimDeploymentFile = "ClaimFusionDeployment.graphql"; + private const string ValidateDeploymentFile = "ValidateFusionDeployment.graphql"; + private const string CommitDeploymentFile = "CommitFusionDeployment.graphql"; + private const string ReleaseDeploymentFile = "ReleaseFusionDeployment.graphql"; + private const string WatchDeploymentFile = "WatchFusionDeployment.graphql"; private static string? s_resolveApiName; private static string? s_validateSchema; - private static string? s_pollSchemaValidation; + private static string? s_watchSchemaValidation; + private static string? s_uploadSourceSchema; + private static string? s_beginDeployment; + private static string? s_claimDeployment; + private static string? s_validateDeployment; + private static string? s_commitDeployment; + private static string? s_releaseDeployment; + private static string? s_watchDeployment; #endif /// @@ -33,7 +61,42 @@ internal static class NitroOperationDocuments /// public const string ResolveApiNameOperationName = "ResolveNitroApiName"; public const string ValidateSchemaOperationName = "ValidateNitroSchema"; - public const string PollSchemaValidationOperationName = "PollNitroSchemaValidation"; + public const string WatchSchemaValidationOperationName = "WatchNitroSchemaValidation"; + + /// + /// The operation name of the document that uploads an immutable source schema version. + /// + public const string UploadSourceSchemaOperationName = "UploadFusionSourceSchema"; + + /// + /// The operation name of the document that opens a publication request. + /// + public const string BeginDeploymentOperationName = "BeginFusionDeployment"; + + /// + /// The operation name of the document that claims the publication slot. + /// + public const string ClaimDeploymentOperationName = "ClaimFusionDeployment"; + + /// + /// The operation name of the document that validates a composed configuration. + /// + public const string ValidateDeploymentOperationName = "ValidateFusionDeployment"; + + /// + /// The operation name of the document that commits a composed configuration. + /// + public const string CommitDeploymentOperationName = "CommitFusionDeployment"; + + /// + /// The operation name of the document that releases the publication slot. + /// + public const string ReleaseDeploymentOperationName = "ReleaseFusionDeployment"; + + /// + /// The operation name of the document that observes the state of a publication request. + /// + public const string WatchDeploymentOperationName = "WatchFusionDeployment"; #if NITRO_PERSISTED_OPERATIONS /// @@ -45,8 +108,29 @@ public static string GetResolveApiNameOperationId() public static string GetValidateSchemaOperationId() => s_validateSchemaHash ??= ReadDocument(ValidateSchemaHashFile).Trim(); - public static string GetPollSchemaValidationOperationId() - => s_pollSchemaValidationHash ??= ReadDocument(PollSchemaValidationHashFile).Trim(); + public static string GetWatchSchemaValidationOperationId() + => s_watchSchemaValidationHash ??= ReadDocument(WatchSchemaValidationHashFile).Trim(); + + public static string GetUploadSourceSchemaOperationId() + => s_uploadSourceSchemaHash ??= ReadDocument(UploadSourceSchemaHashFile).Trim(); + + public static string GetBeginDeploymentOperationId() + => s_beginDeploymentHash ??= ReadDocument(BeginDeploymentHashFile).Trim(); + + public static string GetClaimDeploymentOperationId() + => s_claimDeploymentHash ??= ReadDocument(ClaimDeploymentHashFile).Trim(); + + public static string GetValidateDeploymentOperationId() + => s_validateDeploymentHash ??= ReadDocument(ValidateDeploymentHashFile).Trim(); + + public static string GetCommitDeploymentOperationId() + => s_commitDeploymentHash ??= ReadDocument(CommitDeploymentHashFile).Trim(); + + public static string GetReleaseDeploymentOperationId() + => s_releaseDeploymentHash ??= ReadDocument(ReleaseDeploymentHashFile).Trim(); + + public static string GetWatchDeploymentOperationId() + => s_watchDeploymentHash ??= ReadDocument(WatchDeploymentHashFile).Trim(); #else /// /// Gets the document that resolves the name of an api by its id. @@ -57,8 +141,29 @@ public static string GetResolveApiNameDocument() public static string GetValidateSchemaDocument() => s_validateSchema ??= ReadDocument(ValidateSchemaFile); - public static string GetPollSchemaValidationDocument() - => s_pollSchemaValidation ??= ReadDocument(PollSchemaValidationFile); + public static string GetWatchSchemaValidationDocument() + => s_watchSchemaValidation ??= ReadDocument(WatchSchemaValidationFile); + + public static string GetUploadSourceSchemaDocument() + => s_uploadSourceSchema ??= ReadDocument(UploadSourceSchemaFile); + + public static string GetBeginDeploymentDocument() + => s_beginDeployment ??= ReadDocument(BeginDeploymentFile); + + public static string GetClaimDeploymentDocument() + => s_claimDeployment ??= ReadDocument(ClaimDeploymentFile); + + public static string GetValidateDeploymentDocument() + => s_validateDeployment ??= ReadDocument(ValidateDeploymentFile); + + public static string GetCommitDeploymentDocument() + => s_commitDeployment ??= ReadDocument(CommitDeploymentFile); + + public static string GetReleaseDeploymentDocument() + => s_releaseDeployment ??= ReadDocument(ReleaseDeploymentFile); + + public static string GetWatchDeploymentDocument() + => s_watchDeployment ??= ReadDocument(WatchDeploymentFile); #endif private static string ReadDocument(string fileName) diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSchemaValidator.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSchemaValidator.cs index 558078458bc..8e4047a29d8 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSchemaValidator.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSchemaValidator.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Text.Json; using HotChocolate.Transport; using HotChocolate.Transport.Http; @@ -8,17 +7,11 @@ namespace HotChocolate.Fusion.Aspire.Nitro; internal sealed class NitroSchemaValidator( GraphQLHttpClient client, - TimeProvider timeProvider, ILogger logger) : INitroSchemaValidator { - private const int MaxAttempts = 3; - private const int MaxResponseBytes = 2 * 1024 * 1024; private const int MaxParsedFindings = 10_000; - private static readonly TimeSpan s_requestTimeout = TimeSpan.FromSeconds(15); private static readonly TimeSpan s_overallTimeout = TimeSpan.FromMinutes(2); - private static readonly TimeSpan s_initialPollDelay = TimeSpan.FromMilliseconds(250); - private static readonly TimeSpan s_maxPollDelay = TimeSpan.FromSeconds(2); public async Task ValidateAsync( NitroConnection connection, @@ -66,51 +59,13 @@ public async Task ValidateAsync( schemaHash); } - var pollDelay = s_initialPollDelay; - - while (true) - { - await Task.Delay(pollDelay, timeProvider, overall.Token); - - var poll = await SendPollAsync(connection, requestId, overall.Token); - if (poll.Failure is not null) - { - return Unavailable(poll.Failure, schemaHash, requestId); - } - - if (poll.Result.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) - { - return Unavailable( - "Nitro returned no schema validation result.", - schemaHash, - requestId); - } + var watch = await NitroGraphQL.SubscribeAsync( + client, + CreateWatchRequest(connection, requestId), + result => ReadValidationResult(result, schemaHash, requestId), + overall.Token); - var typeName = GetString(poll.Result, "__typename"); - switch (typeName) - { - case "OperationInProgress": - case "ValidationInProgress": - pollDelay = TimeSpan.FromMilliseconds( - Math.Min(pollDelay.TotalMilliseconds * 2, s_maxPollDelay.TotalMilliseconds)); - continue; - - case "SchemaVersionValidationSuccess": - return NitroSchemaValidationReport.Passed( - schemaHash, - requestId, - timeProvider.GetUtcNow()); - - case "SchemaVersionValidationFailed": - return ParseViolations(poll.Result, schemaHash, requestId); - - default: - return Unavailable( - $"Nitro returned the unknown validation result '{typeName ?? "null"}'.", - schemaHash, - requestId); - } - } + return watch.Value ?? Unavailable(watch.Failure!, schemaHash, requestId); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -153,7 +108,8 @@ public async Task ValidateAsync( } }; - var result = await SendWithRetryAsync( + var result = await NitroGraphQL.SendWithRetryAsync( + client, CreateRequest( connection, NitroOperationDocuments.ValidateSchemaOperationName, @@ -165,6 +121,7 @@ public async Task ValidateAsync( variables, enableFileUploads: true), retryTransientFailures: false, + NitroGraphQL.DefaultRequestTimeout, cancellationToken); if (result.Failure is not null) @@ -185,154 +142,57 @@ public async Task ValidateAsync( : (null, error); } - private async Task<(JsonElement Result, string? Failure)> SendPollAsync( + private static GraphQLHttpRequest CreateWatchRequest( NitroConnection connection, - string requestId, - CancellationToken cancellationToken) - { - var variables = new Dictionary - { - ["input"] = new Dictionary { ["id"] = requestId } - }; - - var result = await SendWithRetryAsync( - CreateRequest( - connection, - NitroOperationDocuments.PollSchemaValidationOperationName, + string requestId) + => CreateRequest( + connection, + NitroOperationDocuments.WatchSchemaValidationOperationName, #if NITRO_PERSISTED_OPERATIONS - NitroOperationDocuments.GetPollSchemaValidationOperationId(), + NitroOperationDocuments.GetWatchSchemaValidationOperationId(), #else - NitroOperationDocuments.GetPollSchemaValidationDocument(), + NitroOperationDocuments.GetWatchSchemaValidationDocument(), #endif - variables, - enableFileUploads: false), - retryTransientFailures: true, - cancellationToken); - - if (result.Failure is not null) - { - return (default, result.Failure); - } + new Dictionary { ["requestId"] = requestId }, + enableFileUploads: false); + private static NitroSchemaValidationReport? ReadValidationResult( + OperationResult result, + string schemaHash, + string requestId) + { if (result.Data.ValueKind is not JsonValueKind.Object - || !result.Data.TryGetProperty("pollSchemaVersionValidationRequest", out var payload) + || !result.Data.TryGetProperty("onSchemaVersionValidationUpdate", out var payload) || payload.ValueKind is not JsonValueKind.Object) { - return (default, "Nitro returned a malformed validation polling response."); - } - - var error = ReadFirstError(payload); - if (error is not null) - { - return (default, error); + return Unavailable( + "Nitro returned no schema validation result.", + schemaHash, + requestId); } - return payload.TryGetProperty("result", out var validationResult) - ? (validationResult.Clone(), null) - : (default, "Nitro returned no schema validation result."); - } - - private async Task SendWithRetryAsync( - GraphQLHttpRequest request, - bool retryTransientFailures, - CancellationToken cancellationToken) - { - for (var attempt = 1; ; attempt++) + var typeName = GetString(payload, "__typename"); + switch (typeName) { - try - { - using var requestTimeout = - CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - requestTimeout.CancelAfter(s_requestTimeout); - - using var response = await client.SendAsync(request, requestTimeout.Token); - if (!response.IsSuccessStatusCode) - { - if (retryTransientFailures - && IsTransient(response.StatusCode) - && attempt < MaxAttempts) - { - await DelayRetryAsync(attempt, cancellationToken); - continue; - } - - return NitroGraphQLResult.Failed( - $"Nitro returned HTTP {(int)response.StatusCode} ({response.StatusCode})."); - } + case "OperationInProgress": + case "ValidationInProgress": + return null; - if (response.ContentHeaders.ContentLength is > MaxResponseBytes) - { - return NitroGraphQLResult.Failed( - "Nitro returned a schema validation response that exceeded the size limit."); - } - - using var document = await ReadBoundedJsonAsync( - response.HttpResponseMessage, - requestTimeout.Token); - var root = document.RootElement; - if (root.TryGetProperty("errors", out var errors) - && errors.ValueKind is JsonValueKind.Array - && errors.GetArrayLength() > 0) - { - return NitroGraphQLResult.Failed("Nitro returned GraphQL errors."); - } - - if (!root.TryGetProperty("data", out var data)) - { - return NitroGraphQLResult.Failed("Nitro returned a malformed GraphQL response."); - } - - return NitroGraphQLResult.Success(data.Clone()); - } - catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) - { - throw; - } - catch (OperationCanceledException) when ( - retryTransientFailures && attempt < MaxAttempts) - { - await DelayRetryAsync(attempt, cancellationToken); - } - catch (HttpRequestException) when ( - retryTransientFailures && attempt < MaxAttempts) - { - await DelayRetryAsync(attempt, cancellationToken); - } - catch (IOException) when ( - retryTransientFailures && attempt < MaxAttempts) - { - await DelayRetryAsync(attempt, cancellationToken); - } - } - } - - private static async Task ReadBoundedJsonAsync( - HttpResponseMessage response, - CancellationToken cancellationToken) - { - await using var source = await response.Content.ReadAsStreamAsync(cancellationToken); - await using var destination = new MemoryStream(); - var buffer = new byte[16 * 1024]; - - while (true) - { - var read = await source.ReadAsync(buffer, cancellationToken); - if (read == 0) - { - break; - } + case "SchemaVersionValidationSuccess": + return NitroSchemaValidationReport.Passed( + schemaHash, + requestId, + DateTimeOffset.UtcNow); - if (destination.Length + read > MaxResponseBytes) - { - throw new NitroResponseTooLargeException( - "Nitro returned a schema validation response that exceeded the size limit."); - } + case "SchemaVersionValidationFailed": + return ParseViolations(payload, schemaHash, requestId); - destination.Write(buffer, 0, read); + default: + return Unavailable( + $"Nitro returned the unknown validation result '{typeName ?? "null"}'.", + schemaHash, + requestId); } - - destination.Position = 0; - return await JsonDocument.ParseAsync(destination, default, cancellationToken); } private static GraphQLHttpRequest CreateRequest( @@ -361,7 +221,7 @@ private static GraphQLHttpRequest CreateRequest( }; } - private NitroSchemaValidationReport ParseViolations( + private static NitroSchemaValidationReport ParseViolations( JsonElement result, string schemaHash, string requestId) @@ -378,7 +238,7 @@ private NitroSchemaValidationReport ParseViolations( return NitroSchemaValidationReport.Unavailable( schemaHash, "Nitro returned a malformed validation failure.", - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); } @@ -389,7 +249,7 @@ private NitroSchemaValidationReport ParseViolations( return NitroSchemaValidationReport.Unavailable( schemaHash, "Nitro returned too many schema validation findings.", - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); } @@ -448,7 +308,7 @@ finding with return NitroSchemaValidationReport.Unavailable( schemaHash, "Nitro returned too many schema validation findings.", - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); } } @@ -461,7 +321,7 @@ finding with return NitroSchemaValidationReport.Unavailable( schemaHash, unknownFailure, - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); } @@ -470,7 +330,7 @@ finding with return NitroSchemaValidationReport.Unavailable( schemaHash, "Nitro returned a validation failure without findings.", - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); } @@ -481,7 +341,7 @@ finding with clients, findings, null, - timeProvider.GetUtcNow()) + DateTimeOffset.UtcNow) { HasMoreClientErrors = hasMoreClientErrors }; @@ -808,45 +668,13 @@ private static string GroupFor(string typeName) ? property.GetBoolean() : null; - private async Task DelayRetryAsync(int attempt, CancellationToken cancellationToken) - => await Task.Delay( - TimeSpan.FromMilliseconds(250 * (1 << (attempt - 1))), - timeProvider, - cancellationToken); - - private NitroSchemaValidationReport Unavailable( + private static NitroSchemaValidationReport Unavailable( string reason, string schemaHash, string? requestId = null) => NitroSchemaValidationReport.Unavailable( schemaHash, reason, - timeProvider.GetUtcNow(), + DateTimeOffset.UtcNow, requestId); - - private static bool IsTransient(HttpStatusCode statusCode) - => statusCode is HttpStatusCode.RequestTimeout - or HttpStatusCode.TooManyRequests - || statusCode >= HttpStatusCode.InternalServerError; - - private sealed class NitroGraphQLResult - { - private NitroGraphQLResult(JsonElement data, string? failure) - { - Data = data; - Failure = failure; - } - - public JsonElement Data { get; } - - public string? Failure { get; } - - public static NitroGraphQLResult Success(JsonElement data) - => new(data, null); - - public static NitroGraphQLResult Failed(string failure) - => new(default, failure); - } - - private sealed class NitroResponseTooLargeException(string message) : Exception(message); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSeedCoordinator.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSeedCoordinator.cs index 2a74be20b49..732d3710888 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSeedCoordinator.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroSeedCoordinator.cs @@ -74,6 +74,9 @@ public static NitroSeedCoordinator CreateProduction(string stage) var timeProvider = TimeProvider.System; var httpClient = new HttpClient(); + // Schema validation waits on a server-sent event stream that outlives any fixed request + // timeout, so the client that carries it is bounded by the validator instead. + var streamingHttpClient = new HttpClient { Timeout = Timeout.InfiniteTimeSpan }; var connectionResolver = new NitroConnectionResolver( new NitroSessionReader( NitroDefaults.GetSessionFilePath(), @@ -91,8 +94,7 @@ public static NitroSeedCoordinator CreateProduction(string stage) new NitroApiLookupClient( GraphQLHttpClient.Create(httpClient, disposeHttpClient: false))); var schemaValidator = new NitroSchemaValidator( - GraphQLHttpClient.Create(httpClient, disposeHttpClient: false), - timeProvider, + GraphQLHttpClient.Create(streamingHttpClient, disposeHttpClient: false), NullLogger.Instance); return new NitroSeedCoordinator( diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql similarity index 89% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql index 3be7fb47089..f19ceb468dc 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/BeginFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql @@ -3,7 +3,7 @@ mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { requestId errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..58680920128 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +d858bf7df2ee5df613f2e699446657dd3bd489b9bd1d246eff7bf021c31307c7 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql similarity index 89% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql index 2707e57c615..079b97a2853 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ClaimFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql @@ -2,7 +2,7 @@ mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput! startFusionConfigurationComposition(input: $input) { errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..f4ae644f5f8 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +5b818ded677899729e77ceeba1fc6ae179a08cf7982d158fcc2731f798614046 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql similarity index 89% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql index a0c34923a19..10d94e1f18d 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/CommitFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql @@ -2,7 +2,7 @@ mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) commitFusionConfigurationPublish(input: $input) { errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..bf46ad93883 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +f66f07ad033aff0963053bd465ff33463b48b2c4888a30b7db7dfbb8301f48ec diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql deleted file mode 100644 index 43c75b5d8d0..00000000000 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql +++ /dev/null @@ -1,153 +0,0 @@ -mutation PollNitroSchemaValidation($input: PollSchemaVersionValidationRequestInput!) { - pollSchemaVersionValidationRequest(input: $input) { - errors { - __typename - ... on Error { - message - } - } - result { - __typename - state - ... on SchemaVersionValidationFailed { - errors { - __typename - message - ... on PersistedQueryValidationError { - clientId - hasMoreErrors - client { - id - name - } - queries { - deployedTags - hash - message - errors { - message - code - path - locations { - line - column - } - } - } - } - ... on SchemaVersionChangeViolationError { - changes { - __typename - ... on SchemaChange { - severity - } - ... on TypeSystemMemberAddedChange { - coordinate - } - ... on TypeSystemMemberModifiedChange { - coordinate - } - ... on TypeSystemMemberRemovedChange { - coordinate - } - ... on ObjectModifiedChange { - coordinate - } - ... on InputObjectModifiedChange { - coordinate - } - ... on DirectiveModifiedChange { - coordinate - } - ... on ScalarModifiedChange { - coordinate - } - ... on EnumModifiedChange { - coordinate - } - ... on UnionModifiedChange { - coordinate - } - ... on InterfaceModifiedChange { - coordinate - } - } - } - ... on InvalidGraphQLSchemaError { - errors { - message - code - } - } - ... on SchemaVersionSyntaxError { - line - column - position - } - ... on OperationsAreNotAllowedError { - operationName - } - ... on OpenApiCollectionValidationError { - collections { - openApiCollection { - id - name - } - entities { - __typename - ... on OpenApiCollectionValidationEndpoint { - httpMethod - route - } - ... on OpenApiCollectionValidationModel { - name - } - errors { - __typename - message - ... on OpenApiCollectionValidationDocumentError { - code - path - locations { - line - column - } - } - } - } - } - } - ... on McpFeatureCollectionValidationError { - collections { - mcpFeatureCollection { - id - name - } - entities { - __typename - ... on McpFeatureCollectionValidationPrompt { - name - } - ... on McpFeatureCollectionValidationTool { - name - } - errors { - __typename - message - ... on McpFeatureCollectionValidationDocumentError { - code - path - locations { - line - column - } - } - } - } - } - } - } - } - } - } -} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql.sha256 deleted file mode 100644 index 6b20b75a7d5..00000000000 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/PollNitroSchemaValidation.graphql.sha256 +++ /dev/null @@ -1 +0,0 @@ -6ebe506f2dda96523ef2a0302b7b4aa0f748c2ace6dd5b89449c081c7dbb135e diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql similarity index 89% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql index 5ca2038c29b..80fba9c927b 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ReleaseFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql @@ -2,7 +2,7 @@ mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInp cancelFusionConfigurationComposition(input: $input) { errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..234bc482a29 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +808c0252d2713db0f747a5c6d8d9e56806db032489daac04d9b075c1baef06a2 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/UploadFusionSourceSchema.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql similarity index 90% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/UploadFusionSourceSchema.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql index fb98f1cd230..92c813a6ae4 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/UploadFusionSourceSchema.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql @@ -5,7 +5,7 @@ mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { } errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 new file mode 100644 index 00000000000..62072fc63ff --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 @@ -0,0 +1 @@ +f091e081d5fea136c590aa34b6f37d391d69844c23f3a61f5e728ba97fed951f diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql similarity index 90% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql index 40c0c6a8ca7..2f1aed8fb76 100644 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/ValidateFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql @@ -2,7 +2,7 @@ mutation ValidateFusionDeployment($input: ValidateFusionConfigurationComposition validateFusionConfigurationComposition(input: $input) { errors { __typename - ...FusionError + message } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..62a8e794ef5 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +de83bb4fc363ce531c18138aa35938387f46a81b1258f8882dd7c67b6c499a5e diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql similarity index 100% rename from src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/WatchFusionDeployment.graphql rename to src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql.sha256 new file mode 100644 index 00000000000..f5be26bac50 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchFusionDeployment.graphql.sha256 @@ -0,0 +1 @@ +25fff4ef7a751b2d9b1008f353dc23c89b8aed8978334407d3aa9a83806839b1 diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql new file mode 100644 index 00000000000..7cbf7e008dc --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql @@ -0,0 +1,145 @@ +subscription WatchNitroSchemaValidation($requestId: ID!) { + onSchemaVersionValidationUpdate(requestId: $requestId) { + __typename + state + ... on SchemaVersionValidationFailed { + errors { + __typename + message + ... on PersistedQueryValidationError { + clientId + hasMoreErrors + client { + id + name + } + queries { + deployedTags + hash + message + errors { + message + code + path + locations { + line + column + } + } + } + } + ... on SchemaVersionChangeViolationError { + changes { + __typename + ... on SchemaChange { + severity + } + ... on TypeSystemMemberAddedChange { + coordinate + } + ... on TypeSystemMemberModifiedChange { + coordinate + } + ... on TypeSystemMemberRemovedChange { + coordinate + } + ... on ObjectModifiedChange { + coordinate + } + ... on InputObjectModifiedChange { + coordinate + } + ... on DirectiveModifiedChange { + coordinate + } + ... on ScalarModifiedChange { + coordinate + } + ... on EnumModifiedChange { + coordinate + } + ... on UnionModifiedChange { + coordinate + } + ... on InterfaceModifiedChange { + coordinate + } + } + } + ... on InvalidGraphQLSchemaError { + errors { + message + code + } + } + ... on SchemaVersionSyntaxError { + line + column + position + } + ... on OperationsAreNotAllowedError { + operationName + } + ... on OpenApiCollectionValidationError { + collections { + openApiCollection { + id + name + } + entities { + __typename + ... on OpenApiCollectionValidationEndpoint { + httpMethod + route + } + ... on OpenApiCollectionValidationModel { + name + } + errors { + __typename + message + ... on OpenApiCollectionValidationDocumentError { + code + path + locations { + line + column + } + } + } + } + } + } + ... on McpFeatureCollectionValidationError { + collections { + mcpFeatureCollection { + id + name + } + entities { + __typename + ... on McpFeatureCollectionValidationPrompt { + name + } + ... on McpFeatureCollectionValidationTool { + name + } + errors { + __typename + message + ... on McpFeatureCollectionValidationDocumentError { + code + path + locations { + line + column + } + } + } + } + } + } + } + } + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql.sha256 new file mode 100644 index 00000000000..df683024370 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/WatchNitroSchemaValidation.graphql.sha256 @@ -0,0 +1 @@ +ad7ee984bdd62c1357d5ee29261adff3f60bb119f5f162ae1ee8a1f9c62632d9 diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs index fbd72a5977c..677c5ff1d1c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs @@ -1,6 +1,7 @@ using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using HotChocolate.Fusion.Aspire.Nitro; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace HotChocolate.Fusion.Aspire; @@ -130,11 +131,11 @@ public static IDistributedApplicationBuilder AddNitro( /// The resource builder for chaining. /// /// - /// The api id only takes effect on a resource whose schema is composed and only when the - /// distributed application calls - /// . - /// On any other resource it is metadata - /// without an effect. + /// On a gateway the api id selects the fusion configuration that the gateway composes on top + /// of, and it only takes effect when the distributed application calls + /// . On a publish target + /// added with the api id selects the api that Fusion + /// deployments publish to. On any other resource it is metadata without an effect. /// public static IResourceBuilder WithNitroApiId( this IResourceBuilder builder, @@ -201,6 +202,359 @@ public static IResourceBuilder WithNitroSchemaValidation( return builder; } + /// + /// Adds the Nitro api that the Fusion deployments of the distributed application publish to. + /// The cloud URL and the api id default to the Nitro:CloudUrl and Nitro:ApiId + /// configuration values, or to the NITRO_CLOUD_URL and NITRO_API_ID environment + /// variables. + /// + /// + /// The distributed application builder. + /// + /// + /// The name of the publish target resource. + /// + /// + /// The resource builder of the publish target for chaining. + /// + public static IResourceBuilder AddNitroPublishTarget( + this IDistributedApplicationBuilder builder, + string name) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + EnsureFusionPipeline(builder); + builder.Services.TryAddSingleton(_ => NitroFusionApi.Create()); + builder.Services.TryAddSingleton(); + + var configuredCloudUrl = + builder.Configuration["Nitro:CloudUrl"] + ?? builder.Configuration["NITRO_CLOUD_URL"]; + var resource = new NitroPublishTargetResource(name) + { + CloudUrl = string.IsNullOrWhiteSpace(configuredCloudUrl) + ? null + : NormalizeCloudUrl(configuredCloudUrl) + }; + var resourceBuilder = builder.AddResource(resource); + + var configuredApiId = + builder.Configuration["Nitro:ApiId"] + ?? builder.Configuration["NITRO_API_ID"]; + + if (!string.IsNullOrWhiteSpace(configuredApiId)) + { + resourceBuilder.WithNitroApiId(configuredApiId); + } + + return resourceBuilder; + } + + /// + /// Sets the Nitro api URL that Fusion deployments publish to. + /// + /// + /// The resource builder of a Nitro publish target. + /// + /// + /// An absolute HTTPS origin without a path, query, fragment, or user information. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithNitroCloudUrl( + this IResourceBuilder builder, + string cloudUrl) + { + ArgumentNullException.ThrowIfNull(builder); + + builder.Resource.CloudUrl = NormalizeCloudUrl(cloudUrl); + return builder; + } + + /// + /// Sets the secret parameter that supplies the Nitro api key. When this is not configured, the + /// credential is resolved from the NITRO_API_KEY environment variable or the Nitro CLI + /// session. + /// + /// + /// The resource builder of a Nitro publish target. + /// + /// + /// The parameter that supplies the api key. It has to be declared as a secret. + /// + /// + /// The resource builder for chaining. + /// + /// + /// The parameter is not declared as a secret. + /// + public static IResourceBuilder WithNitroApiKey( + this IResourceBuilder builder, + IResourceBuilder apiKey) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(apiKey); + + if (!apiKey.Resource.Secret) + { + throw new ArgumentException( + "The Nitro API key parameter must be declared as a secret.", + nameof(apiKey)); + } + + builder.Resource.ApiKey = apiKey.Resource; + return builder; + } + + /// + /// Adds an environment-specific Fusion deployment that publishes to the Nitro publish target. + /// + /// + /// The resource builder of a Nitro publish target. + /// + /// + /// The name of the deployment resource. + /// + /// + /// The resource builder of the deployment for chaining. + /// + public static IResourceBuilder AddFusionDeployment( + this IResourceBuilder builder, + string name) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + var resource = new FusionDeploymentResource(name, builder.Resource); + return builder.ApplicationBuilder.AddResource(resource); + } + + /// + /// Maps the deployment to an exact Aspire environment. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The name of the Aspire environment. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder ForEnvironment( + this IResourceBuilder builder, + string environmentName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + builder.Resource.EnvironmentName = environmentName; + return builder; + } + + /// + /// Maps the deployment to an exact Nitro stage. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The name of the Nitro stage that the deployment publishes to. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder ToStage( + this IResourceBuilder builder, + string stageName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(stageName); + + builder.Resource.StageName = stageName; + return builder; + } + + /// + /// Selects the exact schema-settings.json environment used for composition. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The name of the settings environment. + /// + /// + /// The resource builder for chaining. + /// + /// + /// When this is not configured, an environment selected by the GraphQL composition is used, + /// followed by the Nitro stage name. + /// + public static IResourceBuilder WithCompositionEnvironment( + this IResourceBuilder builder, + string environmentName) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); + + builder.Resource.CompositionEnvironmentName = environmentName; + return builder; + } + + /// + /// Sets the immutable release tag. The tag is both the source schema version and the fusion + /// configuration tag of the deployment. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The parameter that supplies the release tag. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, + IResourceBuilder configurationTag) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(configurationTag); + + builder.Resource.ConfigurationTagParameter = configurationTag.Resource; + builder.Resource.ConfigurationTag = null; + return builder; + } + + /// + /// Sets the immutable release tag. The tag is both the source schema version and the fusion + /// configuration tag of the deployment. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The release tag. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, + string configurationTag) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrWhiteSpace(configurationTag); + + builder.Resource.ConfigurationTag = configurationTag; + builder.Resource.ConfigurationTagParameter = null; + return builder; + } + + /// + /// Configures whether Nitro waits for approval before the configuration is committed. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// Specifies whether the publication waits for approval. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithApproval( + this IResourceBuilder builder, + bool waitForApproval) + { + ArgumentNullException.ThrowIfNull(builder); + builder.Resource.WaitForApproval = waitForApproval; + return builder; + } + + /// + /// Configures whether validation failures may be forced. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// Specifies whether the publication proceeds despite validation failures. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithForce( + this IResourceBuilder builder, + bool force) + { + ArgumentNullException.ThrowIfNull(builder); + builder.Resource.Force = force; + return builder; + } + + /// + /// Configures the operation and approval timeouts of the deployment. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The time a single remote operation may take. + /// + /// + /// The time the publication waits for approval. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder WithTimeouts( + this IResourceBuilder builder, + TimeSpan operation, + TimeSpan approval) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(operation, TimeSpan.Zero); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(approval, TimeSpan.Zero); + + builder.Resource.OperationTimeout = operation; + builder.Resource.ApprovalTimeout = approval; + return builder; + } + + private static void EnsureFusionPipeline(IDistributedApplicationBuilder builder) + { + if (builder.Resources.OfType().Any()) + { + return; + } + + var pipeline = builder.AddResource( + new FusionPipelineResource("fusion-nitro-pipeline")); + FusionPipeline.Configure(pipeline); + } + + private static string NormalizeCloudUrl(string cloudUrl) + { + if (!Uri.TryCreate(cloudUrl, UriKind.Absolute, out var uri) + || uri.Scheme is not "https" + || !string.IsNullOrEmpty(uri.UserInfo) + || uri.AbsolutePath is not "/" + || !string.IsNullOrEmpty(uri.Query) + || !string.IsNullOrEmpty(uri.Fragment)) + { + throw new ArgumentException( + "The Nitro cloud URL must be an absolute HTTPS origin without " + + "a path, query, fragment, or user information.", + nameof(cloudUrl)); + } + + return uri.GetLeftPart(UriPartial.Authority); + } + internal static string? GetNitroApiId(this IResource resource) => resource.Annotations.OfType().SingleOrDefault()?.ApiId; diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs new file mode 100644 index 00000000000..20093fa2b0e --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs @@ -0,0 +1,15 @@ +using Aspire.Hosting.ApplicationModel; + +namespace HotChocolate.Fusion.Aspire; + +/// +/// Represents the Nitro api that the Fusion deployments of a distributed application publish to. +/// +public sealed class NitroPublishTargetResource(string name) : Resource(name) +{ + internal string? CloudUrl { get; set; } + + internal string? ApiId => this.GetNitroApiId(); + + internal ParameterResource? ApiKey { get; set; } +} diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs similarity index 88% rename from src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs rename to src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs index 09c60ffd31d..30f5cb34496 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionPipelineTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs @@ -3,10 +3,11 @@ using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; -using HotChocolate.Fusion.Aspire; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using IOPath = System.IO.Path; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; #pragma warning disable ASPIREPIPELINES001 @@ -17,9 +18,9 @@ public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() { var builder = DistributedApplication.CreateBuilder(); var nitro = builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products"); + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products"); nitro .AddFusionDeployment("production") .ForEnvironment("Production") @@ -46,8 +47,8 @@ public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() var exception = Assert.Throws( () => builder - .AddNitroTarget("nitro") - .WithCloudUrl( + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl( "https://api.chillicream.com/CaseSensitivePath")); Assert.Equal( @@ -61,9 +62,9 @@ public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() { var builder = DistributedApplication.CreateBuilder(); builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products") + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") .AddFusionDeployment("production") .ForEnvironment("Production") .ToStage("production") @@ -81,13 +82,13 @@ public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() public void GetSourceNames_Should_Fail_WhenEffectiveNamesAreDuplicated() { using var testDirectory = new TestDirectory(); - var productsProject = Path.Combine( + var productsProject = IOPath.Combine( testDirectory.Path, "Products.csproj"); - var reviewsProject = Path.Combine( + var reviewsProject = IOPath.Combine( testDirectory.Path, "Reviews.csproj"); - var gatewayProject = Path.Combine( + var gatewayProject = IOPath.Combine( testDirectory.Path, "Gateway.csproj"); File.WriteAllText(productsProject, ""); @@ -120,9 +121,9 @@ public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() { var builder = DistributedApplication.CreateBuilder(); var nitro = builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products"); + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products"); nitro .AddFusionDeployment("production-a") .ForEnvironment("Production") @@ -145,12 +146,17 @@ public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() } [Fact] - public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() + public void CreateSteps_Should_WireArtifactAndRemoteRoots() { + // arrange var resource = new FusionPipelineResource("fusion-pipeline"); - var steps = FusionPipeline.CreateStepDefinitionsForTest(resource); + // act + var steps = FusionPipeline.CreateSteps( + CreatePipelineStepFactoryContext(resource), + new FusionPipelineTopology()); + // assert string.Join( Environment.NewLine, steps.Select(step => @@ -169,15 +175,20 @@ public void CreateStepDefinitions_Should_WireArtifactAndRemoteRoots() } [Fact] - public void CreateStepDefinitions_Should_IsolatePublishFromUploadSteps() + public void CreateSteps_Should_IsolatePublishFromUploadSteps() { + // arrange var resource = new FusionPipelineResource("fusion-pipeline"); - var steps = FusionPipeline.CreateStepDefinitionsForTest(resource); + // act + var steps = FusionPipeline.CreateSteps( + CreatePipelineStepFactoryContext(resource), + new FusionPipelineTopology()); + + // assert var stepsByName = steps.ToDictionary( step => step.Name, StringComparer.Ordinal); - string.Join( Environment.NewLine, GetTransitiveDependencies( @@ -389,7 +400,7 @@ public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() { var deployment = new FusionDeploymentResource( "production", - new NitroResource("nitro")) + new NitroPublishTargetResource("nitro")) { StageName = "production" }; @@ -406,7 +417,7 @@ public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() public async Task VerifyFileDigestAsync_Should_Fail_WhenComposedArchiveChanges() { using var testDirectory = new TestDirectory(); - var farPath = Path.Combine( + var farPath = IOPath.Combine( testDirectory.Path, "fusion-configuration.far"); await File.WriteAllTextAsync( @@ -456,26 +467,25 @@ public void BoundedMemoryStream_Should_ClearBackingBuffer_WhenDisposed() [Fact] public void TransferComposition_Should_ClearArchive_When_CanceledBeforeTransfer() { + // arrange using var state = new FusionDeploymentSessionState( "release-1", "https://api.chillicream.com", "products", []); byte[] fusionArchive = [1, 2, 3]; - var clearedBuffers = new List(); - var executor = new FusionPipelineExecutor( - bufferCleared: clearedBuffers.Add); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); + // act Assert.Throws( - () => executor.TransferComposition( + () => FusionPipelineExecutor.Instance.TransferComposition( state, "development", fusionArchive, cancellation.Token)); - Assert.Same(fusionArchive, Assert.Single(clearedBuffers)); + // assert Assert.Equal(new byte[3], fusionArchive); Assert.Throws(() => state.FusionArchive); } @@ -553,8 +563,8 @@ await Task.Delay( public void ReplaceDirectoryAtomically_Should_RemoveSource_WhenSourceWasRemoved() { using var testDirectory = new TestDirectory(); - var destination = Path.Combine(testDirectory.Path, "production"); - var replacement = Path.Combine(testDirectory.Path, "replacement"); + var destination = IOPath.Combine(testDirectory.Path, "production"); + var replacement = IOPath.Combine(testDirectory.Path, "replacement"); WriteArtifactFile(destination, "sources/products/schema.graphqls"); WriteArtifactFile(destination, "sources/reviews/schema.graphqls"); WriteArtifactFile(destination, "nitro-deployment.json"); @@ -577,8 +587,8 @@ public void ReplaceDirectoryAtomically_Should_RemoveSource_WhenSourceWasRemoved( public void ReplaceDirectoryAtomically_Should_RemoveExtensions_WhenExtensionsWereRemoved() { using var testDirectory = new TestDirectory(); - var destination = Path.Combine(testDirectory.Path, "production"); - var replacement = Path.Combine(testDirectory.Path, "replacement"); + var destination = IOPath.Combine(testDirectory.Path, "production"); + var replacement = IOPath.Combine(testDirectory.Path, "replacement"); WriteArtifactFile(destination, "sources/products/schema.graphqls"); WriteArtifactFile( destination, @@ -615,10 +625,10 @@ private static void WriteArtifactFile( string root, string relativePath) { - var path = Path.Combine( + var path = IOPath.Combine( root, - relativePath.Replace('/', Path.DirectorySeparatorChar)); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); + relativePath.Replace('/', IOPath.DirectorySeparatorChar)); + Directory.CreateDirectory(IOPath.GetDirectoryName(path)!); File.WriteAllText(path, "artifact"); } @@ -627,7 +637,7 @@ private static string GetArtifactFiles(string root) Environment.NewLine, Directory .EnumerateFiles(root, "*", SearchOption.AllDirectories) - .Select(path => Path.GetRelativePath(root, path).Replace('\\', '/')) + .Select(path => IOPath.GetRelativePath(root, path).Replace('\\', '/')) .Order()); private static HashSet GetTransitiveDependencies( @@ -664,6 +674,25 @@ private static PipelineStep CreatePipelineStep( Action = _ => Task.CompletedTask }; + private static PipelineStepFactoryContext CreatePipelineStepFactoryContext( + IResource resource) + { + var builder = DistributedApplication.CreateBuilder(); + var services = builder.Services.BuildServiceProvider(); + + return new PipelineStepFactoryContext + { + PipelineContext = new PipelineContext( + new DistributedApplicationModel(builder.Resources), + new DistributedApplicationExecutionContext( + DistributedApplicationOperation.Publish), + services, + NullLogger.Instance, + TestContext.Current.CancellationToken), + Resource = resource + }; + } + private static PipelineConfigurationContext CreatePipelineConfigurationContext( IReadOnlyList steps) @@ -689,8 +718,8 @@ private sealed class TestDirectory : IDisposable { public TestDirectory() { - Path = System.IO.Path.Combine( - System.IO.Path.GetTempPath(), + Path = IOPath.Combine( + IOPath.GetTempPath(), "chilicream-nitro-aspire-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(Path); diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs similarity index 50% rename from src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs rename to src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs index f7c2746065a..ca6c214a41d 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -1,9 +1,11 @@ +using System.Net; +using System.Text; +using System.Text.Json; using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Pipelines; -using ChilliCream.Nitro.Fusion; +using HotChocolate.Fusion.Aspire.Nitro; using HotChocolate.Fusion; -using HotChocolate.Fusion.Aspire; using HotChocolate.Fusion.Packaging; using HotChocolate.Fusion.SourceSchema.Packaging; using Microsoft.Extensions.Configuration; @@ -12,8 +14,9 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using IOPath = System.IO.Path; -namespace ChilliCream.Nitro.Aspire; +namespace HotChocolate.Fusion.Aspire; #pragma warning disable ASPIREPIPELINES001 #pragma warning disable ASPIREPIPELINES004 @@ -23,15 +26,16 @@ public sealed class FusionReleaseAcceptanceTests [Fact] public async Task Release_Should_UploadOnceAndPublishFromNitroAcrossRunners() { + // arrange using var testDirectory = new TestDirectory(); - var sourceCheckout = Path.Combine( + var sourceCheckout = IOPath.Combine( testDirectory.Path, "runner-a-checkout"); - var runnerAOutput = Path.Combine( + var runnerAOutput = IOPath.Combine( testDirectory.Path, "runner-a-output"); - var runnerB = Path.Combine(testDirectory.Path, "unrelated-runner-b"); - var runnerC = Path.Combine(testDirectory.Path, "unrelated-runner-c"); + var runnerB = IOPath.Combine(testDirectory.Path, "unrelated-runner-b"); + var runnerC = IOPath.Combine(testDirectory.Path, "unrelated-runner-c"); Directory.CreateDirectory(sourceCheckout); var productsProjectPath = await CreateSourceCheckoutAsync( @@ -44,9 +48,9 @@ public async Task Release_Should_UploadOnceAndPublishFromNitroAcrossRunners() "Reviews", "reviews", "Review"); - var gatewayDirectory = Path.Combine(sourceCheckout, "Gateway"); + var gatewayDirectory = IOPath.Combine(sourceCheckout, "Gateway"); Directory.CreateDirectory(gatewayDirectory); - var gatewayProjectPath = Path.Combine( + var gatewayProjectPath = IOPath.Combine( gatewayDirectory, "Gateway.csproj"); await File.WriteAllTextAsync( @@ -54,7 +58,7 @@ await File.WriteAllTextAsync( "", TestContext.Current.CancellationToken); - var workflow = new RecordingFusionDeploymentWorkflow(); + using var nitro = new FakeNitro(); var executor = FusionPipelineExecutor.Instance; var buildModel = CreateModel( productsProjectPath, @@ -64,23 +68,19 @@ await File.WriteAllTextAsync( buildModel, "Development", runnerAOutput, - workflow); + nitro); + // act await executor.CreateArtifactsAsync(buildContext); await executor.UploadAsync(buildContext); - Assert.Collection( - workflow.Reconciliations.OrderBy(source => source.Name), - products => - { - Assert.Equal("products", products.Name); - Assert.Equal("release-1", products.Version); - }, - reviews => - { - Assert.Equal("reviews", reviews.Name); - Assert.Equal("release-1", reviews.Version); - }); + // assert + Assert.Equal( + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + nitro.Uploads.OrderBy(source => source.Name, StringComparer.Ordinal)); var runnerBProjects = await CreateAppHostProjectStubsAsync(runnerB); var runnerCProjects = await CreateAppHostProjectStubsAsync(runnerC); @@ -97,7 +97,7 @@ await File.WriteAllTextAsync( stagingModel, "Development", outputPath: null, - workflow: workflow); + nitro: nitro); using var stagingSession = new FusionPipelineSession( stagingContext.CancellationToken); await executor.PreflightAsync(stagingContext, stagingSession); @@ -109,7 +109,7 @@ await File.WriteAllTextAsync( runnerCProjects.GatewayProjectPath), "Test", outputPath: null, - workflow: workflow); + nitro: nitro); using var productionSession = new FusionPipelineSession( productionContext.CancellationToken); await executor.PreflightAsync(productionContext, productionSession); @@ -120,12 +120,10 @@ await File.WriteAllTextAsync( FusionPipeline.SelectDeployments( stagingModel, "Development")); - using (var lease = stagingSession.Acquire(stagingDeployment)) - { - Assert.Equal(0, lease.State.SourceArchiveBytes); - Assert.Throws( - () => lease.State.Sources); - } + var preflightState = stagingSession.GetState(stagingDeployment); + Assert.Equal(0, preflightState.SourceArchiveBytes); + Assert.Throws( + () => preflightState.Sources); await executor.DownloadAsync(stagingContext, stagingSession); await executor.ComposeAsync(stagingContext, stagingSession); @@ -140,23 +138,22 @@ await File.WriteAllTextAsync( Assert.Equal(0, productionSession.DeploymentCount); - Assert.Equal(2, workflow.Reconciliations.Count); - Assert.Equal( - [ - new FusionSourceSchemaVersion("products", "release-1"), - new FusionSourceSchemaVersion("products", "release-1"), - new FusionSourceSchemaVersion("products", "release-1"), - new FusionSourceSchemaVersion("products", "release-1"), - new FusionSourceSchemaVersion("reviews", "release-1"), - new FusionSourceSchemaVersion("reviews", "release-1"), - new FusionSourceSchemaVersion("reviews", "release-1"), - new FusionSourceSchemaVersion("reviews", "release-1") - ], - workflow.Downloads.OrderBy(source => source.Name)); + // every Fusion source is read back from Nitro once per reconciliation and + // once per preflight and composition of both deployments. + $""" + Uploads: {nitro.Uploads.Count} + Downloads: {DescribeDownloads(nitro)} + """.MatchInlineSnapshot( + """ + Uploads: 2 + Downloads: products@release-1 x5, reviews@release-1 x5 + """); + + var publications = nitro.Publications + .OrderBy(publication => publication.Stage, StringComparer.Ordinal) + .ToArray(); Assert.Collection( - workflow.Publications.OrderBy( - publication => publication.Stage, - StringComparer.Ordinal), + publications, development => { Assert.Equal("development", development.Stage); @@ -166,14 +163,13 @@ await File.WriteAllTextAsync( ["products"] = "https://products.development.example.com/graphql", ["reviews"] = "https://reviews.development.example.com/graphql" }, - development.SourceUrls); + ReadSourceUrls(development.FusionArchive)); Assert.Equal( [ new FusionSourceSchemaVersion("products", "release-1"), new FusionSourceSchemaVersion("reviews", "release-1") ], development.Sources); - Assert.NotEmpty(development.FusionArchive); }, test => { @@ -184,28 +180,17 @@ await File.WriteAllTextAsync( ["products"] = "https://products.test.example.com/graphql", ["reviews"] = "https://reviews.test.example.com/graphql" }, - test.SourceUrls); + ReadSourceUrls(test.FusionArchive)); Assert.Equal( [ new FusionSourceSchemaVersion("products", "release-1"), new FusionSourceSchemaVersion("reviews", "release-1") ], test.Sources); - Assert.NotEmpty(test.FusionArchive); }); Assert.NotEqual( - Convert.ToBase64String(workflow.Publications[0].FusionArchive), - Convert.ToBase64String(workflow.Publications[1].FusionArchive)); - Assert.Empty( - Directory.GetFiles( - runnerB, - "fusion-apply.json", - SearchOption.AllDirectories)); - Assert.Empty( - Directory.GetFiles( - runnerC, - "fusion-apply.json", - SearchOption.AllDirectories)); + Convert.ToBase64String(nitro.Publications[0].FusionArchive), + Convert.ToBase64String(nitro.Publications[1].FusionArchive)); Assert.Equal(runnerBTree, SnapshotTree(runnerB)); Assert.Equal(runnerCTree, SnapshotTree(runnerC)); @@ -217,148 +202,232 @@ await File.WriteAllTextAsync( await executor.PublishAsync(stagingContext, repeatedStagingSession); Assert.Equal(0, repeatedStagingSession.DeploymentCount); - Assert.Equal(3, workflow.Publications.Count); + Assert.Equal(3, nitro.Publications.Count); Assert.Equal( - Convert.ToBase64String( - workflow.Publications[0].FusionArchive), - Convert.ToBase64String( - workflow.Publications[2].FusionArchive)); + Convert.ToBase64String(nitro.Publications[0].FusionArchive), + Convert.ToBase64String(nitro.Publications[2].FusionArchive)); Assert.Equal(runnerBTree, SnapshotTree(runnerB)); + } - using var failingSession = new FusionPipelineSession( - stagingContext.CancellationToken); - await executor.PreflightAsync(stagingContext, failingSession); - await executor.DownloadAsync(stagingContext, failingSession); - await executor.ComposeAsync(stagingContext, failingSession); - byte[][] sourceBuffers; - byte[] farBuffer; - using (var lease = failingSession.Acquire(stagingDeployment)) - { - sourceBuffers = lease.State.Sources - .Select(source => source.Archive) - .ToArray(); - farBuffer = lease.State.FusionArchive; - } - workflow.ThrowOnPublish = true; - - var publishException = await Assert.ThrowsAsync( - () => executor.PublishAsync(stagingContext, failingSession)); - - Assert.Equal("Injected publication failure.", publishException.Message); - Assert.Equal(0, failingSession.DeploymentCount); - foreach (var sourceBuffer in sourceBuffers) - { - Assert.Equal(new byte[sourceBuffer.Length], sourceBuffer); - } + [Fact] + public async Task CreateArtifacts_Should_WriteAnOutputDirectoryThatUploadReadsBack() + { + // arrange + // the source checkout is deleted before the upload so the upload can only + // succeed by reading the publish output written by the artifacts step. + using var testDirectory = new TestDirectory(); + var sourceCheckout = IOPath.Combine(testDirectory.Path, "checkout"); + var output = IOPath.Combine(testDirectory.Path, "output"); + Directory.CreateDirectory(sourceCheckout); + var productsProjectPath = await CreateSourceCheckoutAsync( + sourceCheckout, + "Products", + "products", + "Product"); + var reviewsProjectPath = await CreateSourceCheckoutAsync( + sourceCheckout, + "Reviews", + "reviews", + "Review"); + var gatewayDirectory = IOPath.Combine(sourceCheckout, "Gateway"); + Directory.CreateDirectory(gatewayDirectory); + var gatewayProjectPath = IOPath.Combine( + gatewayDirectory, + "Gateway.csproj"); + await File.WriteAllTextAsync( + gatewayProjectPath, + "", + TestContext.Current.CancellationToken); + using var nitro = new FakeNitro(); + var executor = FusionPipelineExecutor.Instance; + var model = CreateModel( + productsProjectPath, + reviewsProjectPath, + gatewayProjectPath); - Assert.Equal(new byte[farBuffer.Length], farBuffer); - Assert.Equal(runnerBTree, SnapshotTree(runnerB)); + // act + await executor.CreateArtifactsAsync( + CreateContext(model, "Development", output, nitro)); + var artifactTree = SnapshotTree(output); + Directory.Delete(sourceCheckout, recursive: true); + await executor.UploadAsync( + CreateContext(model, "Development", output, nitro)); + + // assert + NormalizeTree(artifactTree).MatchInlineSnapshot( + """ + fusion + fusion/development + fusion/development/nitro-deployment-template.json + fusion/development/sources + fusion/development/sources/products + fusion/development/sources/products/provenance.json + fusion/development/sources/products/schema-settings.template.json + fusion/development/sources/products/schema.graphqls + fusion/development/sources/reviews + fusion/development/sources/reviews/provenance.json + fusion/development/sources/reviews/schema-settings.template.json + fusion/development/sources/reviews/schema.graphqls + """); + NormalizeTree(SnapshotTree(output)).MatchInlineSnapshot( + """ + fusion + fusion/development + fusion/development/materialized + fusion/development/materialized/products-release-1.zip + fusion/development/materialized/reviews-release-1.zip + fusion/development/nitro-deployment-template.json + fusion/development/sources + fusion/development/sources/products + fusion/development/sources/products/provenance.json + fusion/development/sources/products/schema-settings.template.json + fusion/development/sources/products/schema.graphqls + fusion/development/sources/reviews + fusion/development/sources/reviews/provenance.json + fusion/development/sources/reviews/schema-settings.template.json + fusion/development/sources/reviews/schema.graphqls + """); + Assert.Equal( + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + nitro.Uploads.OrderBy(source => source.Name, StringComparer.Ordinal)); + } - workflow.ThrowOnPublish = false; - using var publishCancellation = new CancellationTokenSource(); - var cancelingContext = CreateContext( - stagingModel, + [Fact] + public async Task Publish_Should_ClearSessionBuffers_WhenNitroFailsToOpenTheRequest() + { + // arrange + using var testDirectory = new TestDirectory(); + using var nitro = await CreateSeededNitroAsync(); + var context = CreateContext( + CreateModel( + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow, - cancellationToken: publishCancellation.Token); - using var cancelingSession = new FusionPipelineSession( - publishCancellation.Token); - await executor.PreflightAsync(cancelingContext, cancelingSession); - await executor.DownloadAsync(cancelingContext, cancelingSession); - await executor.ComposeAsync(cancelingContext, cancelingSession); - byte[][] cancelingSourceBuffers; - byte[] cancelingFarBuffer; - using (var lease = cancelingSession.Acquire(stagingDeployment)) - { - cancelingSourceBuffers = lease.State.Sources - .Select(source => source.Archive) - .ToArray(); - cancelingFarBuffer = lease.State.FusionArchive; - } - - workflow.BlockNextPublication(); - var cancelingPublish = executor.PublishAsync( - cancelingContext, - cancelingSession); - await workflow.WaitForBlockedPublicationAsync(); + nitro: nitro); + using var session = new FusionPipelineSession(context.CancellationToken); + var executor = FusionPipelineExecutor.Instance; + await executor.PreflightAsync(context, session); + await executor.DownloadAsync(context, session); + await executor.ComposeAsync(context, session); + var deployment = Assert.Single( + FusionPipeline.SelectDeployments(context.Model, "Development")); + var state = session.GetState(deployment); + var sourceBuffers = state.Sources + .Select(source => source.Archive) + .ToArray(); + var fusionArchiveBuffer = state.FusionArchive; + nitro.BeginException = new HttpRequestException("connection refused"); - publishCancellation.Cancel(); + // act + var exception = + await Assert.ThrowsAsync( + () => executor.PublishAsync(context, session)); + // assert + Assert.Equal( + "The Fusion publication request may have been created, but Nitro did " + + "not return a verifiable result.", + exception.Message); + Assert.Equal(0, session.DeploymentCount); Assert.All( - cancelingSourceBuffers, - buffer => Assert.Contains(buffer, value => value != 0)); - Assert.Contains(cancelingFarBuffer, value => value != 0); - - workflow.ContinueBlockedPublication(); - await Assert.ThrowsAnyAsync( - () => cancelingPublish); + sourceBuffers, + buffer => Assert.Equal(new byte[buffer.Length], buffer)); + Assert.Equal( + new byte[fusionArchiveBuffer.Length], + fusionArchiveBuffer); + } - Assert.True(workflow.BlockedPublicationObservedStableBytes); - Assert.Equal(0, cancelingSession.DeploymentCount); + [Fact] + public async Task Publish_Should_ClearSessionBuffers_WhenThePipelineIsCanceled() + { + // arrange + using var testDirectory = new TestDirectory(); + using var nitro = await CreateSeededNitroAsync(); + using var publishCancellation = new CancellationTokenSource(); + var context = CreateContext( + CreateModel( + await CreateAppHostProjectStubsAsync(testDirectory.Path)), + "Development", + outputPath: null, + nitro: nitro, + cancellationToken: publishCancellation.Token); + using var session = new FusionPipelineSession(publishCancellation.Token); + var executor = FusionPipelineExecutor.Instance; + await executor.PreflightAsync(context, session); + await executor.DownloadAsync(context, session); + await executor.ComposeAsync(context, session); + var deployment = Assert.Single( + FusionPipeline.SelectDeployments(context.Model, "Development")); + var state = session.GetState(deployment); + var sourceBuffers = state.Sources + .Select(source => source.Archive) + .ToArray(); + var fusionArchiveBuffer = state.FusionArchive; + nitro.BlockNextPublication(); + + // act + var publish = executor.PublishAsync(context, session); + await nitro.WaitForBlockedPublicationAsync(); + await publishCancellation.CancelAsync(); + nitro.ContinueBlockedPublication(); + + // assert + await Assert.ThrowsAnyAsync(() => publish); + Assert.Empty(nitro.Publications); + Assert.Equal(0, session.DeploymentCount); Assert.All( - cancelingSourceBuffers, + sourceBuffers, buffer => Assert.Equal(new byte[buffer.Length], buffer)); Assert.Equal( - new byte[cancelingFarBuffer.Length], - cancelingFarBuffer); + new byte[fusionArchiveBuffer.Length], + fusionArchiveBuffer); } [Fact] public async Task Download_Should_ClearSession_WhenExactSourceIsMissing() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); + using var nitro = new FakeNitro(); var context = CreateContext( CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow); + nitro: nitro); using var session = new FusionPipelineSession( context.CancellationToken); + // act var exception = await Assert.ThrowsAsync( () => FusionPipelineExecutor.Instance.PreflightAsync( context, session)); + // assert Assert.Equal( "Fusion source 'products' version 'release-1' does not exist on target 'products'.", exception.Message); Assert.Equal(0, session.DeploymentCount); - Assert.Empty( - Directory.GetFiles( - testDirectory.Path, - "fusion-apply.json", - SearchOption.AllDirectories)); - Assert.Empty(workflow.Reconciliations); - Assert.Empty(workflow.Publications); + Assert.Empty(nitro.Uploads); + Assert.Empty(nitro.Publications); } [Fact] public async Task Download_Should_RejectSourceArchive_WhenPerSourceLimitIsExceeded() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); - workflow.SeedSource( - "products", - "release-1", - new byte[] { 1, 2, 3 }); + using var nitro = await CreateSeededNitroAsync(); var context = CreateContext( CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow); + nitro: nitro); var limits = new FusionPipelineMemoryLimits( SourceArchiveBytes: 2, TotalSourceArchiveBytes: 100, @@ -368,9 +437,11 @@ public async Task Download_Should_RejectSourceArchive_WhenPerSourceLimitIsExceed context.CancellationToken, limits); + // act var exception = await Assert.ThrowsAsync( () => executor.PreflightAsync(context, session)); + // assert Assert.Equal( "Downloaded Fusion source 'products@release-1' exceeds the " + "2-byte per-source in-memory size limit.", @@ -381,34 +452,29 @@ public async Task Download_Should_RejectSourceArchive_WhenPerSourceLimitIsExceed [Fact] public async Task Download_Should_RejectSourceArchives_WhenAggregateLimitIsExceeded() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); - workflow.SeedSource( - "products", - "release-1", - new byte[] { 1, 2, 3 }); + using var nitro = await CreateSeededNitroAsync(); var context = CreateContext( CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow); + nitro: nitro); var limits = new FusionPipelineMemoryLimits( - SourceArchiveBytes: 100, + SourceArchiveBytes: 100_000, TotalSourceArchiveBytes: 2, - FusionArchiveBytes: 100); + FusionArchiveBytes: 100_000); var executor = new FusionPipelineExecutor(limits); using var session = new FusionPipelineSession( context.CancellationToken, limits); + // act var exception = await Assert.ThrowsAsync( () => executor.PreflightAsync(context, session)); + // assert Assert.Equal( "The downloaded Fusion sources exceed the 2-byte aggregate " + "in-memory size limit.", @@ -419,177 +485,84 @@ public async Task Download_Should_RejectSourceArchives_WhenAggregateLimitIsExcee [Fact] public async Task Download_Should_FailBeforeComposition_WhenExactSourceChangesAfterPreflight() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); - workflow.SeedSource( - await CreateSourceDownloadAsync( - "products", - "Product")); - workflow.SeedSource( - await CreateSourceDownloadAsync( - "reviews", - "Review")); + using var nitro = await CreateSeededNitroAsync(); var context = CreateContext( CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow); + nitro: nitro); using var session = new FusionPipelineSession( context.CancellationToken); var executor = FusionPipelineExecutor.Instance; await executor.PreflightAsync(context, session); - workflow.OverrideSource( - await CreateSourceDownloadAsync( - "products", - "ChangedProduct")); + nitro.Seed( + "products", + "release-1", + await CreateSourceArchiveAsync("products", "ChangedProduct")); + // act var exception = await Assert.ThrowsAsync( () => executor.DownloadAsync(context, session)); + // assert Assert.Equal( "Fusion source 'products@release-1' changed between preflight and composition.", exception.Message); Assert.Equal(0, session.DeploymentCount); - Assert.Empty(workflow.Publications); + Assert.Empty(nitro.Publications); } [Fact] - public async Task Download_Should_ClearPartialSources_WhenLaterSourceIsMissing() + public async Task Download_Should_ClearSession_WhenLaterSourceIsMissing() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); - workflow.SeedSource( - await CreateSourceDownloadAsync( - "products", - "Product")); - workflow.SeedSource( - await CreateSourceDownloadAsync( - "reviews", - "Review")); + using var nitro = await CreateSeededNitroAsync(); var context = CreateContext( CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), + await CreateAppHostProjectStubsAsync(testDirectory.Path)), "Development", outputPath: null, - workflow: workflow); + nitro: nitro); using var session = new FusionPipelineSession( context.CancellationToken); - await FusionPipelineExecutor.Instance.PreflightAsync( - context, - session); - workflow.RemoveSource("reviews", "release-1"); - var clearedBuffers = new List(); - var executor = new FusionPipelineExecutor( - bufferCleared: clearedBuffers.Add); + var executor = FusionPipelineExecutor.Instance; + await executor.PreflightAsync(context, session); + nitro.Remove("reviews", "release-1"); + // act var exception = await Assert.ThrowsAsync( () => executor.DownloadAsync(context, session)); + // assert Assert.Equal( "Fusion source 'reviews' version 'release-1' does not exist on target 'products'.", exception.Message); - var clearedBuffer = Assert.Single(clearedBuffers); - Assert.Equal(new byte[clearedBuffer.Length], clearedBuffer); - Assert.Equal(0, session.DeploymentCount); - } - - [Fact] - public async Task Download_Should_ClearCurrentSource_WhenCanonicalDigestIsInvalid() - { - using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); - var workflow = new RecordingFusionDeploymentWorkflow(); - var products = await CreateSourceDownloadAsync( - "products", - "Product"); - var invalidProducts = new FusionSourceSchemaDownload( - products.Name, - products.Version, - products.Archive.ToArray(), - new string('0', 64)); - workflow.SeedSource(products); - workflow.SeedSource( - await CreateSourceDownloadAsync( - "reviews", - "Review")); - var context = CreateContext( - CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath), - "Development", - outputPath: null, - workflow: workflow); - using var session = new FusionPipelineSession( - context.CancellationToken); - await FusionPipelineExecutor.Instance.PreflightAsync( - context, - session); - workflow.OverrideSource( - invalidProducts); - var clearedBuffers = new List(); - var executor = new FusionPipelineExecutor( - bufferCleared: clearedBuffers.Add); - - var exception = await Assert.ThrowsAsync( - () => executor.DownloadAsync(context, session)); - - Assert.Equal( - "Downloaded Fusion source 'products@release-1' content does not match its " - + "canonical digest.", - exception.Message); - var clearedBuffer = Assert.Single(clearedBuffers); - Assert.Equal(new byte[clearedBuffer.Length], clearedBuffer); Assert.Equal(0, session.DeploymentCount); } [Fact] - public async Task Session_Should_RejectAndClearState_WhenCancellationPrecedesTransfer() + public async Task SetAll_Should_ClearOwnedBuffers_WhenCancellationPrecedesTransfer() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); var model = CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath); + await CreateAppHostProjectStubsAsync(testDirectory.Path)); var deployment = Assert.Single( FusionPipeline.SelectDeployments(model, "Development")); using var cancellationSource = new CancellationTokenSource(); using var session = new FusionPipelineSession( cancellationSource.Token); var sourceArchive = new byte[] { 1, 2, 3 }; - var state = new FusionDeploymentSessionState( - "release-1", - "https://api.chillicream.com", - "products", - [ - new FusionSessionSourceIdentity( - "products", - "release-1", - "digest") - ]); - state.SetSources( - [ - new FusionSessionSource( - "products", - "release-1", - sourceArchive, - "digest") - ]); + var state = CreateSessionState(sourceArchive, fusionArchive: null); - cancellationSource.Cancel(); + // act + await cancellationSource.CancelAsync(); + // assert Assert.Throws( () => session.SetAll([(deployment, state)])); Assert.Equal(0, session.DeploymentCount); @@ -597,15 +570,12 @@ public async Task Session_Should_RejectAndClearState_WhenCancellationPrecedesTra } [Fact] - public async Task Session_Should_ClearOwnedBuffers_WhenCanceledAfterTransfer() + public async Task Cancel_Should_ClearOwnedBuffers_WhenStateWasTransferred() { + // arrange using var testDirectory = new TestDirectory(); - var projects = await CreateAppHostProjectStubsAsync( - testDirectory.Path); var model = CreateModel( - projects.ProductsProjectPath, - projects.ReviewsProjectPath, - projects.GatewayProjectPath); + await CreateAppHostProjectStubsAsync(testDirectory.Path)); var deployment = Assert.Single( FusionPipeline.SelectDeployments(model, "Development")); using var cancellationSource = new CancellationTokenSource(); @@ -613,6 +583,24 @@ public async Task Session_Should_ClearOwnedBuffers_WhenCanceledAfterTransfer() cancellationSource.Token); var sourceArchive = new byte[] { 1, 2, 3 }; var fusionArchive = new byte[] { 4, 5, 6 }; + session.SetAll( + [(deployment, CreateSessionState(sourceArchive, fusionArchive))]); + + // act + await cancellationSource.CancelAsync(); + + // assert + Assert.Equal(0, session.DeploymentCount); + Assert.Equal(new byte[3], sourceArchive); + Assert.Equal(new byte[3], fusionArchive); + Assert.Throws( + () => session.GetState(deployment)); + } + + private static FusionDeploymentSessionState CreateSessionState( + byte[] sourceArchive, + byte[]? fusionArchive) + { var state = new FusionDeploymentSessionState( "release-1", "https://api.chillicream.com", @@ -631,27 +619,78 @@ public async Task Session_Should_ClearOwnedBuffers_WhenCanceledAfterTransfer() sourceArchive, "digest") ]); - state.SetComposition( - "development", - fusionArchive, - "far-digest"); - session.SetAll([(deployment, state)]); - var lease = session.Acquire(deployment); + if (fusionArchive is not null) + { + state.SetComposition( + "development", + fusionArchive, + "far-digest"); + } - cancellationSource.Cancel(); + return state; + } - Assert.Equal(1, session.DeploymentCount); - Assert.Equal(new byte[] { 1, 2, 3 }, sourceArchive); - Assert.Equal(new byte[] { 4, 5, 6 }, fusionArchive); + private static async Task CreateSeededNitroAsync() + { + var nitro = new FakeNitro(); + nitro.Seed( + "products", + "release-1", + await CreateSourceArchiveAsync("products", "Product")); + nitro.Seed( + "reviews", + "release-1", + await CreateSourceArchiveAsync("reviews", "Review")); + return nitro; + } - lease.Dispose(); + private static string DescribeDownloads(FakeNitro nitro) + => string.Join( + ", ", + nitro.Downloads + .GroupBy(source => $"{source.Name}@{source.Version}") + .OrderBy(group => group.Key, StringComparer.Ordinal) + .Select(group => $"{group.Key} x{group.Count()}")); - Assert.Equal(0, session.DeploymentCount); - Assert.Equal(new byte[3], sourceArchive); - Assert.Equal(new byte[3], fusionArchive); + private static IReadOnlyDictionary ReadSourceUrls( + byte[] fusionArchive) + { + using var stream = new MemoryStream(fusionArchive, writable: false); + using var archive = FusionArchive.Open(stream); + using var configuration = archive + .TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + TestContext.Current.CancellationToken) + .GetAwaiter() + .GetResult() + ?? throw new InvalidDataException( + "The composed Fusion archive has no gateway configuration."); + + return configuration.Settings.RootElement + .GetProperty("sourceSchemas") + .EnumerateObject() + .ToDictionary( + source => source.Name, + source => source.Value + .GetProperty("transports") + .GetProperty("http") + .GetProperty("url") + .GetString() + ?? throw new InvalidDataException( + "The composed source URL is missing."), + StringComparer.Ordinal); } + private static DistributedApplicationModel CreateModel( + (string ProductsProjectPath, + string ReviewsProjectPath, + string GatewayProjectPath) projects) + => CreateModel( + projects.ProductsProjectPath, + projects.ReviewsProjectPath, + projects.GatewayProjectPath); + private static DistributedApplicationModel CreateModel( string productsProjectPath, string reviewsProjectPath, @@ -675,10 +714,10 @@ private static DistributedApplicationModel CreateModel( .WithReference(reviews) .WithGraphQLSchemaComposition(); var nitro = builder - .AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products") - .WithApiKey(apiKey); + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") + .WithNitroApiKey(apiKey); nitro .AddFusionDeployment("development") .ForEnvironment("Development") @@ -699,13 +738,13 @@ private static PipelineStepContext CreateContext( DistributedApplicationModel model, string environmentName, string? outputPath, - IFusionDeploymentWorkflow workflow, + FakeNitro nitro, CancellationToken? cancellationToken = null) { var services = new ServiceCollection() .AddSingleton( new TestHostEnvironment(environmentName)) - .AddSingleton(workflow) + .AddSingleton(nitro.Workflow) .AddSingleton( new ConfigurationBuilder().Build()); services.AddSingleton( @@ -745,9 +784,9 @@ private static async Task CreateSourceCheckoutAsync( string sourceName, string typeName) { - var sourceDirectory = Path.Combine(checkout, projectName); + var sourceDirectory = IOPath.Combine(checkout, projectName); Directory.CreateDirectory(sourceDirectory); - var projectPath = Path.Combine( + var projectPath = IOPath.Combine( sourceDirectory, $"{projectName}.csproj"); await File.WriteAllTextAsync( @@ -755,7 +794,7 @@ await File.WriteAllTextAsync( "", TestContext.Current.CancellationToken); await File.WriteAllTextAsync( - Path.Combine(sourceDirectory, "schema.graphqls"), + IOPath.Combine(sourceDirectory, "schema.graphqls"), $$""" type Query { {{sourceName}}: {{typeName}} @@ -788,16 +827,15 @@ type Query { sourceName, StringComparison.Ordinal); await File.WriteAllTextAsync( - Path.Combine(sourceDirectory, "schema-settings.json"), + IOPath.Combine(sourceDirectory, "schema-settings.json"), settings, TestContext.Current.CancellationToken); return projectPath; } - private static async Task - CreateSourceDownloadAsync( - string sourceName, - string typeName) + private static async Task CreateSourceArchiveAsync( + string sourceName, + string typeName) { await using var stream = new MemoryStream(); using (var archive = FusionSourceSchemaArchive.Create( @@ -808,10 +846,11 @@ await archive.SetArchiveMetadataAsync( new HotChocolate.Fusion.SourceSchema.Packaging.ArchiveMetadata(), TestContext.Current.CancellationToken); await archive.SetSchemaAsync( - System.Text.Encoding.UTF8.GetBytes( - $"type Query {{ value: {typeName} }} type {typeName} {{ id: ID! }}"), + Encoding.UTF8.GetBytes( + $"type Query {{ {sourceName}: {typeName} }} " + + $"type {typeName} {{ id: ID! }}"), TestContext.Current.CancellationToken); - using var settings = System.Text.Json.JsonDocument.Parse( + using var settings = JsonDocument.Parse( $$""" { "name": "{{sourceName}}", @@ -829,16 +868,7 @@ await archive.CommitAsync( TestContext.Current.CancellationToken); } - var archiveBytes = stream.ToArray(); - var digest = await FusionSourceSchemaContent.ComputeSha256Async( - archiveBytes, - sourceName, - TestContext.Current.CancellationToken); - return new FusionSourceSchemaDownload( - sourceName, - "release-1", - archiveBytes, - digest); + return stream.ToArray(); } private static async Task<( @@ -847,21 +877,21 @@ await archive.CommitAsync( string GatewayProjectPath)> CreateAppHostProjectStubsAsync( string runnerPath) { - var appHostDirectory = Path.Combine(runnerPath, "apphost-model"); + var appHostDirectory = IOPath.Combine(runnerPath, "apphost-model"); Directory.CreateDirectory(appHostDirectory); - var productsDirectory = Path.Combine(appHostDirectory, "Products"); - var reviewsDirectory = Path.Combine(appHostDirectory, "Reviews"); - var gatewayDirectory = Path.Combine(appHostDirectory, "Gateway"); + var productsDirectory = IOPath.Combine(appHostDirectory, "Products"); + var reviewsDirectory = IOPath.Combine(appHostDirectory, "Reviews"); + var gatewayDirectory = IOPath.Combine(appHostDirectory, "Gateway"); Directory.CreateDirectory(productsDirectory); Directory.CreateDirectory(reviewsDirectory); Directory.CreateDirectory(gatewayDirectory); - var productsProjectPath = Path.Combine( + var productsProjectPath = IOPath.Combine( productsDirectory, "Products.csproj"); - var reviewsProjectPath = Path.Combine( + var reviewsProjectPath = IOPath.Combine( reviewsDirectory, "Reviews.csproj"); - var gatewayProjectPath = Path.Combine( + var gatewayProjectPath = IOPath.Combine( gatewayDirectory, "Gateway.csproj"); await File.WriteAllTextAsync( @@ -887,49 +917,69 @@ await File.WriteAllTextAsync( runnerPath, "schema-settings.json", SearchOption.AllDirectories)); - Assert.Empty( - Directory.GetFiles( - runnerPath, - "*manifest*", - SearchOption.AllDirectories)); - Assert.Empty( - Directory.GetDirectories( - runnerPath, - ".git", - SearchOption.AllDirectories)); return ( productsProjectPath, reviewsProjectPath, gatewayProjectPath); } + private static string NormalizeTree(IReadOnlyList tree) + => string.Join('\n', tree).Replace('\\', '/'); + private static IReadOnlyList SnapshotTree(string path) => Directory.EnumerateFileSystemEntries( path, "*", SearchOption.AllDirectories) - .Select(entry => Path.GetRelativePath(path, entry)) + .Select(entry => IOPath.GetRelativePath(path, entry)) .Order(StringComparer.Ordinal) .ToArray(); - private sealed class RecordingFusionDeploymentWorkflow - : IFusionDeploymentWorkflow + private sealed record RecordedPublication( + string Stage, + IReadOnlyList Sources, + byte[] FusionArchive); + + /// + /// An in-memory Nitro API that stores immutable source schema versions and answers the + /// publication protocol with a successful terminal result. + /// + private sealed class FakeNitro : HttpMessageHandler { - private readonly Dictionary< - (string CloudUrl, string ApiId, string Name, string Version), - StoredSourceDownload> _sources = []; + private readonly Dictionary _sources = + new(StringComparer.Ordinal); + private readonly HttpClient _httpClient; + private readonly NitroFusionApi _api; + private (string Name, string Version)? _missingVersion; + private (string Stage, IReadOnlyList Sources)? _openRequest; private TaskCompletionSource? _publishStarted; private TaskCompletionSource? _continuePublish; - public List Reconciliations { get; } = []; + public FakeNitro() + { + _httpClient = new HttpClient(this, disposeHandler: false) + { + Timeout = Timeout.InfiniteTimeSpan + }; + _api = new NitroFusionApi(_httpClient, disposeHttpClient: false); + Workflow = new FusionDeploymentWorkflow(_api); + } + + public FusionDeploymentWorkflow Workflow { get; } public List Downloads { get; } = []; + public List Uploads { get; } = []; + public List Publications { get; } = []; - public bool ThrowOnPublish { get; set; } + public Exception? BeginException { get; set; } + + public void Seed(string name, string version, byte[] archive) + => _sources[$"{name}@{version}"] = archive; - public bool BlockedPublicationObservedStableBytes { get; private set; } + public void Remove(string name, string version) + => _sources.Remove($"{name}@{version}"); public void BlockNextPublication() { @@ -937,7 +987,6 @@ public void BlockNextPublication() TaskCreationOptions.RunContinuationsAsynchronously); _continuePublish = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - BlockedPublicationObservedStableBytes = false; } public Task WaitForBlockedPublicationAsync() @@ -951,198 +1000,248 @@ public void ContinueBlockedPublication() "No publication is blocked.")) .TrySetResult(); - public void SeedSource( - string name, - string version, - byte[] archive) + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) { - var target = new FusionTarget( - new Uri("https://api.chillicream.com"), - "products", - "test-api-key"); - _sources.Add( - CreateKey(target, name, version), - new StoredSourceDownload( - name, - version, - archive, - "unused-before-bound-validation")); + if (request.Method == HttpMethod.Get) + { + return Download(request); + } + + var (operationName, variables, file) = + await ReadRequestAsync(request, cancellationToken); + + switch (operationName) + { + case NitroOperationDocuments.UploadSourceSchemaOperationName: + return Upload(variables, file); + + case NitroOperationDocuments.BeginDeploymentOperationName: + await BlockAsync(cancellationToken); + if (BeginException is not null) + { + throw BeginException; + } + + return Begin(variables); + + case NitroOperationDocuments.WatchDeploymentOperationName: + return Watch(); + + case NitroOperationDocuments.ClaimDeploymentOperationName: + return Json(CommandResult("startFusionConfigurationComposition")); + + case NitroOperationDocuments.ValidateDeploymentOperationName: + AssertFileName(file, "gateway.fgp"); + return Json(CommandResult("validateFusionConfigurationComposition")); + + case NitroOperationDocuments.CommitDeploymentOperationName: + AssertFileName(file, "gateway.far"); + return Commit(file); + + case NitroOperationDocuments.ReleaseDeploymentOperationName: + return Json(CommandResult("cancelFusionConfigurationComposition")); + + default: + throw new InvalidOperationException( + $"The Nitro operation '{operationName}' is not scripted."); + } } - public void SeedSource(FusionSourceSchemaDownload source) + protected override void Dispose(bool disposing) { - var target = CreateTestTarget(); - _sources.Add( - CreateKey(target, source.Name, source.Version), - TakeOwnership(source)); + if (disposing) + { + _api.Dispose(); + _httpClient.Dispose(); + } + + base.Dispose(disposing); } - public void OverrideSource(FusionSourceSchemaDownload source) + private async Task BlockAsync(CancellationToken cancellationToken) { - var target = CreateTestTarget(); - var key = CreateKey(target, source.Name, source.Version); - var replacement = TakeOwnership(source); - if (_sources.TryGetValue(key, out var previous)) + if (_publishStarted is null || _continuePublish is null) { - Array.Clear(previous.Archive); + return; } - _sources[key] = replacement; + _publishStarted.TrySetResult(); + await _continuePublish.Task; + cancellationToken.ThrowIfCancellationRequested(); } - public void RemoveSource(string name, string version) + private HttpResponseMessage Download(HttpRequestMessage request) { - var target = CreateTestTarget(); - if (_sources.Remove( - CreateKey(target, name, version), - out var removed)) + var segments = request.RequestUri!.AbsolutePath.Split('/'); + var name = Uri.UnescapeDataString(segments[6]); + var version = Uri.UnescapeDataString(segments[8]); + Downloads.Add(new FusionSourceSchemaVersion(name, version)); + + if (!_sources.TryGetValue($"{name}@{version}", out var archive)) { - Array.Clear(removed.Archive); + _missingVersion = (name, version); + return new HttpResponseMessage(HttpStatusCode.NotFound) + { + Content = new StringContent(string.Empty) + }; } + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(archive.ToArray()) + }; } - public async Task ReconcileSourceSchemaAsync( - FusionTarget target, - FusionSourceSchemaUpload source, - CancellationToken cancellationToken) + private HttpResponseMessage Upload( + JsonElement variables, + UploadedFile? file) { - var archive = await File.ReadAllBytesAsync( - source.ArchivePath, - cancellationToken); - var contentSha256 = - await FusionSourceSchemaContent.ComputeSha256Async( - source.ArchivePath, - source.Name, - cancellationToken); - _sources.Add( - CreateKey(target, source.Name, source.Version), - new StoredSourceDownload( - source.Name, - source.Version, - archive, - contentSha256)); - Reconciliations.Add(source); + AssertFileName(file, "source-schema.zip"); + var version = variables + .GetProperty("input") + .GetProperty("tag") + .GetString()!; + var name = _missingVersion?.Name + ?? throw new InvalidOperationException( + "Nitro received an upload for a version that was never read back."); + _sources[$"{name}@{version}"] = file!.Content; + Uploads.Add(new FusionSourceSchemaVersion(name, version)); + + return Json( + """ + {"data":{"uploadFusionSubgraph":{"fusionSubgraphVersion":{"id":"version-1"},"errors":[]}}} + """); } - public Task DownloadSourceSchemaAsync( - FusionTarget target, - FusionSourceSchemaVersion source, - CancellationToken cancellationToken) + private HttpResponseMessage Begin(JsonElement variables) { - Downloads.Add(source); - _sources.TryGetValue( - CreateKey(target, source.Name, source.Version), - out var download); - return Task.FromResult( - download is null - ? null - : new FusionSourceSchemaDownload( - download.Name, - download.Version, - download.Archive.ToArray(), - download.ContentSha256)); + var input = variables.GetProperty("input"); + _openRequest = ( + input.GetProperty("stageName").GetString()!, + input.GetProperty("subgraphs") + .EnumerateArray() + .Select(source => new FusionSourceSchemaVersion( + source.GetProperty("name").GetString()!, + source.GetProperty("tag").GetString()!)) + .ToArray()); + + return Json( + """ + {"data":{"beginFusionConfigurationPublish":{"requestId":"request-1","errors":[]}}} + """); } - private static StoredSourceDownload TakeOwnership( - FusionSourceSchemaDownload source) + private HttpResponseMessage Commit(UploadedFile? file) { - using (source) + var openRequest = _openRequest + ?? throw new InvalidOperationException( + "Nitro received a commit without an open publication request."); + Publications.Add( + new RecordedPublication( + openRequest.Stage, + openRequest.Sources, + file!.Content)); + _openRequest = null; + + return Json(CommandResult("commitFusionConfigurationPublish")); + } + + private static HttpResponseMessage Watch() + => new(HttpStatusCode.OK) { - return new StoredSourceDownload( - source.Name, - source.Version, - source.Archive.ToArray(), - source.ContentSha256); + Content = new StringContent( + BuildEventStream( + "ProcessingTaskIsReady", + "FusionConfigurationValidationSuccess", + "FusionConfigurationPublishingSuccess"), + Encoding.UTF8, + "text/event-stream") + }; + + private static string BuildEventStream(params string[] typeNames) + { + var builder = new StringBuilder(); + + foreach (var typeName in typeNames) + { + builder + .Append("event: next\n") + .Append("data: ") + .Append( + "{\"data\":{\"onFusionConfigurationPublishingTaskChanged\":{" + + "\"__typename\":\"" + typeName + "\"," + + "\"state\":\"PROCESSING\",\"errors\":[]}}}") + .Append("\n\n"); } + + return builder.Append("event: complete\n\n").ToString(); } - public async Task PublishAsync( - FusionPublicationRequest request, - ReadOnlyMemory fusionArchive, - CancellationToken cancellationToken) + private static void AssertFileName(UploadedFile? file, string expected) { - if (ThrowOnPublish) + Assert.NotNull(file); + Assert.Equal(expected, file.FileName); + } + + private static string CommandResult(string fieldName) + => "{\"data\":{\"" + fieldName + "\":{\"errors\":[]}}}"; + + private static HttpResponseMessage Json(string json) + => new(HttpStatusCode.OK) { - throw new InvalidOperationException( - "Injected publication failure."); - } + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + + private static async Task<( + string OperationName, + JsonElement Variables, + UploadedFile? File)> ReadRequestAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + string operations; + UploadedFile? file = null; - if (_publishStarted is not null - && _continuePublish is not null) + if (request.Content is MultipartFormDataContent multipart) { - var beforeCancellation = fusionArchive.ToArray(); - _publishStarted.TrySetResult(); - await _continuePublish.Task; - BlockedPublicationObservedStableBytes = - fusionArchive.Span.SequenceEqual(beforeCancellation); - cancellationToken.ThrowIfCancellationRequested(); + operations = string.Empty; + foreach (var part in multipart) + { + var disposition = part.Headers.ContentDisposition; + if (disposition?.FileName is { } fileName) + { + file = new UploadedFile( + fileName.Trim('"'), + await part.ReadAsByteArrayAsync(cancellationToken)); + } + else if (disposition?.Name?.Trim('"') is "operations") + { + operations = await part.ReadAsStringAsync(cancellationToken); + } + } + } + else + { + operations = await request.Content!.ReadAsStringAsync( + cancellationToken); } - var fusionArchiveBytes = fusionArchive.ToArray(); - await using var fusionArchiveStream = new MemoryStream( - fusionArchiveBytes, - writable: false); - using var archive = FusionArchive.Open(fusionArchiveStream); - using var configuration = - await archive.TryGetGatewayConfigurationAsync( - WellKnownVersions.LatestGatewayFormatVersion, - cancellationToken) - ?? throw new InvalidDataException( - "The composed Fusion archive has no gateway configuration."); - var sourceUrls = configuration.Settings.RootElement - .GetProperty("sourceSchemas") - .EnumerateObject() - .ToDictionary( - source => source.Name, - source => source.Value - .GetProperty("transports") - .GetProperty("http") - .GetProperty("url") - .GetString() - ?? throw new InvalidDataException( - "The composed source URL is missing."), - StringComparer.Ordinal); - Publications.Add( - new RecordedPublication( - request.Stage, - request.SourceSchemas.ToArray(), - sourceUrls, - fusionArchiveBytes)); + using var document = JsonDocument.Parse(operations); + var operationName = document.RootElement + .GetProperty("operationName") + .GetString()!; + var variables = document.RootElement + .GetProperty("variables") + .Clone(); + + return (operationName, variables, file); } - private static ( - string CloudUrl, - string ApiId, - string Name, - string Version) CreateKey( - FusionTarget target, - string name, - string version) - => ( - target.CloudUrl.AbsoluteUri.TrimEnd('/'), - target.ApiId, - name, - version); - - private static FusionTarget CreateTestTarget() - => new( - new Uri("https://api.chillicream.com"), - "products", - "test-api-key"); - - private sealed record StoredSourceDownload( - string Name, - string Version, - byte[] Archive, - string ContentSha256); + private sealed record UploadedFile(string FileName, byte[] Content); } - private sealed record RecordedPublication( - string Stage, - IReadOnlyList Sources, - IReadOnlyDictionary SourceUrls, - byte[] FusionArchive); - private sealed class ThrowingPipelineOutputService : IPipelineOutputService { @@ -1166,13 +1265,13 @@ private sealed class TestPipelineOutputService(string outputPath) public string GetOutputDirectory() => outputPath; public string GetOutputDirectory(IResource resource) - => Path.Combine(outputPath, resource.Name); + => IOPath.Combine(outputPath, resource.Name); public string GetTempDirectory() - => Path.Combine(outputPath, ".temp"); + => IOPath.Combine(outputPath, ".temp"); public string GetTempDirectory(IResource resource) - => Path.Combine(outputPath, ".temp", resource.Name); + => IOPath.Combine(outputPath, ".temp", resource.Name); } private sealed class TestHostEnvironment(string environmentName) @@ -1194,8 +1293,8 @@ private sealed class TestDirectory : IDisposable { public TestDirectory() { - Path = System.IO.Path.Combine( - System.IO.Path.GetTempPath(), + Path = IOPath.Combine( + IOPath.GetTempPath(), "chilicream-nitro-aspire-acceptance-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(Path); diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/FusionDeploymentWorkflowTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/FusionDeploymentWorkflowTests.cs new file mode 100644 index 00000000000..7394b73dd6e --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/FusionDeploymentWorkflowTests.cs @@ -0,0 +1,1091 @@ +using System.IO.Compression; +using System.Net; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using HotChocolate.Fusion.SourceSchema.Packaging; +using IOPath = System.IO.Path; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +public sealed class FusionDeploymentWorkflowTests +{ + [Fact] + public void Assembly_Should_ExposeNoNitroType_When_Inspected() + { + // act + var exportedTypes = typeof(FusionDeploymentWorkflow) + .Assembly + .GetExportedTypes() + .Where(type => type.Namespace is "HotChocolate.Fusion.Aspire.Nitro") + .Select(type => type.FullName) + .Order(StringComparer.Ordinal) + .ToArray(); + + // assert + Assert.Empty(exportedTypes); + } + + [Fact] + public void PublishAsync_Should_AcceptFusionArchiveFromMemory_When_Inspected() + { + typeof(FusionDeploymentWorkflow) + .GetMethod(nameof(FusionDeploymentWorkflow.PublishAsync))! + .ToString() + .MatchInlineSnapshot( + """ + System.Threading.Tasks.Task PublishAsync(HotChocolate.Fusion.Aspire.Nitro.FusionPublicationRequest, System.ReadOnlyMemory`1[System.Byte], System.Threading.CancellationToken) + """); + } + + [Fact] + public void Dispose_Should_ClearOwnedArchiveAndRejectAccess_When_Called() + { + byte[] archive = [1, 2, 3]; + var download = new FusionSourceSchemaDownload( + "products", + "20260730", + archive, + new string('A', 64)); + + Assert.Equal(new byte[] { 1, 2, 3 }, download.Archive.ToArray()); + + download.Dispose(); + + Assert.Equal(new byte[3], archive); + Assert.Throws(() => download.Archive); + + archive[0] = 9; + download.Dispose(); + Assert.Equal(new byte[] { 9, 0, 0 }, archive); + } + + [Fact] + public async Task ComputeSha256Async_Should_ReturnSameDigest_When_ContentIsNormalized() + { + var directory = CreateTemporaryDirectory(); + try + { + var firstPath = IOPath.Combine(directory, "first.fss"); + var secondPath = IOPath.Combine(directory, "second.fss"); + await CreateArchiveAsync( + firstPath, + "products", + "type Query { product: String }", + """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); + await CreateArchiveAsync( + secondPath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + + var first = await FusionSourceSchemaContent.ComputeSha256Async( + firstPath, + "products", + TestContext.Current.CancellationToken); + var second = await FusionSourceSchemaContent.ComputeSha256Async( + secondPath, + "products", + TestContext.Current.CancellationToken); + + Assert.Equal(first, second); + first.MatchInlineSnapshot( + """ + 619211A45A3F75EC35BC88FAF88BA74173FE42C157B7F3020CBC896198CFE224 + """); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_ReturnArchiveAndDigest_When_VersionExists() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var remotePath = IOPath.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + remotePath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + var remoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken); + var nitro = new FakeNitro { RemoteArchive = remoteArchive }; + var workflow = CreateWorkflow(nitro); + + // act + using var result = await workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + TestContext.Current.CancellationToken); + + // assert + Assert.NotNull(result); + var ownedArchive = GetUnderlyingArray(result.Archive); + $""" + Name: {result.Name} + Version: {result.Version} + Content SHA-256: {result.ContentSha256} + Archive matches: {result.Archive.ToArray().SequenceEqual(remoteArchive)} + Requested: {nitro.LastDownloadName}/{nitro.LastDownloadVersion} + API key: {nitro.LastApiKey} + """.MatchInlineSnapshot( + """ + Name: products + Version: 20260730 + Content SHA-256: 619211A45A3F75EC35BC88FAF88BA74173FE42C157B7F3020CBC896198CFE224 + Archive matches: True + Requested: products/20260730 + API key: secret + """); + + result.Dispose(); + Assert.Equal(new byte[ownedArchive.Length], ownedArchive); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_ReturnNull_When_VersionDoesNotExist() + { + // arrange + var nitro = new FakeNitro(); + var workflow = CreateWorkflow(nitro); + + // act + var result = await workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "missing"), + TestContext.Current.CancellationToken); + + // assert + Assert.Null(result); + Assert.Equal(1, nitro.DownloadCount); + Assert.Equal("products", nitro.LastDownloadName); + Assert.Equal("missing", nitro.LastDownloadVersion); + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_Throw_When_NitroRejectsTheApiKey() + { + // arrange + var nitro = new FakeNitro { DownloadStatusCode = HttpStatusCode.Unauthorized }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal("Nitro rejected the supplied API key.", exception.Message); + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_NameDiffers() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var remotePath = IOPath.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + remotePath, + "inventory", + "type Query { product: String }", + """{"name":"inventory"}"""); + var nitro = new FakeNitro + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "The source schema settings name must exactly match 'products'.", + exception.Message); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_DecompressedEntryIsTooLarge() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var remotePath = IOPath.Combine(directory, "remote.fss"); + await CreateOversizedSettingsArchiveAsync(remotePath); + var nitro = new FakeNitro + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.DownloadSourceSchemaAsync( + CreateTarget(), + new FusionSourceSchemaVersion("products", "20260730"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "The Fusion source schema archive is invalid.", + exception.Message); + Assert.IsType(exception.InnerException); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_NoOp_When_NormalizedContentMatches() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var localPath = IOPath.Combine(directory, "local.fss"); + var remotePath = IOPath.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + localPath, + "products", + "type Query { product: String }", + """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); + await CreateArchiveAsync( + remotePath, + "products", + """ + type Query { + product: String + } + """, + """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); + var nitro = new FakeNitro + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(nitro); + + // act + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(localPath), + TestContext.Current.CancellationToken); + + // assert + Assert.Equal(0, nitro.UploadCount); + Assert.Equal(1, nitro.DownloadCount); + Assert.Empty(nitro.Calls); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_ThrowCollision_When_ContentDiffers() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var localPath = IOPath.Combine(directory, "local.fss"); + var remotePath = IOPath.Combine(directory, "remote.fss"); + await CreateArchiveAsync( + localPath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + await CreateArchiveAsync( + remotePath, + "products", + "type Query { product: Int }", + """{"name":"products"}"""); + var nitro = new FakeNitro + { + RemoteArchive = await File.ReadAllBytesAsync( + remotePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(nitro); + var upload = await CreateUploadAsync(localPath); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + upload, + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Source schema 'products' version '20260730' already exists " + + "with different normalized schema, settings, or extensions.", + exception.Message); + Assert.Equal(0, nitro.UploadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_UploadArchive_When_VersionIsMissing() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var archivePath = IOPath.Combine(directory, "products.fss"); + await CreateArchiveAsync( + archivePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var nitro = new FakeNitro(); + var workflow = CreateWorkflow(nitro); + + // act + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(archivePath), + TestContext.Current.CancellationToken); + + // assert + $""" + Calls: {string.Join(", ", nitro.Calls)} + Uploads: {nitro.UploadCount} + Multipart: {nitro.LastContentType} + File name: {nitro.UploadBody!.Contains("source-schema.zip", StringComparison.Ordinal)} + Tag: {nitro.UploadBody!.Contains("20260730", StringComparison.Ordinal)} + API key: {nitro.LastApiKey} + """.MatchInlineSnapshot( + """ + Calls: UploadFusionSourceSchema + Uploads: 1 + Multipart: multipart/form-data + File name: True + Tag: True + API key: secret + """); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_VerifyReadBack_When_UploadIsUncertain() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var archivePath = IOPath.Combine(directory, "products.fss"); + await CreateArchiveAsync( + archivePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var archive = await File.ReadAllBytesAsync( + archivePath, + TestContext.Current.CancellationToken); + var nitro = new FakeNitro + { + UploadException = new IOException("Connection reset."), + RemoteArchiveAfterUpload = archive + }; + var workflow = CreateWorkflow(nitro); + + // act + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(archivePath), + TestContext.Current.CancellationToken); + + // assert + Assert.Equal(1, nitro.UploadCount); + Assert.Equal(2, nitro.DownloadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_VerifyReadBack_When_TagIsDuplicated() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var archivePath = IOPath.Combine(directory, "products.fss"); + await CreateArchiveAsync( + archivePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var nitro = new FakeNitro + { + UploadErrorTypeName = "DuplicatedTagError", + RemoteArchiveAfterUpload = await File.ReadAllBytesAsync( + archivePath, + TestContext.Current.CancellationToken) + }; + var workflow = CreateWorkflow(nitro); + + // act + await workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + await CreateUploadAsync(archivePath), + TestContext.Current.CancellationToken); + + // assert + Assert.Equal(1, nitro.UploadCount); + Assert.Equal(2, nitro.DownloadCount); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task ReconcileSourceSchemaAsync_Should_Fail_When_UploadIsRejected() + { + // arrange + var directory = CreateTemporaryDirectory(); + try + { + var archivePath = IOPath.Combine(directory, "products.fss"); + await CreateArchiveAsync( + archivePath, + "products", + "type Query { product: String }", + """{"name":"products"}"""); + var nitro = new FakeNitro + { + UploadErrorTypeName = "InvalidSourceMetadataInputError", + UploadErrorMessage = "The source metadata is invalid." + }; + var workflow = CreateWorkflow(nitro); + var upload = await CreateUploadAsync(archivePath); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.ReconcileSourceSchemaAsync( + CreateTarget(), + upload, + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro rejected the Fusion source schema upload. " + + "The source metadata is invalid.", + exception.Message); + } + finally + { + Directory.Delete(directory, recursive: true); + } + } + + [Fact] + public async Task PublishAsync_Should_Commit_When_ValidationSucceeds() + { + // arrange + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var nitro = new FakeNitro + { + Events = + [ + new WatchEvent("ProcessingTaskIsReady"), + new WatchEvent("FusionConfigurationValidationSuccess"), + new WatchEvent("FusionConfigurationPublishingSuccess") + ] + }; + var workflow = CreateWorkflow(nitro); + + // act + await workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchive, + TestContext.Current.CancellationToken); + + // assert + $""" + Calls: {string.Join(", ", nitro.Calls)} + Validated archive: {DescribeConfiguration(nitro.ValidateBody)} + Committed archive: {DescribeConfiguration(nitro.CommitBody)} + """.MatchInlineSnapshot( + """ + Calls: BeginFusionDeployment, WatchFusionDeployment, ClaimFusionDeployment, ValidateFusionDeployment, CommitFusionDeployment + Validated archive: gateway.fgp, fusion archive + Committed archive: gateway.far, fusion archive + """); + } + + [Fact] + public async Task PublishAsync_Should_ReleaseAndFail_When_ValidationFailsWithoutForce() + { + // arrange + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var nitro = new FakeNitro + { + Events = + [ + new WatchEvent("ProcessingTaskIsReady"), + new WatchEvent( + "FusionConfigurationValidationFailed", + ["Breaking schema change."]) + ] + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + fusionArchive, + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro rejected the Fusion configuration validation. " + + "Breaking schema change.", + exception.Message); + Assert.Equal( + [ + "BeginFusionDeployment", + "WatchFusionDeployment", + "ClaimFusionDeployment", + "ValidateFusionDeployment", + "ReleaseFusionDeployment" + ], + nitro.Calls); + } + + [Fact] + public async Task PublishAsync_Should_Commit_When_ValidationFailsWithForce() + { + // arrange + var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); + var nitro = new FakeNitro + { + Events = + [ + new WatchEvent("ProcessingTaskIsReady"), + new WatchEvent( + "FusionConfigurationValidationFailed", + ["Breaking schema change."]), + new WatchEvent("FusionConfigurationPublishingSuccess") + ] + }; + var workflow = CreateWorkflow(nitro); + + // act + await workflow.PublishAsync( + CreatePublicationRequest(force: true), + fusionArchive, + TestContext.Current.CancellationToken); + + // assert + Assert.Equal( + [ + "BeginFusionDeployment", + "WatchFusionDeployment", + "ClaimFusionDeployment", + "ValidateFusionDeployment", + "CommitFusionDeployment" + ], + nitro.Calls); + } + + [Fact] + public async Task PublishAsync_Should_Fail_When_NitroRejectsTheRequest() + { + // arrange + var nitro = new FakeNitro + { + BeginErrorMessage = "The stage does not exist." + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + Encoding.UTF8.GetBytes("fusion archive"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro rejected the Fusion publication request. The stage does not exist.", + exception.Message); + Assert.Equal(["BeginFusionDeployment"], nitro.Calls); + } + + [Fact] + public async Task PublishAsync_Should_Fail_When_TheSubscriptionEndsWithoutATerminalResult() + { + // arrange + var nitro = new FakeNitro + { + Events = [new WatchEvent("ProcessingTaskIsQueued")] + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + Encoding.UTF8.GetBytes("fusion archive"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "The Fusion publication event stream ended before Nitro reported " + + "a terminal result.", + exception.Message); + Assert.Equal("request-id", exception.RequestId); + } + + [Fact] + public async Task PublishAsync_Should_Fail_When_TheSubscriptionIsNotAnEventStream() + { + // arrange + // A server that ignores the pinned Accept header answers with a single JSON result. + var nitro = new FakeNitro + { + Events = [new WatchEvent("ProcessingTaskIsReady")], + WatchIsEventStream = false + }; + var workflow = CreateWorkflow(nitro); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.PublishAsync( + CreatePublicationRequest(force: false), + Encoding.UTF8.GetBytes("fusion archive"), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "The Fusion publication event stream ended with an error.", + exception.Message); + Assert.Equal( + "Nitro answered the subscription with the content type " + + "'application/json' instead of text/event-stream.", + exception.InnerException!.Message); + } + + private static FusionDeploymentWorkflow CreateWorkflow(FakeNitro nitro) + => new(new NitroFusionApi(new HttpClient(nitro), disposeHttpClient: true)); + + private static FusionTarget CreateTarget() + => new( + new Uri("https://api.chillicream.com"), + "api-id", + "secret"); + + private static FusionPublicationRequest CreatePublicationRequest(bool force) + => new( + CreateTarget(), + "production", + "20260730", + [new FusionSourceSchemaVersion("products", "20260730")], + WaitForApproval: false, + Force: force, + OperationTimeout: TimeSpan.FromMinutes(1), + ApprovalTimeout: TimeSpan.FromMinutes(1)); + + private static string DescribeConfiguration(string? body) + { + if (body is null) + { + return "-"; + } + + var fileName = body.Contains("gateway.fgp", StringComparison.Ordinal) + ? "gateway.fgp" + : body.Contains("gateway.far", StringComparison.Ordinal) + ? "gateway.far" + : "unknown file"; + var content = body.Contains("fusion archive", StringComparison.Ordinal) + ? "fusion archive" + : "unknown content"; + + return $"{fileName}, {content}"; + } + + private static byte[] GetUnderlyingArray(ReadOnlyMemory archive) + { + Assert.True(MemoryMarshal.TryGetArray(archive, out ArraySegment segment)); + return segment.Array!; + } + + private static async Task CreateUploadAsync( + string archivePath) + { + await using var stream = File.OpenRead(archivePath); + var sha256 = Convert.ToHexString( + await SHA256.HashDataAsync( + stream, + TestContext.Current.CancellationToken)); + return new FusionSourceSchemaUpload( + "products", + "20260730", + archivePath, + sha256); + } + + private static async Task CreateArchiveAsync( + string path, + string name, + string schema, + string settings) + { + using var archive = FusionSourceSchemaArchive.Create(path); + await archive.SetArchiveMetadataAsync( + new ArchiveMetadata(), + TestContext.Current.CancellationToken); + await archive.SetSchemaAsync( + Encoding.UTF8.GetBytes(schema), + TestContext.Current.CancellationToken); + using var settingsDocument = JsonDocument.Parse(settings); + await archive.SetSettingsAsync( + settingsDocument, + TestContext.Current.CancellationToken); + await archive.CommitAsync(TestContext.Current.CancellationToken); + } + + private static async Task CreateOversizedSettingsArchiveAsync(string path) + { + await using var stream = File.Create(path); +#if NET10_0_OR_GREATER + await using var archive = new ZipArchive( +#else + using var archive = new ZipArchive( +#endif + stream, + ZipArchiveMode.Create, + leaveOpen: true); + await using (var schema = archive.CreateEntry("schema.graphqls").Open()) + { + await schema.WriteAsync( + Encoding.UTF8.GetBytes("type Query { product: String }"), + TestContext.Current.CancellationToken); + } + + await using (var settings = + archive.CreateEntry("schema-settings.json").Open()) + { + await settings.WriteAsync( + Encoding.UTF8.GetBytes( + $$"""{"name":"products","padding":"{{new string('a', 512_000)}}"}"""), + TestContext.Current.CancellationToken); + } + } + + private static string CreateTemporaryDirectory() + { + var path = IOPath.Combine( + IOPath.GetTempPath(), + "nitro-fusion-tests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } + + private sealed record WatchEvent(string TypeName) + { + public WatchEvent(string typeName, IReadOnlyList errors) + : this(typeName) + { + Errors = errors; + } + + public IReadOnlyList Errors { get; } = []; + } + + private sealed class FakeNitro : HttpMessageHandler + { + private const string ApiKeyHeader = "CCC-api-key"; + + public byte[]? RemoteArchive { get; set; } + + public byte[]? RemoteArchiveAfterUpload { get; set; } + + public HttpStatusCode? DownloadStatusCode { get; set; } + + public Exception? UploadException { get; set; } + + public string? UploadErrorTypeName { get; set; } + + public string UploadErrorMessage { get; set; } = "The upload was rejected."; + + public string? BeginErrorMessage { get; set; } + + public IReadOnlyList Events { get; init; } = []; + + public bool WatchIsEventStream { get; init; } = true; + + public int DownloadCount { get; private set; } + + public int UploadCount { get; private set; } + + public string? LastDownloadName { get; private set; } + + public string? LastDownloadVersion { get; private set; } + + public string? LastApiKey { get; private set; } + + public string? LastContentType { get; private set; } + + public List Calls { get; } = []; + + public string? UploadBody { get; private set; } + + public string? ValidateBody { get; private set; } + + public string? CommitBody { get; private set; } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + LastApiKey = request.Headers.TryGetValues(ApiKeyHeader, out var values) + ? values.Single() + : null; + + if (request.Method == HttpMethod.Get) + { + return Download(request); + } + + var body = request.Content is null + ? string.Empty + : await request.Content.ReadAsStringAsync(cancellationToken); + LastContentType = request.Content?.Headers.ContentType?.MediaType; + var operationName = ReadOperationName(body); + Calls.Add(operationName); + + switch (operationName) + { + case NitroOperationDocuments.UploadSourceSchemaOperationName: + UploadCount++; + UploadBody = body; + if (UploadException is not null) + { + throw UploadException; + } + + return Json(UploadResult()); + + case NitroOperationDocuments.BeginDeploymentOperationName: + return Json(BeginResult()); + + case NitroOperationDocuments.WatchDeploymentOperationName: + return Watch(); + + case NitroOperationDocuments.ClaimDeploymentOperationName: + return Json(CommandResult("startFusionConfigurationComposition")); + + case NitroOperationDocuments.ValidateDeploymentOperationName: + ValidateBody = body; + return Json(CommandResult("validateFusionConfigurationComposition")); + + case NitroOperationDocuments.CommitDeploymentOperationName: + CommitBody = body; + return Json(CommandResult("commitFusionConfigurationPublish")); + + case NitroOperationDocuments.ReleaseDeploymentOperationName: + return Json(CommandResult("cancelFusionConfigurationComposition")); + + default: + throw new InvalidOperationException( + $"The Nitro operation '{operationName}' is not scripted."); + } + } + + private HttpResponseMessage Download(HttpRequestMessage request) + { + DownloadCount++; + var segments = request.RequestUri!.AbsolutePath.Split('/'); + LastDownloadName = Uri.UnescapeDataString(segments[6]); + LastDownloadVersion = Uri.UnescapeDataString(segments[8]); + + if (DownloadStatusCode is { } statusCode) + { + return new HttpResponseMessage(statusCode) + { + Content = new StringContent(string.Empty) + }; + } + + var archive = DownloadCount > 1 && RemoteArchiveAfterUpload is not null + ? RemoteArchiveAfterUpload + : RemoteArchive; + + if (archive is null) + { + return new HttpResponseMessage(HttpStatusCode.NotFound) + { + Content = new StringContent(string.Empty) + }; + } + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(archive.ToArray()) + }; + } + + private string UploadResult() + { + if (UploadErrorTypeName is null) + { + return """ + {"data":{"uploadFusionSubgraph":{"fusionSubgraphVersion":{"id":"version-1"},"errors":[]}}} + """; + } + + return "{\"data\":{\"uploadFusionSubgraph\":{\"fusionSubgraphVersion\":null,\"errors\":[" + + Error(UploadErrorTypeName, UploadErrorMessage) + + "]}}}"; + } + + private string BeginResult() + => BeginErrorMessage is null + ? """ + {"data":{"beginFusionConfigurationPublish":{"requestId":"request-id","errors":[]}}} + """ + : "{\"data\":{\"beginFusionConfigurationPublish\":{\"requestId\":null,\"errors\":[" + + Error("StageNotFoundError", BeginErrorMessage) + + "]}}}"; + + private static string Error(string typeName, string message) + => "{\"__typename\":\"" + typeName + "\",\"message\":\"" + message + "\"}"; + + private static string CommandResult(string fieldName) + => "{\"data\":{\"" + fieldName + "\":{\"errors\":[]}}}"; + + private HttpResponseMessage Watch() + { + var payloads = Events.Select(WatchPayload).ToArray(); + + return WatchIsEventStream + ? new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent( + BuildEventStream(payloads), + Encoding.UTF8, + "text/event-stream") + } + : Json(payloads[0]); + } + + private static string WatchPayload(WatchEvent watchEvent) + { + var errors = string.Join( + ',', + watchEvent.Errors.Select(error => "{\"message\":\"" + error + "\"}")); + + return "{\"data\":{\"onFusionConfigurationPublishingTaskChanged\":{" + + "\"__typename\":\"" + watchEvent.TypeName + "\"," + + "\"state\":\"PROCESSING\"," + + "\"errors\":[" + errors + "]}}}"; + } + + private static string BuildEventStream(IEnumerable payloads) + { + var builder = new StringBuilder(); + + foreach (var payload in payloads) + { + builder.Append("event: next\n"); + + foreach (var line in payload.Split('\n')) + { + builder.Append("data: ").Append(line.TrimEnd('\r')).Append('\n'); + } + + builder.Append('\n'); + } + + builder.Append("event: complete\n\n"); + + return builder.ToString(); + } + + private static HttpResponseMessage Json(string json) + => new(HttpStatusCode.OK) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + + private static string ReadOperationName(string body) + { + string[] operationNames = + [ + NitroOperationDocuments.UploadSourceSchemaOperationName, + NitroOperationDocuments.BeginDeploymentOperationName, + NitroOperationDocuments.WatchDeploymentOperationName, + NitroOperationDocuments.ClaimDeploymentOperationName, + NitroOperationDocuments.ValidateDeploymentOperationName, + NitroOperationDocuments.CommitDeploymentOperationName, + NitroOperationDocuments.ReleaseDeploymentOperationName + ]; + + return Array.Find( + operationNames, + operationName => body.Contains(operationName, StringComparison.Ordinal)) + ?? "unknown"; + } + } +} diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroFusionApiTests.cs similarity index 93% rename from src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs rename to src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroFusionApiTests.cs index 1983162b686..92f1300d911 100644 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionApiTransportFactoryTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroFusionApiTests.cs @@ -1,9 +1,8 @@ using System.Buffers; -using ChilliCream.Nitro.Fusion.Transport; -namespace ChilliCream.Nitro.Fusion; +namespace HotChocolate.Fusion.Aspire.Nitro; -public sealed class FusionApiTransportFactoryTests +public sealed class NitroFusionApiTests { [Fact] public async Task ReadSourceSchemaArchiveAsync_Should_RejectBody_When_DeclaredLengthIsTooLarge() @@ -12,7 +11,7 @@ public async Task ReadSourceSchemaArchiveAsync_Should_RejectBody_When_DeclaredLe content.Headers.ContentLength = 17; var exception = await Assert.ThrowsAsync( - () => FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + () => NitroFusionApi.ReadSourceSchemaArchiveAsync( content, 16, TestContext.Current.CancellationToken)); @@ -32,7 +31,7 @@ public async Task ReadSourceSchemaArchiveAsync_Should_RejectBody_When_ChunkedBod Assert.Null(content.Headers.ContentLength); var exception = await Assert.ThrowsAsync( - () => FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + () => NitroFusionApi.ReadSourceSchemaArchiveAsync( content, 16, bufferPool, @@ -53,7 +52,7 @@ public async Task ReadSourceSchemaArchiveAsync_Should_Cancel_When_ReadIsCanceled using var content = new StreamContent(stream); using var cancellation = new CancellationTokenSource(); var bufferPool = new TrackingArrayPool(); - var readTask = FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + var readTask = NitroFusionApi.ReadSourceSchemaArchiveAsync( content, 16, bufferPool, @@ -77,7 +76,7 @@ public async Task ReadSourceSchemaArchiveAsync_Should_ClearEveryRentedBuffer_Whe using var content = new StreamContent(stream); var bufferPool = new TrackingArrayPool(); - var archive = await FusionApiTransportFactory.ReadSourceSchemaArchiveAsync( + var archive = await NitroFusionApi.ReadSourceSchemaArchiveAsync( content, 16, bufferPool, diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs index bc24cf1982e..b5b26fbeb84 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs @@ -1,7 +1,61 @@ +using System.Security.Cryptography; +using System.Text; +using HotChocolate.Language; + namespace HotChocolate.Fusion.Aspire.Nitro; public sealed class NitroOperationDocumentsTests { + /// + /// The persisted operation id of every operation. The ids are the sidecars that + /// .github/scripts/nitro-aspire-operations.sh generates and that Nitro registers, so + /// they are pinned here rather than recomputed. + /// + public static TheoryData OperationIds => + new() + { + { + NitroOperationDocuments.ResolveApiNameOperationName, + "6480d908b5fe1cad49198376e03c5547c83234d90c154c9566324717cda41f45" + }, + { + NitroOperationDocuments.ValidateSchemaOperationName, + "89e8800b8720401041061528aef1101683b241d7d24c7f0912928a1a901def02" + }, + { + NitroOperationDocuments.WatchSchemaValidationOperationName, + "ad7ee984bdd62c1357d5ee29261adff3f60bb119f5f162ae1ee8a1f9c62632d9" + }, + { + NitroOperationDocuments.UploadSourceSchemaOperationName, + "f091e081d5fea136c590aa34b6f37d391d69844c23f3a61f5e728ba97fed951f" + }, + { + NitroOperationDocuments.BeginDeploymentOperationName, + "d858bf7df2ee5df613f2e699446657dd3bd489b9bd1d246eff7bf021c31307c7" + }, + { + NitroOperationDocuments.ClaimDeploymentOperationName, + "5b818ded677899729e77ceeba1fc6ae179a08cf7982d158fcc2731f798614046" + }, + { + NitroOperationDocuments.ValidateDeploymentOperationName, + "de83bb4fc363ce531c18138aa35938387f46a81b1258f8882dd7c67b6c499a5e" + }, + { + NitroOperationDocuments.CommitDeploymentOperationName, + "f66f07ad033aff0963053bd465ff33463b48b2c4888a30b7db7dfbb8301f48ec" + }, + { + NitroOperationDocuments.ReleaseDeploymentOperationName, + "808c0252d2713db0f747a5c6d8d9e56806db032489daac04d9b075c1baef06a2" + }, + { + NitroOperationDocuments.WatchDeploymentOperationName, + "25fff4ef7a751b2d9b1008f353dc23c89b8aed8978334407d3aa9a83806839b1" + } + }; + #if !NITRO_PERSISTED_OPERATIONS [Fact] public void GetResolveApiNameDocument_Should_ReturnTheEmbeddedDocument() @@ -23,68 +77,110 @@ ... on Api { """); } - [Fact] - public void ResolveApiNameOperationName_Should_MatchTheOperationInTheDocument() - { - // act - var document = NitroOperationDocuments.GetResolveApiNameDocument(); - - // assert - Assert.Contains( - $"query {NitroOperationDocuments.ResolveApiNameOperationName}(", - document, - StringComparison.Ordinal); - } - [Theory] + [InlineData("ResolveNitroApiName", "query ResolveNitroApiName(")] [InlineData("ValidateNitroSchema", "mutation ValidateNitroSchema(")] - [InlineData("PollNitroSchemaValidation", "mutation PollNitroSchemaValidation(")] - public void ValidationOperationName_Should_MatchTheOperationInTheDocument( + [InlineData("WatchNitroSchemaValidation", "subscription WatchNitroSchemaValidation(")] + [InlineData("UploadFusionSourceSchema", "mutation UploadFusionSourceSchema(")] + [InlineData("BeginFusionDeployment", "mutation BeginFusionDeployment(")] + [InlineData("ClaimFusionDeployment", "mutation ClaimFusionDeployment(")] + [InlineData("ValidateFusionDeployment", "mutation ValidateFusionDeployment(")] + [InlineData("CommitFusionDeployment", "mutation CommitFusionDeployment(")] + [InlineData("ReleaseFusionDeployment", "mutation ReleaseFusionDeployment(")] + [InlineData("WatchFusionDeployment", "subscription WatchFusionDeployment(")] + public void OperationName_Should_MatchTheOperationInTheDocument( string operationName, string expectedDeclaration) { // act - var document = operationName == NitroOperationDocuments.ValidateSchemaOperationName - ? NitroOperationDocuments.GetValidateSchemaDocument() - : NitroOperationDocuments.GetPollSchemaValidationDocument(); + var document = GetDocument(operationName); // assert Assert.Contains(expectedDeclaration, document, StringComparison.Ordinal); } -#endif - -#if NITRO_PERSISTED_OPERATIONS - [Fact] - public void GetResolveApiNameOperationId_Should_ReturnTheEmbeddedHash() + [Theory] + [MemberData(nameof(OperationIds))] + public void GetDocument_Should_HashToTheOperationId( + string operationName, + string expectedOperationId) { // act - var operationId = NitroOperationDocuments.GetResolveApiNameOperationId(); + // the document that is sent on the wire has to be exactly the document that the sidecar + // was computed over, otherwise a release build sends an id that Nitro never persisted. + var document = Utf8GraphQLParser.Parse(GetDocument(operationName)).ToString(false); + var operationId = Convert.ToHexStringLower( + SHA256.HashData(Encoding.UTF8.GetBytes(document))); // assert - Assert.Equal( - "6480d908b5fe1cad49198376e03c5547c83234d90c154c9566324717cda41f45", - operationId); + Assert.Equal(expectedOperationId, operationId); } + private static string GetDocument(string operationName) + => operationName switch + { + NitroOperationDocuments.ResolveApiNameOperationName + => NitroOperationDocuments.GetResolveApiNameDocument(), + NitroOperationDocuments.ValidateSchemaOperationName + => NitroOperationDocuments.GetValidateSchemaDocument(), + NitroOperationDocuments.WatchSchemaValidationOperationName + => NitroOperationDocuments.GetWatchSchemaValidationDocument(), + NitroOperationDocuments.UploadSourceSchemaOperationName + => NitroOperationDocuments.GetUploadSourceSchemaDocument(), + NitroOperationDocuments.BeginDeploymentOperationName + => NitroOperationDocuments.GetBeginDeploymentDocument(), + NitroOperationDocuments.ClaimDeploymentOperationName + => NitroOperationDocuments.GetClaimDeploymentDocument(), + NitroOperationDocuments.ValidateDeploymentOperationName + => NitroOperationDocuments.GetValidateDeploymentDocument(), + NitroOperationDocuments.CommitDeploymentOperationName + => NitroOperationDocuments.GetCommitDeploymentDocument(), + NitroOperationDocuments.ReleaseDeploymentOperationName + => NitroOperationDocuments.GetReleaseDeploymentDocument(), + NitroOperationDocuments.WatchDeploymentOperationName + => NitroOperationDocuments.GetWatchDeploymentDocument(), + _ => throw new ArgumentOutOfRangeException(nameof(operationName)) + }; +#endif + +#if NITRO_PERSISTED_OPERATIONS [Theory] - [InlineData( - "ValidateNitroSchema", - "89e8800b8720401041061528aef1101683b241d7d24c7f0912928a1a901def02")] - [InlineData( - "PollNitroSchemaValidation", - "6ebe506f2dda96523ef2a0302b7b4aa0f748c2ace6dd5b89449c081c7dbb135e")] - public void GetValidationOperationId_Should_ReturnTheEmbeddedHash( + [MemberData(nameof(OperationIds))] + public void GetOperationId_Should_ReturnTheEmbeddedHash( string operationName, string expectedOperationId) { // act - var operationId = operationName == NitroOperationDocuments.ValidateSchemaOperationName - ? NitroOperationDocuments.GetValidateSchemaOperationId() - : NitroOperationDocuments.GetPollSchemaValidationOperationId(); + var operationId = GetOperationId(operationName); // assert Assert.Equal(expectedOperationId, operationId); } + + private static string GetOperationId(string operationName) + => operationName switch + { + NitroOperationDocuments.ResolveApiNameOperationName + => NitroOperationDocuments.GetResolveApiNameOperationId(), + NitroOperationDocuments.ValidateSchemaOperationName + => NitroOperationDocuments.GetValidateSchemaOperationId(), + NitroOperationDocuments.WatchSchemaValidationOperationName + => NitroOperationDocuments.GetWatchSchemaValidationOperationId(), + NitroOperationDocuments.UploadSourceSchemaOperationName + => NitroOperationDocuments.GetUploadSourceSchemaOperationId(), + NitroOperationDocuments.BeginDeploymentOperationName + => NitroOperationDocuments.GetBeginDeploymentOperationId(), + NitroOperationDocuments.ClaimDeploymentOperationName + => NitroOperationDocuments.GetClaimDeploymentOperationId(), + NitroOperationDocuments.ValidateDeploymentOperationName + => NitroOperationDocuments.GetValidateDeploymentOperationId(), + NitroOperationDocuments.CommitDeploymentOperationName + => NitroOperationDocuments.GetCommitDeploymentOperationId(), + NitroOperationDocuments.ReleaseDeploymentOperationName + => NitroOperationDocuments.GetReleaseDeploymentOperationId(), + NitroOperationDocuments.WatchDeploymentOperationName + => NitroOperationDocuments.GetWatchDeploymentOperationId(), + _ => throw new ArgumentOutOfRangeException(nameof(operationName)) + }; #endif } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaCompositionTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaCompositionTests.cs index 45dc2b0c289..f5d6f75a294 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaCompositionTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaCompositionTests.cs @@ -787,7 +787,6 @@ private NitroSeedCoordinator CreateCoordinator( validator ?? new NitroSchemaValidator( GraphQLHttpClient.Create(_httpClient, disposeHttpClient: false), - _timeProvider, new RecordingLogger()), _directory.GetPath("run")); diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaValidatorTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaValidatorTests.cs index 15e2f0d6e02..6c2f49c96a0 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaValidatorTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroSchemaValidatorTests.cs @@ -2,7 +2,6 @@ using System.Net.Http.Headers; using System.Text; using HotChocolate.Transport.Http; -using Microsoft.Extensions.Time.Testing; namespace HotChocolate.Fusion.Aspire.Nitro; @@ -18,9 +17,10 @@ public async Task ValidateAsync_Should_ReturnPassed_When_ValidationSucceedsAfter // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json(PollResult("""{"__typename":"OperationInProgress","state":"PROCESSING"}""")), - Json(PollResult("""{"__typename":"ValidationInProgress","state":"PROCESSING"}""")), - Json(PollResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); + Sse( + WatchResult("""{"__typename":"OperationInProgress","state":"PROCESSING"}"""), + WatchResult("""{"__typename":"ValidationInProgress","state":"PROCESSING"}"""), + WatchResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); // act var report = await session.ValidateAsync(); @@ -36,8 +36,8 @@ public async Task ValidateAsync_Should_ReturnPassed_When_ValidationSucceedsAfter """ Status: Passed Request ID: request-1 - Calls: 4 - Operations: ValidateNitroSchema, PollNitroSchemaValidation, PollNitroSchemaValidation, PollNitroSchemaValidation + Calls: 2 + Operations: ValidateNitroSchema, WatchNitroSchemaValidation Start wire: multipart=True, apiId=True, stage=True, schema=True, apiKey=nitro-api-key """); } @@ -51,7 +51,7 @@ public async Task ValidateAsync_Should_ReturnStructuredViolations_When_TerminalF // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json(PollFailure(error))); + Sse(WatchFailure(error))); // act var report = await session.ValidateAsync(); @@ -80,23 +80,83 @@ public async Task ValidateAsync_Should_ReturnUnavailable_When_StartReturnsAnErro } [Theory] - [InlineData("UnauthorizedOperation", "The credential is invalid.")] - [InlineData("SchemaVersionRequestNotFoundError", "The validation request does not exist.")] - public async Task ValidateAsync_Should_ReturnUnavailable_When_PollReturnsAnError( - string typeName, + [InlineData("The credential is invalid.")] + [InlineData("The validation request does not exist.")] + public async Task ValidateAsync_Should_ReturnUnavailable_When_SubscriptionReturnsAnError( string message) { // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json(PollFailurePayload(typeName, message))); + Sse(WatchError(message))); // act var report = await session.ValidateAsync(); // assert Assert.Equal( - $"Unavailable|request=request-1|reason={typeName}: {message}|calls=2", + $"Unavailable|request=request-1|reason=Nitro returned GraphQL errors: {message}" + + "|calls=2", + DescribeUnavailable(report, session.Handler.Requests.Count)); + } + + [Fact] + public async Task ValidateAsync_Should_ReturnUnavailable_When_SubscriptionEndsWithoutAResult() + { + // arrange + using var session = new ValidatorSession( + Json(StartSuccess), + Sse(WatchResult("""{"__typename":"ValidationInProgress","state":"PROCESSING"}"""))); + + // act + var report = await session.ValidateAsync(); + + // assert + Assert.Equal( + "Unavailable|request=request-1|reason=Nitro ended the subscription without a result." + + "|calls=2", + DescribeUnavailable(report, session.Handler.Requests.Count)); + } + + [Fact] + public async Task ValidateAsync_Should_ReturnUnavailable_When_SubscriptionIsNotAnEventStream() + { + // arrange + // A server that ignores the pinned Accept header answers with a single JSON result. + using var session = new ValidatorSession( + Json(StartSuccess), + Json(WatchResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); + + // act + var report = await session.ValidateAsync(); + + // assert + Assert.Equal( + "Unavailable|request=request-1|reason=Nitro answered the subscription with the " + + "content type 'application/json' instead of text/event-stream.|calls=2", + DescribeUnavailable(report, session.Handler.Requests.Count)); + } + + [Fact] + public async Task ValidateAsync_Should_ReturnUnavailable_When_SubscriptionExceedsTheEventLimit() + { + // arrange + var events = Enumerable + .Repeat( + WatchResult("""{"__typename":"ValidationInProgress","state":"PROCESSING"}"""), + 1001) + .ToArray(); + using var session = new ValidatorSession( + Json(StartSuccess), + Sse(events)); + + // act + var report = await session.ValidateAsync(); + + // assert + Assert.Equal( + "Unavailable|request=request-1|reason=Nitro sent too many subscription events." + + "|calls=2", DescribeUnavailable(report, session.Handler.Requests.Count)); } @@ -106,8 +166,8 @@ public async Task ValidateAsync_Should_UseClientIdAndQueryMessage_When_ClientIsN // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json( - PollFailure( + Sse( + WatchFailure( """ { "__typename": "PersistedQueryValidationError", @@ -199,7 +259,7 @@ public async Task ValidateAsync_Should_ReturnUnavailable_When_ResponseIsMalforme // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json(response)); + Sse(response)); // act var report = await session.ValidateAsync(); @@ -216,7 +276,7 @@ public async Task ValidateAsync_Should_ReturnUnavailable_When_ResponseContainsIn // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json("{")); + Sse("{")); // act var report = await session.ValidateAsync(); @@ -234,7 +294,7 @@ public async Task ValidateAsync_Should_ReturnUnavailable_When_ResultTypeIsUnknow // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json(PollResult("""{"__typename":"FutureValidationResult","state":"SUCCESS"}"""))); + Sse(WatchResult("""{"__typename":"FutureValidationResult","state":"SUCCESS"}"""))); // act var report = await session.ValidateAsync(); @@ -252,8 +312,8 @@ public async Task ValidateAsync_Should_ReturnUnavailable_When_FailureTypeIsUnkno // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json( - PollFailure( + Sse( + WatchFailure( """ { "__typename": "FutureValidationError", @@ -277,8 +337,8 @@ public async Task ValidateAsync_Should_KeepKnownFindings_When_FailureAlsoContain // arrange using var session = new ValidatorSession( Json(StartSuccess), - Json( - PollFailure( + Sse( + WatchFailure( """ { "__typename": "SchemaVersionSyntaxError", @@ -312,7 +372,6 @@ public async Task ValidateAsync_Should_EnforceResponseLimit_When_ContentLengthIs var oversizedContent = ChunkedContent(new byte[2 * 1024 * 1024 + 1]); var contentLength = oversizedContent.Headers.ContentLength; using var session = new ValidatorSession( - Json(StartSuccess), Returns( new HttpResponseMessage(HttpStatusCode.OK) { @@ -333,7 +392,7 @@ public async Task ValidateAsync_Should_EnforceResponseLimit_When_ContentLengthIs Status: Unavailable Reason: Nitro schema validation was unavailable: Nitro returned a schema validation response that exceeded the size limit. Content-Length: - Calls: 2 + Calls: 1 """); } @@ -355,14 +414,14 @@ public async Task ValidateAsync_Should_NotRetry_When_StartReturnsTransientStatus } [Fact] - public async Task ValidateAsync_Should_RetryTransientFailures_When_Polling() + public async Task ValidateAsync_Should_RetryTransientFailures_When_Subscribing() { // arrange using var session = new ValidatorSession( Json(StartSuccess), Status(HttpStatusCode.ServiceUnavailable), Status(HttpStatusCode.TooManyRequests), - Json(PollResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); + Sse(WatchResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); // act var report = await session.ValidateAsync(); @@ -370,8 +429,8 @@ public async Task ValidateAsync_Should_RetryTransientFailures_When_Polling() // assert Assert.Equal( "Passed|request=request-1|calls=4|operations=ValidateNitroSchema," - + "PollNitroSchemaValidation,PollNitroSchemaValidation," - + "PollNitroSchemaValidation", + + "WatchNitroSchemaValidation,WatchNitroSchemaValidation," + + "WatchNitroSchemaValidation", $"{report.Status}|request={report.RequestId}|calls={session.Handler.Requests.Count}" + $"|operations={string.Join(',', session.Handler.Requests.Select(r => r.OperationName))}"); } @@ -380,14 +439,14 @@ public async Task ValidateAsync_Should_RetryTransientFailures_When_Polling() [InlineData(HttpStatusCode.BadRequest)] [InlineData(HttpStatusCode.Unauthorized)] [InlineData(HttpStatusCode.Forbidden)] - public async Task ValidateAsync_Should_NotRetry_When_PollReturnsNonTransientStatus( + public async Task ValidateAsync_Should_NotRetry_When_SubscribeReturnsNonTransientStatus( HttpStatusCode statusCode) { // arrange using var session = new ValidatorSession( Json(StartSuccess), Status(statusCode), - Json(PollResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); + Sse(WatchResult("""{"__typename":"SchemaVersionValidationSuccess","state":"SUCCESS"}"""))); // act var report = await session.ValidateAsync(); @@ -610,27 +669,18 @@ public static TheoryData MalformedResponseData() { { """{"data":{}}""", - "Nitro returned a malformed validation polling response." + "Nitro returned no schema validation result." }, { """{"errors":[{"message":"GraphQL failed."}]}""", - "Nitro returned GraphQL errors." + "Nitro returned GraphQL errors: GraphQL failed." }, { - """ - { - "data": { - "pollSchemaVersionValidationRequest": { - "errors": [], - "result": null - } - } - } - """, + """{"data":{"onSchemaVersionValidationUpdate":null}}""", "Nitro returned no schema validation result." }, { - PollFailure(""), + WatchFailure(""), "Nitro returned a validation failure without findings." } }; @@ -699,47 +749,24 @@ private static string StartFailure(string typeName, string message) } """; - private static string PollFailurePayload(string typeName, string message) - => $$""" - { - "data": { - "pollSchemaVersionValidationRequest": { - "errors": [ - { - "__typename": "{{typeName}}", - "message": "{{message}}" - } - ], - "result": null - } - } - } - """; + private static string WatchError(string message) + => $$"""{"errors":[{"message":"{{message}}"}]}"""; - private static string PollFailure(params string[] errors) - => $$""" - { - "data": { - "pollSchemaVersionValidationRequest": { - "errors": [], - "result": { - "__typename": "SchemaVersionValidationFailed", - "state": "FAILED", - "errors": [{{string.Join(',', errors)}}] - } + private static string WatchFailure(params string[] errors) + => WatchResult( + $$""" + { + "__typename": "SchemaVersionValidationFailed", + "state": "FAILED", + "errors": [{{string.Join(',', errors)}}] } - } - } - """; + """); - private static string PollResult(string result) + private static string WatchResult(string result) => $$""" { "data": { - "pollSchemaVersionValidationRequest": { - "errors": [], - "result": {{result}} - } + "onSchemaVersionValidationUpdate": {{result}} } } """; @@ -751,6 +778,37 @@ private static ResponseStep Json(string json) Content = new StringContent(json, Encoding.UTF8, "application/json") }); + private static ResponseStep Sse(params string[] payloads) + => Returns( + new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent( + BuildEventStream(payloads), + Encoding.UTF8, + "text/event-stream") + }); + + private static string BuildEventStream(IEnumerable payloads) + { + var builder = new StringBuilder(); + + foreach (var payload in payloads) + { + builder.Append("event: next\n"); + + foreach (var line in payload.Split('\n')) + { + builder.Append("data: ").Append(line.TrimEnd('\r')).Append('\n'); + } + + builder.Append('\n'); + } + + builder.Append("event: complete\n\n"); + + return builder.ToString(); + } + private static ResponseStep Status(HttpStatusCode statusCode) => Returns( new HttpResponseMessage(statusCode) @@ -779,7 +837,6 @@ private static StreamContent ChunkedContent(byte[] content) private sealed class ValidatorSession : IDisposable { private readonly HttpClient _httpClient; - private readonly FakeTimeProvider _timeProvider = new(); private readonly NitroSchemaValidator _validator; public ValidatorSession(params ResponseStep[] responses) @@ -788,16 +845,14 @@ public ValidatorSession(params ResponseStep[] responses) _httpClient = new HttpClient(Handler); _validator = new NitroSchemaValidator( GraphQLHttpClient.Create(_httpClient, disposeHttpClient: false), - _timeProvider, new RecordingLogger()); } public ScriptedHttpMessageHandler Handler { get; } - public async Task ValidateAsync( + public Task ValidateAsync( NitroCredential? credential = null) - { - var validation = _validator.ValidateAsync( + => _validator.ValidateAsync( new NitroConnection( new Uri("https://api.example.test"), new Uri("https://api.example.test/graphql"), @@ -808,17 +863,6 @@ public async Task ValidateAsync( SchemaHash, TestContext.Current.CancellationToken); - for (var attempt = 0; attempt < 100 && !validation.IsCompleted; attempt++) - { - await Task.Yield(); - _timeProvider.Advance(TimeSpan.FromSeconds(5)); - } - - return await validation.WaitAsync( - TimeSpan.FromSeconds(10), - TestContext.Current.CancellationToken); - } - public void Dispose() => _httpClient.Dispose(); } @@ -841,9 +885,9 @@ protected override async Task SendAsync( StringComparison.Ordinal) ? NitroOperationDocuments.ValidateSchemaOperationName : body.Contains( - NitroOperationDocuments.PollSchemaValidationOperationName, + NitroOperationDocuments.WatchSchemaValidationOperationName, StringComparison.Ordinal) - ? NitroOperationDocuments.PollSchemaValidationOperationName + ? NitroOperationDocuments.WatchSchemaValidationOperationName : "unknown"; var apiKey = request.Headers.TryGetValues(NitroRequestHeaders.ApiKey, out var values) ? values.Single() diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj deleted file mode 100644 index c45e01f0fb1..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/ChilliCream.Nitro.Aspire.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - ChilliCream.Nitro.Aspire - ChilliCream.Nitro.Aspire - net11.0;net10.0;net9.0 - false - Integrates deterministic Hot Chocolate Fusion publication workflows with .NET Aspire deployment pipelines. - README.md - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs deleted file mode 100644 index fb0a8ba1829..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/IFusionPipelineExecutor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Aspire.Hosting.Pipelines; - -namespace ChilliCream.Nitro.Aspire; - -#pragma warning disable ASPIREPIPELINES001 - -internal interface IFusionPipelineExecutor -{ - Task CreateArtifactsAsync(PipelineStepContext context); - - Task VerifyReadinessAsync( - PipelineStepContext context, - FusionPipelineSession session); - - Task UploadAsync(PipelineStepContext context); - - Task PreflightAsync( - PipelineStepContext context, - FusionPipelineSession session); - - Task DownloadAsync( - PipelineStepContext context, - FusionPipelineSession session); - - Task ComposeAsync( - PipelineStepContext context, - FusionPipelineSession session); - - Task PublishAsync( - PipelineStepContext context, - FusionPipelineSession session); -} - -#pragma warning restore ASPIREPIPELINES001 diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs deleted file mode 100644 index b3b8a59722a..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResource.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Aspire.Hosting.ApplicationModel; - -namespace ChilliCream.Nitro.Aspire; - -/// -/// Represents a Nitro target in an Aspire application model. -/// -public sealed class NitroResource(string name) : Resource(name) -{ - internal string? CloudUrl { get; set; } - - internal string? ApiId { get; set; } - - internal ParameterResource? ApiKey { get; set; } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs deleted file mode 100644 index 050fa8bf163..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/NitroResourceBuilderExtensions.cs +++ /dev/null @@ -1,249 +0,0 @@ -using Aspire.Hosting; -using Aspire.Hosting.ApplicationModel; -using ChilliCream.Nitro.Fusion; - -namespace ChilliCream.Nitro.Aspire; - -/// -/// Provides extensions for declaring Nitro Fusion deployments. -/// -public static class NitroResourceBuilderExtensions -{ - /// - /// Adds a Nitro target. - /// - public static IResourceBuilder AddNitroTarget( - this IDistributedApplicationBuilder builder, - string name) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(name); - - EnsureFusionPipeline(builder); - builder.Services.AddNitroFusionDeploymentWorkflow(); - - var configuredCloudUrl = - builder.Configuration["Nitro:CloudUrl"] - ?? builder.Configuration["NITRO_CLOUD_URL"]; - var resource = new NitroResource(name) - { - CloudUrl = string.IsNullOrWhiteSpace(configuredCloudUrl) - ? null - : NormalizeCloudUrl(configuredCloudUrl), - ApiId = - builder.Configuration["Nitro:ApiId"] - ?? builder.Configuration["NITRO_API_ID"] - }; - - return builder.AddResource(resource); - } - - /// - /// Sets the Nitro GraphQL API URL. - /// - public static IResourceBuilder WithCloudUrl( - this IResourceBuilder builder, - string cloudUrl) - { - ArgumentNullException.ThrowIfNull(builder); - - builder.Resource.CloudUrl = NormalizeCloudUrl(cloudUrl); - return builder; - } - - /// - /// Sets the Nitro API identifier. - /// - public static IResourceBuilder WithApiId( - this IResourceBuilder builder, - string apiId) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(apiId); - - builder.Resource.ApiId = apiId; - return builder; - } - - /// - /// Sets the secret parameter used as the Nitro API key. - /// - public static IResourceBuilder WithApiKey( - this IResourceBuilder builder, - IResourceBuilder apiKey) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(apiKey); - - if (!apiKey.Resource.Secret) - { - throw new ArgumentException( - "The Nitro API key parameter must be declared as a secret.", - nameof(apiKey)); - } - - builder.Resource.ApiKey = apiKey.Resource; - return builder; - } - - /// - /// Adds an environment-specific Fusion deployment. - /// - public static IResourceBuilder AddFusionDeployment( - this IResourceBuilder builder, - string name) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(name); - - var resource = new FusionDeploymentResource(name, builder.Resource); - return builder.ApplicationBuilder.AddResource(resource); - } - - /// - /// Maps the deployment to an exact Aspire environment. - /// - public static IResourceBuilder ForEnvironment( - this IResourceBuilder builder, - string environmentName) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); - - builder.Resource.EnvironmentName = environmentName; - return builder; - } - - /// - /// Maps the deployment to an exact Nitro stage. - /// - public static IResourceBuilder ToStage( - this IResourceBuilder builder, - string stageName) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(stageName); - - builder.Resource.StageName = stageName; - return builder; - } - - /// - /// Selects the exact schema-settings.json environment used for composition. - /// - /// - /// When this is not configured, an environment selected by the GraphQL composition is used, - /// followed by the Nitro stage name. - /// - public static IResourceBuilder WithCompositionEnvironment( - this IResourceBuilder builder, - string environmentName) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); - - builder.Resource.CompositionEnvironmentName = environmentName; - return builder; - } - - /// - /// Sets the immutable release tag. - /// - public static IResourceBuilder WithConfigurationTag( - this IResourceBuilder builder, - IResourceBuilder configurationTag) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(configurationTag); - - builder.Resource.ConfigurationTagParameter = configurationTag.Resource; - builder.Resource.ConfigurationTag = null; - return builder; - } - - /// - /// Sets the immutable release tag. - /// - public static IResourceBuilder WithConfigurationTag( - this IResourceBuilder builder, - string configurationTag) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(configurationTag); - - builder.Resource.ConfigurationTag = configurationTag; - builder.Resource.ConfigurationTagParameter = null; - return builder; - } - - /// - /// Configures whether Nitro waits for approval. - /// - public static IResourceBuilder WithApproval( - this IResourceBuilder builder, - bool waitForApproval) - { - ArgumentNullException.ThrowIfNull(builder); - builder.Resource.WaitForApproval = waitForApproval; - return builder; - } - - /// - /// Configures whether validation failures may be forced. - /// - public static IResourceBuilder WithForce( - this IResourceBuilder builder, - bool force) - { - ArgumentNullException.ThrowIfNull(builder); - builder.Resource.Force = force; - return builder; - } - - /// - /// Configures operation and approval timeouts. - /// - public static IResourceBuilder WithTimeouts( - this IResourceBuilder builder, - TimeSpan operation, - TimeSpan approval) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(operation, TimeSpan.Zero); - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(approval, TimeSpan.Zero); - - builder.Resource.OperationTimeout = operation; - builder.Resource.ApprovalTimeout = approval; - return builder; - } - - private static void EnsureFusionPipeline(IDistributedApplicationBuilder builder) - { - if (builder.Resources.OfType().Any()) - { - return; - } - - var pipeline = builder.AddResource( - new FusionPipelineResource("fusion-nitro-pipeline")); - FusionPipeline.Configure(pipeline); - } - - private static string NormalizeCloudUrl(string cloudUrl) - { - if (!Uri.TryCreate(cloudUrl, UriKind.Absolute, out var uri) - || uri.Scheme is not "https" - || !string.IsNullOrEmpty(uri.UserInfo) - || uri.AbsolutePath is not "/" - || !string.IsNullOrEmpty(uri.Query) - || !string.IsNullOrEmpty(uri.Fragment)) - { - throw new ArgumentException( - "The Nitro cloud URL must be an absolute HTTPS origin without " - + "a path, query, fragment, or user information.", - nameof(cloudUrl)); - } - - return uri.GetLeftPart(UriPartial.Authority); - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md deleted file mode 100644 index 387669c70a7..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Aspire/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# ChilliCream.Nitro.Aspire - -Publishes Hot Chocolate Fusion configurations through the .NET Aspire deployment pipeline. - -See [Publishing Fusion with Aspire](FUSION_PUBLISHING.md) for the command contract, failure -behavior, ordering, and CI/CD example. - -```csharp -var tag = builder.AddParameter("tag"); -var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); - -var nitro = builder.AddNitroTarget("nitro") - .WithCloudUrl("https://api.chillicream.com") - .WithApiId("products-fusion") - .WithApiKey(nitroApiKey); - -nitro.AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithCompositionEnvironment("production") - .WithConfigurationTag(tag) - .WithApproval(waitForApproval: true) - .WithForce(false) - .WithTimeouts( - operation: TimeSpan.FromMinutes(15), - approval: TimeSpan.FromHours(2)); -``` - -Run `aspire do fusion-upload --environment Production` after building the source schemas. It -exports the AppHost-declared source set and reconciles every source as the immutable version -`name@tag` on the selected Nitro target. - -Run `aspire do fusion-publish --environment Production` on the deployment runner. It infers the -same complete source set from the AppHost, preflight-downloads each exact `name@tag`, deploys the -source services, downloads and digest-matches the exact versions again, composes with the selected -environment settings, checks readiness, publishes the Nitro stage, and then deploys the gateway. -The preflight retains only identities and digests while the providers run. It does not read source -schemas, use Git, or upload source versions. No manifest or CI artifact is passed between upload and -publish. Exact source archives -and the composed FAR stay in a bounded, invocation-scoped memory session that is cleared after -success, failure, or cancellation. The Fusion-specific publish steps create no Fusion apply-state -files and do not resolve Aspire's output-path service. Hosting-provider dependencies may still -write target artifacts or Aspire deployment state according to that provider's configuration. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json deleted file mode 100644 index 87f7b94b3a8..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/.graphqlrc.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "ChilliCream.Nitro.Fusion", - "schema": "../ChilliCream.Nitro.Client/schema.graphql", - "documents": "Operations/*.graphql", - "extensions": { - "strawberryShake": { - "name": "FusionApiClient", - "namespace": "ChilliCream.Nitro.Fusion.Transport", - "accessModifier": "internal", - "emitGeneratedCode": true, - "noStore": true, - "records": { - "inputs": true, - "entities": true - }, - "transportProfiles": [ - { - "name": "Default", - "default": "HTTP", - "subscription": "HTTP" - } - ] - } - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj deleted file mode 100644 index 1a6d03773e7..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/ChilliCream.Nitro.Fusion.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - ChilliCream.Nitro.Fusion - ChilliCream.Nitro.Fusion - net11.0;net10.0;net9.0 - false - Provides deterministic Nitro upload and publication workflows for Hot Chocolate Fusion. - README.md - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs deleted file mode 100644 index 2f3c8ebf331..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/FusionSourceSchemaVersion.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ChilliCream.Nitro.Fusion; - -/// -/// Selects one immutable source schema version for a Fusion publication. -/// -public sealed record FusionSourceSchemaVersion(string Name, string Version); diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs deleted file mode 100644 index 2817e38a53d..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Generated/FusionApiClient.Client.cs +++ /dev/null @@ -1,11092 +0,0 @@ -// -#nullable enable annotations -#nullable disable warnings - -namespace ChilliCream.Nitro.Fusion.Transport -{ - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResult : global::System.IEquatable, IClaimFusionDeploymentResult - { - public ClaimFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition startFusionConfigurationComposition) - { - StartFusionConfigurationComposition = startFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (StartFusionConfigurationComposition.Equals(other.StartFusionConfigurationComposition)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ClaimFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * StartFusionConfigurationComposition.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload - { - public ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) - { - Errors = errors; - } - - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation - { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError - { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError - { - public ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition StartFusionConfigurationComposition { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload : IClaimFusionDeployment_StartFusionConfigurationComposition - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IFusionError - { - public global::System.String Message { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IClaimFusionDeployment_StartFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResult : global::System.IEquatable, IWatchFusionDeploymentResult - { - public WatchFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged onFusionConfigurationPublishingTaskChanged) - { - OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (OnFusionConfigurationPublishingTaskChanged.Equals(other.OnFusionConfigurationPublishingTaskChanged)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * OnFusionConfigurationPublishingTaskChanged.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) - { - this.__typename = __typename; - State = state; - Errors = errors; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Collections.Generic.IReadOnlyList errors) - { - this.__typename = __typename; - State = state; - Errors = errors; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state, global::System.Int32 queuePosition) - { - this.__typename = __typename; - State = state; - QueuePosition = queuePosition; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - public global::System.Int32 QueuePosition { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State) && global::System.Object.Equals(QueuePosition, other.QueuePosition); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - hash ^= 397 * QueuePosition.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState state) - { - this.__typename = __typename; - State = state; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && State.Equals(other.State); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * State.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : global::System.IEquatable, IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 - { - public WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(global::System.String message) - { - Message = message; - } - - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Message.Equals(other.Message)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged OnFusionConfigurationPublishingTaskChanged { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.String __typename { get; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState State { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Collections.Generic.IReadOnlyList Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - public global::System.Int32 QueuePosition { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - public global::System.String Message { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - public global::System.String Message { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1 : IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaResult : global::System.IEquatable, IUploadFusionSourceSchemaResult - { - public UploadFusionSourceSchemaResult(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph uploadFusionSubgraph) - { - UploadFusionSubgraph = uploadFusionSubgraph; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchemaResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (UploadFusionSubgraph.Equals(other.UploadFusionSubgraph)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchemaResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * UploadFusionSubgraph.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload - { - public UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? fusionSubgraphVersion, global::System.Collections.Generic.IReadOnlyList? errors) - { - FusionSubgraphVersion = fusionSubgraphVersion; - Errors = errors; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (((FusionSubgraphVersion is null && other.FusionSubgraphVersion is null) || FusionSubgraphVersion != null && FusionSubgraphVersion.Equals(other.FusionSubgraphVersion))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (FusionSubgraphVersion != null) - { - hash ^= 397 * FusionSubgraphVersion.GetHashCode(); - } - - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion - { - public UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(global::System.String id) - { - Id = id; - } - - public global::System.String Id { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Id.Equals(other.Id)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Id.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError - { - public UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchemaResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph UploadFusionSubgraph { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph - { - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? FusionSubgraphVersion { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload : IUploadFusionSourceSchema_UploadFusionSubgraph - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion - { - public global::System.String Id { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion : IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError : IUploadFusionSourceSchema_UploadFusionSubgraph_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentResult : global::System.IEquatable, IBeginFusionDeploymentResult - { - public BeginFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish beginFusionConfigurationPublish) - { - BeginFusionConfigurationPublish = beginFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (BeginFusionConfigurationPublish.Equals(other.BeginFusionConfigurationPublish)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * BeginFusionConfigurationPublish.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(global::System.String? requestId, global::System.Collections.Generic.IReadOnlyList? errors) - { - RequestId = requestId; - Errors = errors; - } - - public global::System.String? RequestId { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (((RequestId is null && other.RequestId is null) || RequestId != null && RequestId.Equals(other.RequestId))) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (RequestId != null) - { - hash ^= 397 * RequestId.GetHashCode(); - } - - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : global::System.IEquatable, IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError - { - public BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish BeginFusionConfigurationPublish { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish - { - public global::System.String? RequestId { get; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload : IBeginFusionDeployment_BeginFusionConfigurationPublish - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError : IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentResult : global::System.IEquatable, IReleaseFusionDeploymentResult - { - public ReleaseFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition cancelFusionConfigurationComposition) - { - CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } - - public virtual global::System.Boolean Equals(ReleaseFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (CancelFusionConfigurationComposition.Equals(other.CancelFusionConfigurationComposition)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ReleaseFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * CancelFusionConfigurationComposition.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload - { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) - { - Errors = errors; - } - - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation - { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError - { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError - { - public ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition CancelFusionConfigurationComposition { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload : IReleaseFusionDeployment_CancelFusionConfigurationComposition - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentResult : global::System.IEquatable, IValidateFusionDeploymentResult - { - public ValidateFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition validateFusionConfigurationComposition) - { - ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } - - public virtual global::System.Boolean Equals(ValidateFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (ValidateFusionConfigurationComposition.Equals(other.ValidateFusionConfigurationComposition)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ValidateFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * ValidateFusionConfigurationComposition.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload - { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(global::System.Collections.Generic.IReadOnlyList? errors) - { - Errors = errors; - } - - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation - { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError - { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError - { - public ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition ValidateFusionConfigurationComposition { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload : IValidateFusionDeployment_ValidateFusionConfigurationComposition - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError : IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentResult : global::System.IEquatable, ICommitFusionDeploymentResult - { - public CommitFusionDeploymentResult(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish commitFusionConfigurationPublish) - { - CommitFusionConfigurationPublish = commitFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeploymentResult? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (CommitFusionConfigurationPublish.Equals(other.CommitFusionConfigurationPublish)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeploymentResult)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * CommitFusionConfigurationPublish.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload - { - public CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(global::System.Collections.Generic.IReadOnlyList? errors) - { - Errors = errors; - } - - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Errors, other.Errors)); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (Errors != null) - { - foreach (var Errors_elm in Errors) - { - hash ^= 397 * Errors_elm.GetHashCode(); - } - } - - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation - { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError - { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : global::System.IEquatable, ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError - { - public CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(global::System.String __typename, global::System.String message) - { - this.__typename = __typename; - Message = message; - } - - /// - /// The name of the current Object type at runtime. - /// - public global::System.String __typename { get; } - public global::System.String Message { get; } - - public virtual global::System.Boolean Equals(CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (__typename.Equals(other.__typename)) && Message.Equals(other.Message); - } - - public override global::System.Boolean Equals(global::System.Object? obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError)obj); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * __typename.GetHashCode(); - hash ^= 397 * Message.GetHashCode(); - return hash; - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeploymentResult - { - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish CommitFusionConfigurationPublish { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish - { - public global::System.Collections.Generic.IReadOnlyList? Errors { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload : ICommitFusionDeployment_CommitFusionConfigurationPublish - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors - { - public global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError : ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors, IFusionError - { - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class StartFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "StartFusionConfigurationCompositionInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsRequestIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); - } - - return fields; - } - - private global::System.Object? FormatRequestId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StartFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo - { - public virtual global::System.Boolean Equals(StartFusionConfigurationCompositionInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (RequestId.Equals(other.RequestId)); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * RequestId.GetHashCode(); - return hash; - } - } - - private global::System.String _value_requestId = default !; - private global::System.Boolean _set_requestId; - public global::System.String RequestId - { - get => _value_requestId; - init - { - _set_requestId = true; - _value_requestId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSubgraphInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _sourceMetadataInputFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - public global::System.String TypeName => "UploadFusionSubgraphInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); - _sourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("SourceMetadataInput"); - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsApiIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("apiId", FormatApiId(input.ApiId))); - } - - if (inputInfo.IsArchiveSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("archive", FormatArchive(input.Archive))); - } - - if (inputInfo.IsSourceSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("source", FormatSource(input.Source))); - } - - if (inputInfo.IsTagSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); - } - - return fields; - } - - private global::System.Object? FormatApiId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - - private global::System.Object? FormatArchive(global::StrawberryShake.Upload input) - { - return _uploadFormatter.Format(input); - } - - private global::System.Object? FormatSource(global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? input) - { - if (input is null) - { - return input; - } - else - { - return _sourceMetadataInputFormatter.Format(input); - } - } - - private global::System.Object? FormatTag(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UploadFusionSubgraphInput : global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo - { - public virtual global::System.Boolean Equals(UploadFusionSubgraphInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (ApiId.Equals(other.ApiId)) && Archive.Equals(other.Archive) && ((Source is null && other.Source is null) || Source != null && Source.Equals(other.Source)) && Tag.Equals(other.Tag); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * ApiId.GetHashCode(); - hash ^= 397 * Archive.GetHashCode(); - if (Source != null) - { - hash ^= 397 * Source.GetHashCode(); - } - - hash ^= 397 * Tag.GetHashCode(); - return hash; - } - } - - private global::System.String _value_apiId = default !; - private global::System.Boolean _set_apiId; - private global::StrawberryShake.Upload _value_archive; - private global::System.Boolean _set_archive; - private global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? _value_source; - private global::System.Boolean _set_source; - private global::System.String _value_tag = default !; - private global::System.Boolean _set_tag; - public global::System.String ApiId - { - get => _value_apiId; - init - { - _set_apiId = true; - _value_apiId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsApiIdSet => _set_apiId; - - public global::StrawberryShake.Upload Archive - { - get => _value_archive; - init - { - _set_archive = true; - _value_archive = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsArchiveSet => _set_archive; - - public global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? Source - { - get => _value_source; - init - { - _set_source = true; - _value_source = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsSourceSet => _set_source; - - public global::System.String Tag - { - get => _value_tag; - init - { - _set_tag = true; - _value_tag = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphInputInfo.IsTagSet => _set_tag; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class SourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _azureDevOpsSourceMetadataInputFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _gitHubSourceMetadataInputFormatter = default !; - public global::System.String TypeName => "SourceMetadataInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _azureDevOpsSourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("AzureDevOpsSourceMetadataInput"); - _gitHubSourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("GitHubSourceMetadataInput"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsAzureDevOpsSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("azureDevOps", FormatAzureDevOps(input.AzureDevOps))); - } - - if (inputInfo.IsGithubSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("github", FormatGithub(input.Github))); - } - - return fields; - } - - private global::System.Object? FormatAzureDevOps(global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? input) - { - if (input is null) - { - return input; - } - else - { - return _azureDevOpsSourceMetadataInputFormatter.Format(input); - } - } - - private global::System.Object? FormatGithub(global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? input) - { - if (input is null) - { - return input; - } - else - { - return _gitHubSourceMetadataInputFormatter.Format(input); - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record SourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo - { - public virtual global::System.Boolean Equals(SourceMetadataInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (((AzureDevOps is null && other.AzureDevOps is null) || AzureDevOps != null && AzureDevOps.Equals(other.AzureDevOps))) && ((Github is null && other.Github is null) || Github != null && Github.Equals(other.Github)); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (AzureDevOps != null) - { - hash ^= 397 * AzureDevOps.GetHashCode(); - } - - if (Github != null) - { - hash ^= 397 * Github.GetHashCode(); - } - - return hash; - } - } - - private global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? _value_azureDevOps; - private global::System.Boolean _set_azureDevOps; - private global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? _value_github; - private global::System.Boolean _set_github; - public global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput? AzureDevOps - { - get => _value_azureDevOps; - init - { - _set_azureDevOps = true; - _value_azureDevOps = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo.IsAzureDevOpsSet => _set_azureDevOps; - - public global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput? Github - { - get => _value_github; - init - { - _set_github = true; - _value_github = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ISourceMetadataInputInfo.IsGithubSet => _set_github; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class AzureDevOpsSourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _azureDevOpsActorInputFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _uRLFormatter = default !; - public global::System.String TypeName => "AzureDevOpsSourceMetadataInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _azureDevOpsActorInputFormatter = serializerResolver.GetInputValueFormatter("AzureDevOpsActorInput"); - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - _uRLFormatter = serializerResolver.GetInputValueFormatter("URL"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsSourceMetadataInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsActorSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("actor", FormatActor(input.Actor))); - } - - if (inputInfo.IsCommitHashSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("commitHash", FormatCommitHash(input.CommitHash))); - } - - if (inputInfo.IsJobIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("jobId", FormatJobId(input.JobId))); - } - - if (inputInfo.IsPipelineNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("pipelineName", FormatPipelineName(input.PipelineName))); - } - - if (inputInfo.IsProjectUrlSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("projectUrl", FormatProjectUrl(input.ProjectUrl))); - } - - if (inputInfo.IsRepositoryUrlSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("repositoryUrl", FormatRepositoryUrl(input.RepositoryUrl))); - } - - if (inputInfo.IsRunIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("runId", FormatRunId(input.RunId))); - } - - if (inputInfo.IsRunNumberSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("runNumber", FormatRunNumber(input.RunNumber))); - } - - if (inputInfo.IsTaskIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("taskId", FormatTaskId(input.TaskId))); - } - - return fields; - } - - private global::System.Object? FormatActor(global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _azureDevOpsActorInputFormatter.Format(input); - } - - private global::System.Object? FormatCommitHash(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - - private global::System.Object? FormatJobId(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - - private global::System.Object? FormatPipelineName(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatProjectUrl(global::System.Uri input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _uRLFormatter.Format(input); - } - - private global::System.Object? FormatRepositoryUrl(global::System.Uri? input) - { - if (input is null) - { - return input; - } - else - { - return _uRLFormatter.Format(input); - } - } - - private global::System.Object? FormatRunId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatRunNumber(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatTaskId(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record AzureDevOpsSourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo - { - public virtual global::System.Boolean Equals(AzureDevOpsSourceMetadataInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Actor.Equals(other.Actor)) && ((CommitHash is null && other.CommitHash is null) || CommitHash != null && CommitHash.Equals(other.CommitHash)) && ((JobId is null && other.JobId is null) || JobId != null && JobId.Equals(other.JobId)) && PipelineName.Equals(other.PipelineName) && ProjectUrl.Equals(other.ProjectUrl) && ((RepositoryUrl is null && other.RepositoryUrl is null) || RepositoryUrl != null && RepositoryUrl.Equals(other.RepositoryUrl)) && RunId.Equals(other.RunId) && RunNumber.Equals(other.RunNumber) && ((TaskId is null && other.TaskId is null) || TaskId != null && TaskId.Equals(other.TaskId)); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Actor.GetHashCode(); - if (CommitHash != null) - { - hash ^= 397 * CommitHash.GetHashCode(); - } - - if (JobId != null) - { - hash ^= 397 * JobId.GetHashCode(); - } - - hash ^= 397 * PipelineName.GetHashCode(); - hash ^= 397 * ProjectUrl.GetHashCode(); - if (RepositoryUrl != null) - { - hash ^= 397 * RepositoryUrl.GetHashCode(); - } - - hash ^= 397 * RunId.GetHashCode(); - hash ^= 397 * RunNumber.GetHashCode(); - if (TaskId != null) - { - hash ^= 397 * TaskId.GetHashCode(); - } - - return hash; - } - } - - private global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput _value_actor = default !; - private global::System.Boolean _set_actor; - private global::System.String? _value_commitHash; - private global::System.Boolean _set_commitHash; - private global::System.String? _value_jobId; - private global::System.Boolean _set_jobId; - private global::System.String _value_pipelineName = default !; - private global::System.Boolean _set_pipelineName; - private global::System.Uri _value_projectUrl = default !; - private global::System.Boolean _set_projectUrl; - private global::System.Uri? _value_repositoryUrl; - private global::System.Boolean _set_repositoryUrl; - private global::System.String _value_runId = default !; - private global::System.Boolean _set_runId; - private global::System.String _value_runNumber = default !; - private global::System.Boolean _set_runNumber; - private global::System.String? _value_taskId; - private global::System.Boolean _set_taskId; - public global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput Actor - { - get => _value_actor; - init - { - _set_actor = true; - _value_actor = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsActorSet => _set_actor; - - public global::System.String? CommitHash - { - get => _value_commitHash; - init - { - _set_commitHash = true; - _value_commitHash = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsCommitHashSet => _set_commitHash; - - public global::System.String? JobId - { - get => _value_jobId; - init - { - _set_jobId = true; - _value_jobId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsJobIdSet => _set_jobId; - - public global::System.String PipelineName - { - get => _value_pipelineName; - init - { - _set_pipelineName = true; - _value_pipelineName = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsPipelineNameSet => _set_pipelineName; - - public global::System.Uri ProjectUrl - { - get => _value_projectUrl; - init - { - _set_projectUrl = true; - _value_projectUrl = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsProjectUrlSet => _set_projectUrl; - - public global::System.Uri? RepositoryUrl - { - get => _value_repositoryUrl; - init - { - _set_repositoryUrl = true; - _value_repositoryUrl = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRepositoryUrlSet => _set_repositoryUrl; - - public global::System.String RunId - { - get => _value_runId; - init - { - _set_runId = true; - _value_runId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRunIdSet => _set_runId; - - public global::System.String RunNumber - { - get => _value_runNumber; - init - { - _set_runNumber = true; - _value_runNumber = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsRunNumberSet => _set_runNumber; - - public global::System.String? TaskId - { - get => _value_taskId; - init - { - _set_taskId = true; - _value_taskId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsSourceMetadataInputInfo.IsTaskIdSet => _set_taskId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class AzureDevOpsActorInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - public global::System.String TypeName => "AzureDevOpsActorInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.AzureDevOpsActorInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsEmailSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("email", FormatEmail(input.Email))); - } - - if (inputInfo.IsNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("name", FormatName(input.Name))); - } - - return fields; - } - - private global::System.Object? FormatEmail(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - - private global::System.Object? FormatName(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record AzureDevOpsActorInput : global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo - { - public virtual global::System.Boolean Equals(AzureDevOpsActorInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (((Email is null && other.Email is null) || Email != null && Email.Equals(other.Email))) && Name.Equals(other.Name); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - if (Email != null) - { - hash ^= 397 * Email.GetHashCode(); - } - - hash ^= 397 * Name.GetHashCode(); - return hash; - } - } - - private global::System.String? _value_email; - private global::System.Boolean _set_email; - private global::System.String _value_name = default !; - private global::System.Boolean _set_name; - public global::System.String? Email - { - get => _value_email; - init - { - _set_email = true; - _value_email = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo.IsEmailSet => _set_email; - - public global::System.String Name - { - get => _value_name; - init - { - _set_name = true; - _value_name = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IAzureDevOpsActorInputInfo.IsNameSet => _set_name; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class GitHubSourceMetadataInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _uRLFormatter = default !; - public global::System.String TypeName => "GitHubSourceMetadataInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - _uRLFormatter = serializerResolver.GetInputValueFormatter("URL"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.GitHubSourceMetadataInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsActorSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("actor", FormatActor(input.Actor))); - } - - if (inputInfo.IsCommitHashSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("commitHash", FormatCommitHash(input.CommitHash))); - } - - if (inputInfo.IsJobIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("jobId", FormatJobId(input.JobId))); - } - - if (inputInfo.IsRepositoryUrlSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("repositoryUrl", FormatRepositoryUrl(input.RepositoryUrl))); - } - - if (inputInfo.IsRunIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("runId", FormatRunId(input.RunId))); - } - - if (inputInfo.IsRunNumberSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("runNumber", FormatRunNumber(input.RunNumber))); - } - - if (inputInfo.IsWorkflowNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("workflowName", FormatWorkflowName(input.WorkflowName))); - } - - return fields; - } - - private global::System.Object? FormatActor(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatCommitHash(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatJobId(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - - private global::System.Object? FormatRepositoryUrl(global::System.Uri input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _uRLFormatter.Format(input); - } - - private global::System.Object? FormatRunId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatRunNumber(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatWorkflowName(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record GitHubSourceMetadataInput : global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo - { - public virtual global::System.Boolean Equals(GitHubSourceMetadataInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Actor.Equals(other.Actor)) && CommitHash.Equals(other.CommitHash) && ((JobId is null && other.JobId is null) || JobId != null && JobId.Equals(other.JobId)) && RepositoryUrl.Equals(other.RepositoryUrl) && RunId.Equals(other.RunId) && RunNumber.Equals(other.RunNumber) && WorkflowName.Equals(other.WorkflowName); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Actor.GetHashCode(); - hash ^= 397 * CommitHash.GetHashCode(); - if (JobId != null) - { - hash ^= 397 * JobId.GetHashCode(); - } - - hash ^= 397 * RepositoryUrl.GetHashCode(); - hash ^= 397 * RunId.GetHashCode(); - hash ^= 397 * RunNumber.GetHashCode(); - hash ^= 397 * WorkflowName.GetHashCode(); - return hash; - } - } - - private global::System.String _value_actor = default !; - private global::System.Boolean _set_actor; - private global::System.String _value_commitHash = default !; - private global::System.Boolean _set_commitHash; - private global::System.String? _value_jobId; - private global::System.Boolean _set_jobId; - private global::System.Uri _value_repositoryUrl = default !; - private global::System.Boolean _set_repositoryUrl; - private global::System.String _value_runId = default !; - private global::System.Boolean _set_runId; - private global::System.String _value_runNumber = default !; - private global::System.Boolean _set_runNumber; - private global::System.String _value_workflowName = default !; - private global::System.Boolean _set_workflowName; - public global::System.String Actor - { - get => _value_actor; - init - { - _set_actor = true; - _value_actor = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsActorSet => _set_actor; - - public global::System.String CommitHash - { - get => _value_commitHash; - init - { - _set_commitHash = true; - _value_commitHash = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsCommitHashSet => _set_commitHash; - - public global::System.String? JobId - { - get => _value_jobId; - init - { - _set_jobId = true; - _value_jobId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsJobIdSet => _set_jobId; - - public global::System.Uri RepositoryUrl - { - get => _value_repositoryUrl; - init - { - _set_repositoryUrl = true; - _value_repositoryUrl = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRepositoryUrlSet => _set_repositoryUrl; - - public global::System.String RunId - { - get => _value_runId; - init - { - _set_runId = true; - _value_runId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRunIdSet => _set_runId; - - public global::System.String RunNumber - { - get => _value_runNumber; - init - { - _set_runNumber = true; - _value_runNumber = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsRunNumberSet => _set_runNumber; - - public global::System.String WorkflowName - { - get => _value_workflowName; - init - { - _set_workflowName = true; - _value_workflowName = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IGitHubSourceMetadataInputInfo.IsWorkflowNameSet => _set_workflowName; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionConfigurationPublishInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _sourceMetadataInputFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _fusionSubgraphVersionInputFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _booleanFormatter = default !; - public global::System.String TypeName => "BeginFusionConfigurationPublishInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - _sourceMetadataInputFormatter = serializerResolver.GetInputValueFormatter("SourceMetadataInput"); - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - _fusionSubgraphVersionInputFormatter = serializerResolver.GetInputValueFormatter("FusionSubgraphVersionInput"); - _booleanFormatter = serializerResolver.GetInputValueFormatter("Boolean"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsApiIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("apiId", FormatApiId(input.ApiId))); - } - - if (inputInfo.IsSourceSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("source", FormatSource(input.Source))); - } - - if (inputInfo.IsStageNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("stageName", FormatStageName(input.StageName))); - } - - if (inputInfo.IsSubgraphApiIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphApiId", FormatSubgraphApiId(input.SubgraphApiId))); - } - - if (inputInfo.IsSubgraphNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphName", FormatSubgraphName(input.SubgraphName))); - } - - if (inputInfo.IsSubgraphsSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("subgraphs", FormatSubgraphs(input.Subgraphs))); - } - - if (inputInfo.IsTagSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); - } - - if (inputInfo.IsWaitForApprovalSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("waitForApproval", FormatWaitForApproval(input.WaitForApproval))); - } - - return fields; - } - - private global::System.Object? FormatApiId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - - private global::System.Object? FormatSource(global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? input) - { - if (input is null) - { - return input; - } - else - { - return _sourceMetadataInputFormatter.Format(input); - } - } - - private global::System.Object? FormatStageName(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatSubgraphApiId(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _iDFormatter.Format(input); - } - } - - private global::System.Object? FormatSubgraphName(global::System.String? input) - { - if (input is null) - { - return input; - } - else - { - return _stringFormatter.Format(input); - } - } - - private global::System.Object? FormatSubgraphs(global::System.Collections.Generic.IReadOnlyList? input) - { - if (input is null) - { - return input; - } - else - { - var input_list = new global::System.Collections.Generic.List(); - foreach (var input_elm in input) - { - if (input_elm is null) - { - throw new global::System.ArgumentNullException(nameof(input_elm)); - } - - input_list.Add(_fusionSubgraphVersionInputFormatter.Format(input_elm)); - } - - return input_list; - } - } - - private global::System.Object? FormatTag(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatWaitForApproval(global::System.Boolean? input) - { - if (input is null) - { - return input; - } - else - { - return _booleanFormatter.Format(input); - } - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record BeginFusionConfigurationPublishInput : global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo - { - public virtual global::System.Boolean Equals(BeginFusionConfigurationPublishInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (ApiId.Equals(other.ApiId)) && ((Source is null && other.Source is null) || Source != null && Source.Equals(other.Source)) && StageName.Equals(other.StageName) && ((SubgraphApiId is null && other.SubgraphApiId is null) || SubgraphApiId != null && SubgraphApiId.Equals(other.SubgraphApiId)) && ((SubgraphName is null && other.SubgraphName is null) || SubgraphName != null && SubgraphName.Equals(other.SubgraphName)) && global::StrawberryShake.Internal.ComparisonHelper.SequenceEqual(Subgraphs, other.Subgraphs) && Tag.Equals(other.Tag) && global::System.Object.Equals(WaitForApproval, other.WaitForApproval); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * ApiId.GetHashCode(); - if (Source != null) - { - hash ^= 397 * Source.GetHashCode(); - } - - hash ^= 397 * StageName.GetHashCode(); - if (SubgraphApiId != null) - { - hash ^= 397 * SubgraphApiId.GetHashCode(); - } - - if (SubgraphName != null) - { - hash ^= 397 * SubgraphName.GetHashCode(); - } - - if (Subgraphs != null) - { - foreach (var Subgraphs_elm in Subgraphs) - { - hash ^= 397 * Subgraphs_elm.GetHashCode(); - } - } - - hash ^= 397 * Tag.GetHashCode(); - if (WaitForApproval != null) - { - hash ^= 397 * WaitForApproval.GetHashCode(); - } - - return hash; - } - } - - private global::System.String _value_apiId = default !; - private global::System.Boolean _set_apiId; - private global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? _value_source; - private global::System.Boolean _set_source; - private global::System.String _value_stageName = default !; - private global::System.Boolean _set_stageName; - private global::System.String? _value_subgraphApiId; - private global::System.Boolean _set_subgraphApiId; - private global::System.String? _value_subgraphName; - private global::System.Boolean _set_subgraphName; - private global::System.Collections.Generic.IReadOnlyList? _value_subgraphs; - private global::System.Boolean _set_subgraphs; - private global::System.String _value_tag = default !; - private global::System.Boolean _set_tag; - private global::System.Boolean? _value_waitForApproval; - private global::System.Boolean _set_waitForApproval; - public global::System.String ApiId - { - get => _value_apiId; - init - { - _set_apiId = true; - _value_apiId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsApiIdSet => _set_apiId; - - public global::ChilliCream.Nitro.Fusion.Transport.SourceMetadataInput? Source - { - get => _value_source; - init - { - _set_source = true; - _value_source = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSourceSet => _set_source; - - public global::System.String StageName - { - get => _value_stageName; - init - { - _set_stageName = true; - _value_stageName = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsStageNameSet => _set_stageName; - - public global::System.String? SubgraphApiId - { - get => _value_subgraphApiId; - init - { - _set_subgraphApiId = true; - _value_subgraphApiId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphApiIdSet => _set_subgraphApiId; - - public global::System.String? SubgraphName - { - get => _value_subgraphName; - init - { - _set_subgraphName = true; - _value_subgraphName = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphNameSet => _set_subgraphName; - - public global::System.Collections.Generic.IReadOnlyList? Subgraphs - { - get => _value_subgraphs; - init - { - _set_subgraphs = true; - _value_subgraphs = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsSubgraphsSet => _set_subgraphs; - - public global::System.String Tag - { - get => _value_tag; - init - { - _set_tag = true; - _value_tag = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsTagSet => _set_tag; - - public global::System.Boolean? WaitForApproval - { - get => _value_waitForApproval; - init - { - _set_waitForApproval = true; - _value_waitForApproval = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishInputInfo.IsWaitForApprovalSet => _set_waitForApproval; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class FusionSubgraphVersionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _stringFormatter = default !; - public global::System.String TypeName => "FusionSubgraphVersionInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _stringFormatter = serializerResolver.GetInputValueFormatter("String"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.FusionSubgraphVersionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsNameSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("name", FormatName(input.Name))); - } - - if (inputInfo.IsTagSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("tag", FormatTag(input.Tag))); - } - - return fields; - } - - private global::System.Object? FormatName(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - - private global::System.Object? FormatTag(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _stringFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionSubgraphVersionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo - { - public virtual global::System.Boolean Equals(FusionSubgraphVersionInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Name.Equals(other.Name)) && Tag.Equals(other.Tag); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Name.GetHashCode(); - hash ^= 397 * Tag.GetHashCode(); - return hash; - } - } - - private global::System.String _value_name = default !; - private global::System.Boolean _set_name; - private global::System.String _value_tag = default !; - private global::System.Boolean _set_tag; - public global::System.String Name - { - get => _value_name; - init - { - _set_name = true; - _value_name = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo.IsNameSet => _set_name; - - public global::System.String Tag - { - get => _value_tag; - init - { - _set_tag = true; - _value_tag = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IFusionSubgraphVersionInputInfo.IsTagSet => _set_tag; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CancelFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "CancelFusionConfigurationCompositionInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsRequestIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); - } - - return fields; - } - - private global::System.Object? FormatRequestId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CancelFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo - { - public virtual global::System.Boolean Equals(CancelFusionConfigurationCompositionInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (RequestId.Equals(other.RequestId)); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * RequestId.GetHashCode(); - return hash; - } - } - - private global::System.String _value_requestId = default !; - private global::System.Boolean _set_requestId; - public global::System.String RequestId - { - get => _value_requestId; - init - { - _set_requestId = true; - _value_requestId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionConfigurationCompositionInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "ValidateFusionConfigurationCompositionInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsConfigurationSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("configuration", FormatConfiguration(input.Configuration))); - } - - if (inputInfo.IsRequestIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); - } - - return fields; - } - - private global::System.Object? FormatConfiguration(global::StrawberryShake.Upload input) - { - return _uploadFormatter.Format(input); - } - - private global::System.Object? FormatRequestId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ValidateFusionConfigurationCompositionInput : global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo - { - public virtual global::System.Boolean Equals(ValidateFusionConfigurationCompositionInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Configuration.Equals(other.Configuration)) && RequestId.Equals(other.RequestId); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Configuration.GetHashCode(); - hash ^= 397 * RequestId.GetHashCode(); - return hash; - } - } - - private global::StrawberryShake.Upload _value_configuration; - private global::System.Boolean _set_configuration; - private global::System.String _value_requestId = default !; - private global::System.Boolean _set_requestId; - public global::StrawberryShake.Upload Configuration - { - get => _value_configuration; - init - { - _set_configuration = true; - _value_configuration = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo.IsConfigurationSet => _set_configuration; - - public global::System.String RequestId - { - get => _value_requestId; - init - { - _set_requestId = true; - _value_requestId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionInputInfo.IsRequestIdSet => _set_requestId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputValueFormatterGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionConfigurationPublishInputInputValueFormatter : global::StrawberryShake.Serialization.IInputObjectFormatter - { - private global::StrawberryShake.Serialization.IInputValueFormatter _uploadFormatter = default !; - private global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter = default !; - public global::System.String TypeName => "CommitFusionConfigurationPublishInput"; - - public void Initialize(global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _uploadFormatter = serializerResolver.GetInputValueFormatter("Upload"); - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - public global::System.Object? Format(global::System.Object? runtimeValue) - { - if (runtimeValue is null) - { - return null; - } - - var input = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput; - var inputInfo = runtimeValue as global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo; - if (input is null || inputInfo is null) - { - throw new global::System.ArgumentException(nameof(runtimeValue)); - } - - var fields = new global::System.Collections.Generic.List>(); - if (inputInfo.IsConfigurationSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("configuration", FormatConfiguration(input.Configuration))); - } - - if (inputInfo.IsRequestIdSet) - { - fields.Add(new global::System.Collections.Generic.KeyValuePair("requestId", FormatRequestId(input.RequestId))); - } - - return fields; - } - - private global::System.Object? FormatConfiguration(global::StrawberryShake.Upload input) - { - return _uploadFormatter.Format(input); - } - - private global::System.Object? FormatRequestId(global::System.String input) - { - if (input is null) - { - throw new global::System.ArgumentNullException(nameof(input)); - } - - return _iDFormatter.Format(input); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CommitFusionConfigurationPublishInput : global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo - { - public virtual global::System.Boolean Equals(CommitFusionConfigurationPublishInput? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return (Configuration.Equals(other.Configuration)) && RequestId.Equals(other.RequestId); - } - - public override global::System.Int32 GetHashCode() - { - unchecked - { - int hash = 5; - hash ^= 397 * Configuration.GetHashCode(); - hash ^= 397 * RequestId.GetHashCode(); - return hash; - } - } - - private global::StrawberryShake.Upload _value_configuration; - private global::System.Boolean _set_configuration; - private global::System.String _value_requestId = default !; - private global::System.Boolean _set_requestId; - public global::StrawberryShake.Upload Configuration - { - get => _value_configuration; - init - { - _set_configuration = true; - _value_configuration = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo.IsConfigurationSet => _set_configuration; - - public global::System.String RequestId - { - get => _value_requestId; - init - { - _set_requestId = true; - _value_requestId = value; - } - } - - global::System.Boolean global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishInputInfo.IsRequestIdSet => _set_requestId; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.EnumGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal enum ProcessingState - { - Approved, - Cancelled, - Failed, - Processing, - Queued, - Ready, - Success, - WaitingForApproval - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.EnumParserGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ProcessingStateSerializer : global::StrawberryShake.Serialization.IInputValueFormatter, global::StrawberryShake.Serialization.ILeafValueParser - { - public global::System.String TypeName => "ProcessingState"; - - public ProcessingState Parse(global::System.String serializedValue) - { - return serializedValue switch - { - "APPROVED" => ProcessingState.Approved, - "CANCELLED" => ProcessingState.Cancelled, - "FAILED" => ProcessingState.Failed, - "PROCESSING" => ProcessingState.Processing, - "QUEUED" => ProcessingState.Queued, - "READY" => ProcessingState.Ready, - "SUCCESS" => ProcessingState.Success, - "WAITING_FOR_APPROVAL" => ProcessingState.WaitingForApproval, - _ => throw new global::StrawberryShake.GraphQLClientException($"String value '{serializedValue}' can't be converted to enum ProcessingState")}; - } - - public global::System.Object Format(global::System.Object? runtimeValue) - { - return runtimeValue switch - { - ProcessingState.Approved => "APPROVED", - ProcessingState.Cancelled => "CANCELLED", - ProcessingState.Failed => "FAILED", - ProcessingState.Processing => "PROCESSING", - ProcessingState.Queued => "QUEUED", - ProcessingState.Ready => "READY", - ProcessingState.Success => "SUCCESS", - ProcessingState.WaitingForApproval => "WAITING_FOR_APPROVAL", - _ => throw new global::StrawberryShake.GraphQLClientException($"Enum ProcessingState value '{runtimeValue}' can't be converted to string")}; - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation - /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentMutationDocument : global::StrawberryShake.IDocument - { - private ClaimFusionDeploymentMutationDocument() - { - } - - public static ClaimFusionDeploymentMutationDocument Instance { get; } = new ClaimFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput!) { startFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "260243f143261b45d8ac102e9c5459bd"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation - /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _startFusionConfigurationCompositionInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _startFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("StartFusionConfigurationCompositionInput"); - } - - private ClaimFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter startFusionConfigurationCompositionInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _startFusionConfigurationCompositionInputFormatter = startFusionConfigurationCompositionInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IClaimFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _startFusionConfigurationCompositionInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - return CreateRequest(variables); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return new global::StrawberryShake.OperationRequest(id: ClaimFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ClaimFusionDeployment", document: ClaimFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _startFusionConfigurationCompositionInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the ClaimFusionDeployment GraphQL operation - /// - /// mutation ClaimFusionDeployment( - /// $input: StartFusionConfigurationCompositionInput! - /// ) { - /// startFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IClaimFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.StartFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentSubscriptionDocument : global::StrawberryShake.IDocument - { - private WatchFusionDeploymentSubscriptionDocument() - { - } - - public static WatchFusionDeploymentSubscriptionDocument Instance { get; } = new WatchFusionDeploymentSubscriptionDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Subscription; - public global::System.ReadOnlySpan Body => "subscription WatchFusionDeployment($requestId: ID!) { onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { __typename state ... on ProcessingTaskIsQueued { queuePosition } ... on FusionConfigurationPublishingFailed { errors { __typename message } } ... on FusionConfigurationValidationFailed { errors { __typename message } } } }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "e2015ba8e5cd3117ae71c8d44bd650d7"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentSubscription : global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _iDFormatter; - public WatchFusionDeploymentSubscription(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _iDFormatter = serializerResolver.GetInputValueFormatter("ID"); - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IWatchFusionDeploymentResult); - - public global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(requestId); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.String requestId) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("requestId", FormatRequestId(requestId)); - return CreateRequest(variables); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return new global::StrawberryShake.OperationRequest(id: WatchFusionDeploymentSubscriptionDocument.Instance.Hash.Value, name: "WatchFusionDeployment", document: WatchFusionDeploymentSubscriptionDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); - } - - private global::System.Object? FormatRequestId(global::System.String value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _iDFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the WatchFusionDeployment GraphQL operation - /// - /// subscription WatchFusionDeployment($requestId: ID!) { - /// onFusionConfigurationPublishingTaskChanged(requestId: $requestId) { - /// __typename - /// state - /// ... on ProcessingTaskIsQueued { - /// queuePosition - /// } - /// ... on FusionConfigurationPublishingFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// ... on FusionConfigurationValidationFailed { - /// errors { - /// __typename - /// message - /// } - /// } - /// } - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IWatchFusionDeploymentSubscription : global::StrawberryShake.IOperationRequestFactory - { - global::System.IObservable> Watch(global::System.String requestId, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation - /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { - /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaMutationDocument : global::StrawberryShake.IDocument - { - private UploadFusionSourceSchemaMutationDocument() - { - } - - public static UploadFusionSourceSchemaMutationDocument Instance { get; } = new UploadFusionSourceSchemaMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { uploadFusionSubgraph(input: $input) { __typename fusionSubgraphVersion { __typename id } errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "00a2f0dbe78ac0ebc27723990e9d8a6e"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation - /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { - /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaMutation : global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _uploadFusionSubgraphInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _uploadFusionSubgraphInputFormatter = serializerResolver.GetInputValueFormatter("UploadFusionSubgraphInput"); - } - - private UploadFusionSourceSchemaMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter uploadFusionSubgraphInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _uploadFusionSubgraphInputFormatter = uploadFusionSubgraphInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IUploadFusionSourceSchemaResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchemaMutation(_operationExecutor, _configure.Add(configure), _uploadFusionSubgraphInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - private void MapFilesFromTypeUploadFusionSubgraphInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) - { - var pathArchive = path + ".archive"; - var valueArchive = value.Archive; - files.Add(pathArchive, valueArchive is global::StrawberryShake.Upload u ? u : null); - } - - private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value, global::System.Collections.Generic.Dictionary files) - { - if (value is { } value_i) - { - MapFilesFromTypeUploadFusionSubgraphInput(path, value_i, files); - } - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - var files = new global::System.Collections.Generic.Dictionary(); - MapFilesFromArgumentInput("variables.input", input, files); - return CreateRequest(variables, files); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) - { - return new global::StrawberryShake.OperationRequest(id: UploadFusionSourceSchemaMutationDocument.Instance.Hash.Value, name: "UploadFusionSourceSchema", document: UploadFusionSourceSchemaMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _uploadFusionSubgraphInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the UploadFusionSourceSchema GraphQL operation - /// - /// mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { - /// uploadFusionSubgraph(input: $input) { - /// __typename - /// fusionSubgraphVersion { - /// __typename - /// id - /// } - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IUploadFusionSourceSchemaMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSubgraphInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation - /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { - /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentMutationDocument : global::StrawberryShake.IDocument - { - private BeginFusionDeploymentMutationDocument() - { - } - - public static BeginFusionDeploymentMutationDocument Instance { get; } = new BeginFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { beginFusionConfigurationPublish(input: $input) { __typename requestId errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "820eda44da60af366f2c12a562552673"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation - /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { - /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _beginFusionConfigurationPublishInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _beginFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("BeginFusionConfigurationPublishInput"); - } - - private BeginFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter beginFusionConfigurationPublishInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _beginFusionConfigurationPublishInputFormatter = beginFusionConfigurationPublishInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IBeginFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _beginFusionConfigurationPublishInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - return CreateRequest(variables); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return new global::StrawberryShake.OperationRequest(id: BeginFusionDeploymentMutationDocument.Instance.Hash.Value, name: "BeginFusionDeployment", document: BeginFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _beginFusionConfigurationPublishInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the BeginFusionDeployment GraphQL operation - /// - /// mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { - /// beginFusionConfigurationPublish(input: $input) { - /// __typename - /// requestId - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IBeginFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.BeginFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation - /// - /// mutation ReleaseFusionDeployment( - /// $input: CancelFusionConfigurationCompositionInput! - /// ) { - /// cancelFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentMutationDocument : global::StrawberryShake.IDocument - { - private ReleaseFusionDeploymentMutationDocument() - { - } - - public static ReleaseFusionDeploymentMutationDocument Instance { get; } = new ReleaseFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInput!) { cancelFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "66f0c2afee4b4150360b31081c019f81"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation - /// - /// mutation ReleaseFusionDeployment( - /// $input: CancelFusionConfigurationCompositionInput! - /// ) { - /// cancelFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _cancelFusionConfigurationCompositionInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public ReleaseFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _cancelFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("CancelFusionConfigurationCompositionInput"); - } - - private ReleaseFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter cancelFusionConfigurationCompositionInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _cancelFusionConfigurationCompositionInputFormatter = cancelFusionConfigurationCompositionInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IReleaseFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _cancelFusionConfigurationCompositionInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - return CreateRequest(variables); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return new global::StrawberryShake.OperationRequest(id: ReleaseFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ReleaseFusionDeployment", document: ReleaseFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _cancelFusionConfigurationCompositionInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the ReleaseFusionDeployment GraphQL operation - /// - /// mutation ReleaseFusionDeployment( - /// $input: CancelFusionConfigurationCompositionInput! - /// ) { - /// cancelFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IReleaseFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CancelFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the ValidateFusionDeployment GraphQL operation - /// - /// mutation ValidateFusionDeployment( - /// $input: ValidateFusionConfigurationCompositionInput! - /// ) { - /// validateFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentMutationDocument : global::StrawberryShake.IDocument - { - private ValidateFusionDeploymentMutationDocument() - { - } - - public static ValidateFusionDeploymentMutationDocument Instance { get; } = new ValidateFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation ValidateFusionDeployment($input: ValidateFusionConfigurationCompositionInput!) { validateFusionConfigurationComposition(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "f33496c099bf6caf49ab60806b98a9eb"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the ValidateFusionDeployment GraphQL operation - /// - /// mutation ValidateFusionDeployment( - /// $input: ValidateFusionConfigurationCompositionInput! - /// ) { - /// validateFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _validateFusionConfigurationCompositionInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public ValidateFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _validateFusionConfigurationCompositionInputFormatter = serializerResolver.GetInputValueFormatter("ValidateFusionConfigurationCompositionInput"); - } - - private ValidateFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter validateFusionConfigurationCompositionInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _validateFusionConfigurationCompositionInputFormatter = validateFusionConfigurationCompositionInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(IValidateFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _validateFusionConfigurationCompositionInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - private void MapFilesFromTypeValidateFusionConfigurationCompositionInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value, global::System.Collections.Generic.Dictionary files) - { - var pathConfiguration = path + ".configuration"; - var valueConfiguration = value.Configuration; - files.Add(pathConfiguration, valueConfiguration is global::StrawberryShake.Upload u ? u : null); - } - - private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value, global::System.Collections.Generic.Dictionary files) - { - if (value is { } value_i) - { - MapFilesFromTypeValidateFusionConfigurationCompositionInput(path, value_i, files); - } - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - var files = new global::System.Collections.Generic.Dictionary(); - MapFilesFromArgumentInput("variables.input", input, files); - return CreateRequest(variables, files); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) - { - return new global::StrawberryShake.OperationRequest(id: ValidateFusionDeploymentMutationDocument.Instance.Hash.Value, name: "ValidateFusionDeployment", document: ValidateFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _validateFusionConfigurationCompositionInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the ValidateFusionDeployment GraphQL operation - /// - /// mutation ValidateFusionDeployment( - /// $input: ValidateFusionConfigurationCompositionInput! - /// ) { - /// validateFusionConfigurationComposition(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IValidateFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionConfigurationCompositionInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationDocumentGenerator - /// - /// Represents the operation service of the CommitFusionDeployment GraphQL operation - /// - /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { - /// commitFusionConfigurationPublish(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentMutationDocument : global::StrawberryShake.IDocument - { - private CommitFusionDeploymentMutationDocument() - { - } - - public static CommitFusionDeploymentMutationDocument Instance { get; } = new CommitFusionDeploymentMutationDocument(); - public global::StrawberryShake.OperationKind Kind => global::StrawberryShake.OperationKind.Mutation; - public global::System.ReadOnlySpan Body => "mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { commitFusionConfigurationPublish(input: $input) { __typename errors { __typename ...FusionError } } } fragment FusionError on Error { message }"u8; - public global::StrawberryShake.DocumentHash Hash { get; } = new global::StrawberryShake.DocumentHash("md5Hash", "1f0d0749e4b952e52a1322169df539ac"); - - public override global::System.String ToString() - { -#if NETCOREAPP3_1_OR_GREATER - return global::System.Text.Encoding.UTF8.GetString(Body); -#else - return global::System.Text.Encoding.UTF8.GetString(Body.ToArray()); -#endif - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceGenerator - /// - /// Represents the operation service of the CommitFusionDeployment GraphQL operation - /// - /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { - /// commitFusionConfigurationPublish(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentMutation : global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation - { - private readonly global::StrawberryShake.IOperationExecutor _operationExecutor; - private readonly global::StrawberryShake.Serialization.IInputValueFormatter _commitFusionConfigurationPublishInputFormatter; - private readonly global::System.Collections.Immutable.ImmutableArray> _configure = global::System.Collections.Immutable.ImmutableArray>.Empty; - public CommitFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - _operationExecutor = operationExecutor ?? throw new global::System.ArgumentNullException(nameof(operationExecutor)); - _commitFusionConfigurationPublishInputFormatter = serializerResolver.GetInputValueFormatter("CommitFusionConfigurationPublishInput"); - } - - private CommitFusionDeploymentMutation(global::StrawberryShake.IOperationExecutor operationExecutor, global::System.Collections.Immutable.ImmutableArray> configure, global::StrawberryShake.Serialization.IInputValueFormatter commitFusionConfigurationPublishInputFormatter) - { - _operationExecutor = operationExecutor; - _configure = configure; - _commitFusionConfigurationPublishInputFormatter = commitFusionConfigurationPublishInputFormatter; - } - - global::System.Type global::StrawberryShake.IOperationRequestFactory.ResultType => typeof(ICommitFusionDeploymentResult); - - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation With(global::System.Action configure) - { - return new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeploymentMutation(_operationExecutor, _configure.Add(configure), _commitFusionConfigurationPublishInputFormatter); - } - - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.RequestUri"] = requestUri); - } - - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient) - { - return With(r => r.ContextData["StrawberryShake.Transport.Http.HttpConnection.HttpClient"] = httpClient); - } - - public async global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default) - { - var request = CreateRequest(input); - foreach (var configure in _configure) - { - configure(request); - } - - return await _operationExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - } - - private void MapFilesFromTypeCommitFusionConfigurationPublishInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value, global::System.Collections.Generic.Dictionary files) - { - var pathConfiguration = path + ".configuration"; - var valueConfiguration = value.Configuration; - files.Add(pathConfiguration, valueConfiguration is global::StrawberryShake.Upload u ? u : null); - } - - private void MapFilesFromArgumentInput(global::System.String path, global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value, global::System.Collections.Generic.Dictionary files) - { - if (value is { } value_i) - { - MapFilesFromTypeCommitFusionConfigurationPublishInput(path, value_i, files); - } - } - - public global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null) - { - var request = CreateRequest(input); - return _operationExecutor.Watch(request, strategy); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input) - { - var variables = new global::System.Collections.Generic.Dictionary(); - variables.Add("input", FormatInput(input)); - var files = new global::System.Collections.Generic.Dictionary(); - MapFilesFromArgumentInput("variables.input", input, files); - return CreateRequest(variables, files); - } - - private global::StrawberryShake.OperationRequest CreateRequest(global::System.Collections.Generic.IReadOnlyDictionary? variables, global::System.Collections.Generic.Dictionary files) - { - return new global::StrawberryShake.OperationRequest(id: CommitFusionDeploymentMutationDocument.Instance.Hash.Value, name: "CommitFusionDeployment", document: CommitFusionDeploymentMutationDocument.Instance, strategy: global::StrawberryShake.RequestStrategy.Default, files: files, variables: variables); - } - - private global::System.Object? FormatInput(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput value) - { - if (value is null) - { - throw new global::System.ArgumentNullException(nameof(value)); - } - - return _commitFusionConfigurationPublishInputFormatter.Format(value); - } - - global::StrawberryShake.OperationRequest global::StrawberryShake.IOperationRequestFactory.Create(global::System.Collections.Generic.IReadOnlyDictionary? variables) - { - return CreateRequest(variables!, new global::System.Collections.Generic.Dictionary()); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.OperationServiceInterfaceGenerator - /// - /// Represents the operation service of the CommitFusionDeployment GraphQL operation - /// - /// mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) { - /// commitFusionConfigurationPublish(input: $input) { - /// __typename - /// errors { - /// __typename - /// ...FusionError - /// } - /// } - /// } - /// - /// fragment FusionError on Error { - /// message - /// } - /// - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface ICommitFusionDeploymentMutation : global::StrawberryShake.IOperationRequestFactory - { - global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation With(global::System.Action configure); - global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithRequestUri(global::System.Uri requestUri); - global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation WithHttpClient(global::System.Net.Http.HttpClient httpClient); - global::System.Threading.Tasks.Task> ExecuteAsync(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::System.Threading.CancellationToken cancellationToken = default); - global::System.IObservable> Watch(global::ChilliCream.Nitro.Fusion.Transport.CommitFusionConfigurationPublishInput input, global::StrawberryShake.ExecutionStrategy? strategy = null); - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ClientGenerator - /// - /// Represents the FusionApiClient GraphQL client - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class FusionApiClient : global::ChilliCream.Nitro.Fusion.Transport.IFusionApiClient - { - private readonly global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation _claimFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription _watchFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation _uploadFusionSourceSchema; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation _beginFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation _releaseFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation _validateFusionDeployment; - private readonly global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation _commitFusionDeployment; - public FusionApiClient(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation claimFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription watchFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation uploadFusionSourceSchema, global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation beginFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation releaseFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation validateFusionDeployment, global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation commitFusionDeployment) - { - _claimFusionDeployment = claimFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(claimFusionDeployment)); - _watchFusionDeployment = watchFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(watchFusionDeployment)); - _uploadFusionSourceSchema = uploadFusionSourceSchema ?? throw new global::System.ArgumentNullException(nameof(uploadFusionSourceSchema)); - _beginFusionDeployment = beginFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(beginFusionDeployment)); - _releaseFusionDeployment = releaseFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(releaseFusionDeployment)); - _validateFusionDeployment = validateFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(validateFusionDeployment)); - _commitFusionDeployment = commitFusionDeployment ?? throw new global::System.ArgumentNullException(nameof(commitFusionDeployment)); - } - - public static global::System.String ClientName => "FusionApiClient"; - public global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment => _claimFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment => _watchFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema => _uploadFusionSourceSchema; - public global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment => _beginFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment => _releaseFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment => _validateFusionDeployment; - public global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment => _commitFusionDeployment; - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ClientInterfaceGenerator - /// - /// Represents the FusionApiClient GraphQL client - /// - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial interface IFusionApiClient - { - global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentMutation ClaimFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentSubscription WatchFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaMutation UploadFusionSourceSchema { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentMutation BeginFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentMutation ReleaseFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentMutation ValidateFusionDeployment { get; } - - global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentMutation CommitFusionDeployment { get; } - } -} - -namespace ChilliCream.Nitro.Fusion.Transport.State -{ - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public ClaimFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeploymentResult); - - public ClaimFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is ClaimFusionDeploymentResultInfo info) - { - return new ClaimFusionDeploymentResult(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(info.StartFusionConfigurationComposition)); - } - - throw new global::System.ArgumentException("ClaimFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData data) - { - IClaimFusionDeployment_StartFusionConfigurationComposition returnValue = default !; - if (data.__typename.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new ClaimFusionDeployment_StartFusionConfigurationComposition_StartFusionConfigurationCompositionPayload(MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIClaimFusionDeployment_StartFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData child in list) - { - startFusionConfigurationCompositionErrors.Add(MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(child)); - } - - return startFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IClaimFusionDeployment_StartFusionConfigurationComposition_Errors MapNonNullableIClaimFusionDeployment_StartFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData data) - { - IClaimFusionDeployment_StartFusionConfigurationComposition_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ClaimFusionDeployment_StartFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public ClaimFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData startFusionConfigurationComposition) - { - StartFusionConfigurationComposition = startFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData StartFusionConfigurationComposition { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new ClaimFusionDeploymentResultInfo(StartFusionConfigurationComposition); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public WatchFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeploymentResult); - - public WatchFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is WatchFusionDeploymentResultInfo info) - { - return new WatchFusionDeploymentResult(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(info.OnFusionConfigurationPublishingTaskChanged)); - } - - throw new global::System.ArgumentException("WatchFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData data) - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData fusionConfigurationPublishingFailed) - { - if (!fusionConfigurationPublishingFailed.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed(fusionConfigurationPublishingFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(fusionConfigurationPublishingFailed.Errors)); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData fusionConfigurationPublishingSuccess) - { - if (!fusionConfigurationPublishingSuccess.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingSuccess(fusionConfigurationPublishingSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationPublishingSuccess.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData fusionConfigurationValidationFailed) - { - if (!fusionConfigurationValidationFailed.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed(fusionConfigurationValidationFailed.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationFailed.State!.Value, MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(fusionConfigurationValidationFailed.Errors)); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData fusionConfigurationValidationSuccess) - { - if (!fusionConfigurationValidationSuccess.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationSuccess(fusionConfigurationValidationSuccess.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationValidationSuccess.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData operationInProgress) - { - if (!operationInProgress.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_OperationInProgress(operationInProgress.__typename ?? throw new global::System.ArgumentNullException(), operationInProgress.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData processingTaskApproved) - { - if (!processingTaskApproved.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskApproved(processingTaskApproved.__typename ?? throw new global::System.ArgumentNullException(), processingTaskApproved.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData processingTaskIsQueued) - { - if (!processingTaskIsQueued.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (!processingTaskIsQueued.QueuePosition.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsQueued(processingTaskIsQueued.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsQueued.State!.Value, processingTaskIsQueued.QueuePosition!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData processingTaskIsReady) - { - if (!processingTaskIsReady.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ProcessingTaskIsReady(processingTaskIsReady.__typename ?? throw new global::System.ArgumentNullException(), processingTaskIsReady.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData validationInProgress) - { - if (!validationInProgress.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ValidationInProgress(validationInProgress.__typename ?? throw new global::System.ArgumentNullException(), validationInProgress.State!.Value); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData waitForApproval) - { - if (!waitForApproval.State.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_WaitForApproval(waitForApproval.__typename ?? throw new global::System.ArgumentNullException(), waitForApproval.State!.Value); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - throw new global::System.ArgumentNullException(); - } - - var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData child in list) - { - fusionConfigurationPublishingErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(child)); - } - - return fusionConfigurationPublishingErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData data) - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ConcurrentOperationError(concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData processingTimeoutError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ProcessingTimeoutError(processingTimeoutError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData readyTimeoutError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_ReadyTimeoutError(readyTimeoutError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1NonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - throw new global::System.ArgumentNullException(); - } - - var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData child in list) - { - fusionConfigurationValidationErrors.Add(MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(child)); - } - - return fusionConfigurationValidationErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1 MapNonNullableIWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData data) - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_1? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData invalidGraphQLSchemaError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_InvalidGraphQLSchemaError_1(invalidGraphQLSchemaError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData mcpFeatureCollectionValidationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_McpFeatureCollectionValidationError(mcpFeatureCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData openApiCollectionValidationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_OpenApiCollectionValidationError(openApiCollectionValidationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData persistedQueryValidationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_PersistedQueryValidationError(persistedQueryValidationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData schemaVersionChangeViolationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_SchemaVersionChangeViolationError(schemaVersionChangeViolationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData unexpectedProcessingError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.WatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_Errors_UnexpectedProcessingError_1(unexpectedProcessingError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public WatchFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData onFusionConfigurationPublishingTaskChanged) - { - OnFusionConfigurationPublishingTaskChanged = onFusionConfigurationPublishingTaskChanged; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData OnFusionConfigurationPublishingTaskChanged { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new WatchFusionDeploymentResultInfo(OnFusionConfigurationPublishingTaskChanged); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public UploadFusionSourceSchemaResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchemaResult); - - public UploadFusionSourceSchemaResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is UploadFusionSourceSchemaResultInfo info) - { - return new UploadFusionSourceSchemaResult(MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(info.UploadFusionSubgraph)); - } - - throw new global::System.ArgumentException("UploadFusionSourceSchemaResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData data) - { - IUploadFusionSourceSchema_UploadFusionSubgraph returnValue = default !; - if (data.__typename.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new UploadFusionSourceSchema_UploadFusionSubgraph_UploadFusionSubgraphPayload(MapIUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(data.FusionSubgraphVersion), MapIUploadFusionSourceSchema_UploadFusionSubgraph_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion? MapIUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? data) - { - if (data is null) - { - return null; - } - - IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion returnValue = default !; - if (data.__typename.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal)) - { - returnValue = new UploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion_FusionSubgraphVersion(data.Id ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIUploadFusionSourceSchema_UploadFusionSubgraph_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData child in list) - { - uploadFusionSubgraphErrors.Add(MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph_Errors(child)); - } - - return uploadFusionSubgraphErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IUploadFusionSourceSchema_UploadFusionSubgraph_Errors MapNonNullableIUploadFusionSourceSchema_UploadFusionSubgraph_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData data) - { - IUploadFusionSourceSchema_UploadFusionSubgraph_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData invalidFusionSourceSchemaArchiveError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidFusionSourceSchemaArchiveError(invalidFusionSourceSchemaArchiveError.__typename ?? throw new global::System.ArgumentNullException(), invalidFusionSourceSchemaArchiveError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData apiNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ApiNotFoundError(apiNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), apiNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData concurrentOperationError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_ConcurrentOperationError(concurrentOperationError.__typename ?? throw new global::System.ArgumentNullException(), concurrentOperationError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData duplicatedTagError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_DuplicatedTagError(duplicatedTagError.__typename ?? throw new global::System.ArgumentNullException(), duplicatedTagError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData invalidSourceMetadataInputError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.UploadFusionSourceSchema_UploadFusionSubgraph_Errors_InvalidSourceMetadataInputError(invalidSourceMetadataInputError.__typename ?? throw new global::System.ArgumentNullException(), invalidSourceMetadataInputError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public UploadFusionSourceSchemaResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData uploadFusionSubgraph) - { - UploadFusionSubgraph = uploadFusionSubgraph; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData UploadFusionSubgraph { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new UploadFusionSourceSchemaResultInfo(UploadFusionSubgraph); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public BeginFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeploymentResult); - - public BeginFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is BeginFusionDeploymentResultInfo info) - { - return new BeginFusionDeploymentResult(MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(info.BeginFusionConfigurationPublish)); - } - - throw new global::System.ArgumentException("BeginFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData data) - { - IBeginFusionDeployment_BeginFusionConfigurationPublish returnValue = default !; - if (data.__typename.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new BeginFusionDeployment_BeginFusionConfigurationPublish_BeginFusionConfigurationPublishPayload(data.RequestId, MapIBeginFusionDeployment_BeginFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIBeginFusionDeployment_BeginFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData child in list) - { - beginFusionConfigurationPublishErrors.Add(MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish_Errors(child)); - } - - return beginFusionConfigurationPublishErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors MapNonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData data) - { - IBeginFusionDeployment_BeginFusionConfigurationPublish_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData apiNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_ApiNotFoundError(apiNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), apiNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData stageNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_StageNotFoundError(stageNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), stageNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData subgraphInvalidError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_SubgraphInvalidError(subgraphInvalidError.__typename ?? throw new global::System.ArgumentNullException(), subgraphInvalidError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData invalidSourceMetadataInputError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.BeginFusionDeployment_BeginFusionConfigurationPublish_Errors_InvalidSourceMetadataInputError(invalidSourceMetadataInputError.__typename ?? throw new global::System.ArgumentNullException(), invalidSourceMetadataInputError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public BeginFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData beginFusionConfigurationPublish) - { - BeginFusionConfigurationPublish = beginFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData BeginFusionConfigurationPublish { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new BeginFusionDeploymentResultInfo(BeginFusionConfigurationPublish); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public ReleaseFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeploymentResult); - - public ReleaseFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is ReleaseFusionDeploymentResultInfo info) - { - return new ReleaseFusionDeploymentResult(MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(info.CancelFusionConfigurationComposition)); - } - - throw new global::System.ArgumentException("ReleaseFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData data) - { - IReleaseFusionDeployment_CancelFusionConfigurationComposition returnValue = default !; - if (data.__typename.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new ReleaseFusionDeployment_CancelFusionConfigurationComposition_CancelFusionConfigurationCompositionPayload(MapIReleaseFusionDeployment_CancelFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIReleaseFusionDeployment_CancelFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData child in list) - { - cancelFusionConfigurationCompositionErrors.Add(MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors(child)); - } - - return cancelFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors MapNonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData data) - { - IReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ReleaseFusionDeployment_CancelFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public ReleaseFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData cancelFusionConfigurationComposition) - { - CancelFusionConfigurationComposition = cancelFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData CancelFusionConfigurationComposition { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new ReleaseFusionDeploymentResultInfo(CancelFusionConfigurationComposition); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public ValidateFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeploymentResult); - - public ValidateFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is ValidateFusionDeploymentResultInfo info) - { - return new ValidateFusionDeploymentResult(MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(info.ValidateFusionConfigurationComposition)); - } - - throw new global::System.ArgumentException("ValidateFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData data) - { - IValidateFusionDeployment_ValidateFusionConfigurationComposition returnValue = default !; - if (data.__typename.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new ValidateFusionDeployment_ValidateFusionConfigurationComposition_ValidateFusionConfigurationCompositionPayload(MapIValidateFusionDeployment_ValidateFusionConfigurationComposition_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapIValidateFusionDeployment_ValidateFusionConfigurationComposition_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData child in list) - { - validateFusionConfigurationCompositionErrors.Add(MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors(child)); - } - - return validateFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors MapNonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData data) - { - IValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.ValidateFusionDeployment_ValidateFusionConfigurationComposition_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public ValidateFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData validateFusionConfigurationComposition) - { - ValidateFusionConfigurationComposition = validateFusionConfigurationComposition; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData ValidateFusionConfigurationComposition { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new ValidateFusionDeploymentResultInfo(ValidateFusionConfigurationComposition); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultDataFactoryGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentResultFactory : global::StrawberryShake.IOperationResultDataFactory - { - public CommitFusionDeploymentResultFactory() - { - } - - global::System.Type global::StrawberryShake.IOperationResultDataFactory.ResultType => typeof(global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeploymentResult); - - public CommitFusionDeploymentResult Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot = null) - { - if (dataInfo is CommitFusionDeploymentResultInfo info) - { - return new CommitFusionDeploymentResult(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(info.CommitFusionConfigurationPublish)); - } - - throw new global::System.ArgumentException("CommitFusionDeploymentResultInfo expected."); - } - - private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData data) - { - ICommitFusionDeployment_CommitFusionConfigurationPublish returnValue = default !; - if (data.__typename.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal)) - { - returnValue = new CommitFusionDeployment_CommitFusionConfigurationPublish_CommitFusionConfigurationPublishPayload(MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(data.Errors)); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - private global::System.Collections.Generic.IReadOnlyList? MapICommitFusionDeployment_CommitFusionConfigurationPublish_ErrorsNonNullableArray(global::System.Collections.Generic.IReadOnlyList? list) - { - if (list is null) - { - return null; - } - - var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); - foreach (global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData child in list) - { - commitFusionConfigurationPublishErrors.Add(MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(child)); - } - - return commitFusionConfigurationPublishErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors MapNonNullableICommitFusionDeployment_CommitFusionConfigurationPublish_Errors(global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData data) - { - ICommitFusionDeployment_CommitFusionConfigurationPublish_Errors? returnValue; - if (data is global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData unauthorizedOperation) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_UnauthorizedOperation(unauthorizedOperation.__typename ?? throw new global::System.ArgumentNullException(), unauthorizedOperation.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData fusionConfigurationRequestNotFoundError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_FusionConfigurationRequestNotFoundError(fusionConfigurationRequestNotFoundError.__typename ?? throw new global::System.ArgumentNullException(), fusionConfigurationRequestNotFoundError.Message ?? throw new global::System.ArgumentNullException()); - } - else if (data is global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData invalidProcessingStateTransitionError) - { - returnValue = new global::ChilliCream.Nitro.Fusion.Transport.CommitFusionDeployment_CommitFusionConfigurationPublish_Errors_InvalidProcessingStateTransitionError(invalidProcessingStateTransitionError.__typename ?? throw new global::System.ArgumentNullException(), invalidProcessingStateTransitionError.Message ?? throw new global::System.ArgumentNullException()); - } - else - { - throw new global::System.NotSupportedException(); - } - - return returnValue; - } - - global::System.Object global::StrawberryShake.IOperationResultDataFactory.Create(global::StrawberryShake.IOperationResultDataInfo dataInfo, global::StrawberryShake.IEntityStoreSnapshot? snapshot) - { - return Create(dataInfo, snapshot); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.ResultInfoGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentResultInfo : global::StrawberryShake.IOperationResultDataInfo - { - public CommitFusionDeploymentResultInfo(global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData commitFusionConfigurationPublish) - { - CommitFusionConfigurationPublish = commitFusionConfigurationPublish; - } - - public global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData CommitFusionConfigurationPublish { get; } - public global::System.Collections.Generic.IReadOnlyCollection EntityIds => global::System.Array.Empty(); - public global::System.UInt64 Version => 0; - - public global::StrawberryShake.IOperationResultDataInfo WithVersion(global::System.UInt64 version) - { - return new CommitFusionDeploymentResultInfo(CommitFusionConfigurationPublish); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IStartFusionConfigurationCompositionInputInfo - { - global::System.Boolean IsRequestIdSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IUploadFusionSubgraphInputInfo - { - global::System.Boolean IsApiIdSet { get; } - - global::System.Boolean IsArchiveSet { get; } - - global::System.Boolean IsSourceSet { get; } - - global::System.Boolean IsTagSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface ISourceMetadataInputInfo - { - global::System.Boolean IsAzureDevOpsSet { get; } - - global::System.Boolean IsGithubSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IAzureDevOpsSourceMetadataInputInfo - { - global::System.Boolean IsActorSet { get; } - - global::System.Boolean IsCommitHashSet { get; } - - global::System.Boolean IsJobIdSet { get; } - - global::System.Boolean IsPipelineNameSet { get; } - - global::System.Boolean IsProjectUrlSet { get; } - - global::System.Boolean IsRepositoryUrlSet { get; } - - global::System.Boolean IsRunIdSet { get; } - - global::System.Boolean IsRunNumberSet { get; } - - global::System.Boolean IsTaskIdSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IAzureDevOpsActorInputInfo - { - global::System.Boolean IsEmailSet { get; } - - global::System.Boolean IsNameSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IGitHubSourceMetadataInputInfo - { - global::System.Boolean IsActorSet { get; } - - global::System.Boolean IsCommitHashSet { get; } - - global::System.Boolean IsJobIdSet { get; } - - global::System.Boolean IsRepositoryUrlSet { get; } - - global::System.Boolean IsRunIdSet { get; } - - global::System.Boolean IsRunNumberSet { get; } - - global::System.Boolean IsWorkflowNameSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IBeginFusionConfigurationPublishInputInfo - { - global::System.Boolean IsApiIdSet { get; } - - global::System.Boolean IsSourceSet { get; } - - global::System.Boolean IsStageNameSet { get; } - - global::System.Boolean IsSubgraphApiIdSet { get; } - - global::System.Boolean IsSubgraphNameSet { get; } - - global::System.Boolean IsSubgraphsSet { get; } - - global::System.Boolean IsTagSet { get; } - - global::System.Boolean IsWaitForApprovalSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IFusionSubgraphVersionInputInfo - { - global::System.Boolean IsNameSet { get; } - - global::System.Boolean IsTagSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface ICancelFusionConfigurationCompositionInputInfo - { - global::System.Boolean IsRequestIdSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface IValidateFusionConfigurationCompositionInputInfo - { - global::System.Boolean IsConfigurationSet { get; } - - global::System.Boolean IsRequestIdSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.InputTypeStateInterfaceGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal interface ICommitFusionConfigurationPublishInputInfo - { - global::System.Boolean IsConfigurationSet { get; } - - global::System.Boolean IsRequestIdSet { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ClaimFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ClaimFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new ClaimFusionDeploymentResultInfo(Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "startFusionConfigurationComposition"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData Deserialize_NonNullableIClaimFusionDeployment_StartFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("StartFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.StartFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IStartFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var startFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - startFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(child)); - } - - return startFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IStartFusionConfigurationCompositionErrorData Deserialize_NonNullableIStartFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class WatchFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _processingStateParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _intParser; - public WatchFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _processingStateParser = serializerResolver.GetLeafValueParser("ProcessingState") ?? throw new global::System.ArgumentException("No serializer for type `ProcessingState` found."); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _intParser = serializerResolver.GetLeafValueParser("Int") ?? throw new global::System.ArgumentException("No serializer for type `Int` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new WatchFusionDeploymentResultInfo(Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "onFusionConfigurationPublishingTaskChanged"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingResultData Deserialize_NonNullableIFusionConfigurationPublishingResultData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("FusionConfigurationPublishingFailed", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - if (typename?.Equals("FusionConfigurationPublishingSuccess", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationPublishingSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("FusionConfigurationValidationFailed", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationFailedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), errors: Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - if (typename?.Equals("FusionConfigurationValidationSuccess", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationValidationSuccessData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("OperationInProgress", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.OperationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("ProcessingTaskApproved", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskApprovedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("ProcessingTaskIsQueued", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsQueuedData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state")), queuePosition: Deserialize_NonNullableInt32(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "queuePosition"))); - } - - if (typename?.Equals("ProcessingTaskIsReady", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTaskIsReadyData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("ValidationInProgress", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidationInProgressData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - if (typename?.Equals("WaitForApproval", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.WaitForApprovalData(typename, state: Deserialize_NonNullableProcessingState(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "state"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - - private global::ChilliCream.Nitro.Fusion.Transport.ProcessingState Deserialize_NonNullableProcessingState(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _processingStateParser.Parse(obj.Value.GetString()!); - } - - private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationPublishingErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var fusionConfigurationPublishingErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - fusionConfigurationPublishingErrors.Add(Deserialize_NonNullableIFusionConfigurationPublishingErrorData(child)); - } - - return fusionConfigurationPublishingErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationPublishingErrorData Deserialize_NonNullableIFusionConfigurationPublishingErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ProcessingTimeoutError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ProcessingTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ReadyTimeoutError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ReadyTimeoutErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Collections.Generic.IReadOnlyList Deserialize_NonNullableIFusionConfigurationValidationErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var fusionConfigurationValidationErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - fusionConfigurationValidationErrors.Add(Deserialize_NonNullableIFusionConfigurationValidationErrorData(child)); - } - - return fusionConfigurationValidationErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IFusionConfigurationValidationErrorData Deserialize_NonNullableIFusionConfigurationValidationErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("InvalidGraphQLSchemaError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidGraphQLSchemaErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("McpFeatureCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.McpFeatureCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("OpenApiCollectionValidationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.OpenApiCollectionValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("PersistedQueryValidationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.PersistedQueryValidationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("SchemaVersionChangeViolationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.SchemaVersionChangeViolationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("UnexpectedProcessingError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnexpectedProcessingErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Int32 Deserialize_NonNullableInt32(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _intParser.Parse(obj.Value.GetInt32()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class UploadFusionSourceSchemaBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; - public UploadFusionSourceSchemaBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new UploadFusionSourceSchemaResultInfo(Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "uploadFusionSubgraph"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData Deserialize_NonNullableIUploadFusionSourceSchema_UploadFusionSubgraph(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UploadFusionSubgraphPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSubgraphPayloadData(typename, fusionSubgraphVersion: Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "fusionSubgraphVersion")), errors: Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? Deserialize_IUploadFusionSourceSchema_UploadFusionSubgraph_FusionSubgraphVersion(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("FusionSubgraphVersion", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData(typename, id: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _iDParser.Parse(obj.Value.GetString()!); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IUploadFusionSubgraphErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var uploadFusionSubgraphErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - uploadFusionSubgraphErrors.Add(Deserialize_NonNullableIUploadFusionSubgraphErrorData(child)); - } - - return uploadFusionSubgraphErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IUploadFusionSubgraphErrorData Deserialize_NonNullableIUploadFusionSubgraphErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("InvalidFusionSourceSchemaArchiveError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidFusionSourceSchemaArchiveErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ConcurrentOperationError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ConcurrentOperationErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("DuplicatedTagError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.DuplicatedTagErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class BeginFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _booleanParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uRLParser; - public BeginFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - _booleanParser = serializerResolver.GetLeafValueParser("Boolean") ?? throw new global::System.ArgumentException("No serializer for type `Boolean` found."); - _uRLParser = serializerResolver.GetLeafValueParser("URL") ?? throw new global::System.ArgumentException("No serializer for type `URL` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new BeginFusionDeploymentResultInfo(Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "beginFusionConfigurationPublish"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData Deserialize_NonNullableIBeginFusionDeployment_BeginFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("BeginFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionConfigurationPublishPayloadData(typename, requestId: Deserialize_String(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "requestId")), errors: Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String? Deserialize_String(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - return _iDParser.Parse(obj.Value.GetString()!); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IBeginFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var beginFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - beginFusionConfigurationPublishErrors.Add(Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(child)); - } - - return beginFusionConfigurationPublishErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IBeginFusionConfigurationPublishErrorData Deserialize_NonNullableIBeginFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("ApiNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ApiNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("StageNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.StageNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("SubgraphInvalidError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.SubgraphInvalidErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidSourceMetadataInputError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidSourceMetadataInputErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ReleaseFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ReleaseFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new ReleaseFusionDeploymentResultInfo(Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "cancelFusionConfigurationComposition"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData Deserialize_NonNullableIReleaseFusionDeployment_CancelFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("CancelFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.CancelFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICancelFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var cancelFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - cancelFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(child)); - } - - return cancelFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.ICancelFusionConfigurationCompositionErrorData Deserialize_NonNullableICancelFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class ValidateFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public ValidateFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new ValidateFusionDeploymentResultInfo(Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "validateFusionConfigurationComposition"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData Deserialize_NonNullableIValidateFusionDeployment_ValidateFusionConfigurationComposition(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("ValidateFusionConfigurationCompositionPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionConfigurationCompositionPayloadData(typename, errors: Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_IValidateFusionConfigurationCompositionErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var validateFusionConfigurationCompositionErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - validateFusionConfigurationCompositionErrors.Add(Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(child)); - } - - return validateFusionConfigurationCompositionErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.IValidateFusionConfigurationCompositionErrorData Deserialize_NonNullableIValidateFusionConfigurationCompositionErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.JsonResultBuilderGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class CommitFusionDeploymentBuilder : global::StrawberryShake.OperationResultBuilder - { - private readonly global::StrawberryShake.Serialization.ILeafValueParser _uploadParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _iDParser; - private readonly global::StrawberryShake.Serialization.ILeafValueParser _stringParser; - public CommitFusionDeploymentBuilder(global::StrawberryShake.IOperationResultDataFactory resultDataFactory, global::StrawberryShake.Serialization.ISerializerResolver serializerResolver) - { - ResultDataFactory = resultDataFactory ?? throw new global::System.ArgumentNullException(nameof(resultDataFactory)); - _uploadParser = serializerResolver.GetLeafValueParser("Upload") ?? throw new global::System.ArgumentException("No serializer for type `Upload` found."); - _iDParser = serializerResolver.GetLeafValueParser("ID") ?? throw new global::System.ArgumentException("No serializer for type `ID` found."); - _stringParser = serializerResolver.GetLeafValueParser("String") ?? throw new global::System.ArgumentException("No serializer for type `String` found."); - } - - protected override global::StrawberryShake.IOperationResultDataFactory ResultDataFactory { get; } - - protected override global::StrawberryShake.IOperationResultDataInfo BuildData(global::System.Text.Json.JsonElement obj) - { - return new CommitFusionDeploymentResultInfo(Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "commitFusionConfigurationPublish"))); - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData Deserialize_NonNullableICommitFusionDeployment_CommitFusionConfigurationPublish(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("CommitFusionConfigurationPublishPayload", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionConfigurationPublishPayloadData(typename, errors: Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "errors"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.Collections.Generic.IReadOnlyList? Deserialize_ICommitFusionConfigurationPublishErrorDataNonNullableArray(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - return null; - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - return null; - } - - var commitFusionConfigurationPublishErrors = new global::System.Collections.Generic.List(); - foreach (global::System.Text.Json.JsonElement child in obj.Value.EnumerateArray()) - { - commitFusionConfigurationPublishErrors.Add(Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(child)); - } - - return commitFusionConfigurationPublishErrors; - } - - private global::ChilliCream.Nitro.Fusion.Transport.State.ICommitFusionConfigurationPublishErrorData Deserialize_NonNullableICommitFusionConfigurationPublishErrorData(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - var typename = obj.Value.GetProperty("__typename").GetString(); - if (typename?.Equals("UnauthorizedOperation", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.UnauthorizedOperationData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("FusionConfigurationRequestNotFoundError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.FusionConfigurationRequestNotFoundErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - if (typename?.Equals("InvalidProcessingStateTransitionError", global::System.StringComparison.Ordinal) ?? false) - { - return new global::ChilliCream.Nitro.Fusion.Transport.State.InvalidProcessingStateTransitionErrorData(typename, message: Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "message"))); - } - - throw new global::System.NotSupportedException(); - } - - private global::System.String Deserialize_NonNullableString(global::System.Text.Json.JsonElement? obj) - { - if (!obj.HasValue) - { - throw new global::System.ArgumentNullException(); - } - - if (obj.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null) - { - throw new global::System.ArgumentNullException(); - } - - return _stringParser.Parse(obj.Value.GetString()!); - } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StartFusionConfigurationCompositionPayloadData - { - public StartFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IApproveDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IBeginFusionConfigurationPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICancelDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICancelFusionConfigurationCompositionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICommitFusionConfigurationPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateAccountErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateApiKeyErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateApiKeyForApiErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateMcpFeatureCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateMockSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateOpenApiCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreatePersonalAccessTokenErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ICreateWorkspaceErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteApiByIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteApiKeyErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteClientByIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteMcpFeatureCollectionByIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteMockSchemaByIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IDeleteOpenApiCollectionByIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IEnsureTunnelSessionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IForceDeleteStageByApiIdErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollClientVersionPublishRequestErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollClientVersionValidationRequestErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollSchemaVersionPublishRequestErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPollSchemaVersionValidationRequestErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishMcpFeatureCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishOpenApiCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPublishSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPushDocumentChangesErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IPushWorkspaceChangesErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRemoveWorkspaceErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRenameWorkspaceErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IRevokePersonalAccessTokenErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISetActiveWorkspaceErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IStartFusionConfigurationCompositionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUnpublishClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateApiSettingsErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateFeatureFlagsErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateMockSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdatePreferencesErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateStageCompositionSettingsErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateThemeSettingsErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadFusionSubgraphErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadMcpFeatureCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadOpenApiCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUploadSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateClientErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateFusionConfigurationCompositionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateMcpFeatureCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateOpenApiCollectionErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IValidateSchemaErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UnauthorizedOperationData : IApproveDeploymentErrorData, IBeginFusionConfigurationPublishErrorData, ICancelDeploymentErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, ICreateAccountErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, ICreatePersonalAccessTokenErrorData, ICreateWorkspaceErrorData, IDeleteApiByIdErrorData, IDeleteApiKeyErrorData, IDeleteClientByIdErrorData, IDeleteMcpFeatureCollectionByIdErrorData, IDeleteMockSchemaByIdErrorData, IDeleteOpenApiCollectionByIdErrorData, IEnsureTunnelSessionErrorData, IForceDeleteStageByApiIdErrorData, IPollClientVersionPublishRequestErrorData, IPollClientVersionValidationRequestErrorData, IPollSchemaVersionPublishRequestErrorData, IPollSchemaVersionValidationRequestErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IPushDocumentChangesErrorData, IPushWorkspaceChangesErrorData, IRemoveWorkspaceErrorData, IRenameWorkspaceErrorData, IRevokePersonalAccessTokenErrorData, ISetActiveWorkspaceErrorData, IStartFusionConfigurationCompositionErrorData, IUnpublishClientErrorData, IUpdateApiSettingsErrorData, IUpdateFeatureFlagsErrorData, IUpdateMockSchemaErrorData, IUpdatePreferencesErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateThemeSettingsErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateFusionConfigurationCompositionErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData - { - public UnauthorizedOperationData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationRequestNotFoundErrorData : ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData - { - public FusionConfigurationRequestNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidProcessingStateTransitionErrorData : IBeginFusionConfigurationPublishErrorData, ICancelFusionConfigurationCompositionErrorData, ICommitFusionConfigurationPublishErrorData, IStartFusionConfigurationCompositionErrorData, IValidateFusionConfigurationCompositionErrorData, IErrorData - { - public InvalidProcessingStateTransitionErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationPublishingResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationPublishingFailedData : IFusionConfigurationPublishingResultData - { - public FusionConfigurationPublishingFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationPublishingSuccessData : IFusionConfigurationPublishingResultData - { - public FusionConfigurationPublishingSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationValidationFailedData : IFusionConfigurationPublishingResultData - { - public FusionConfigurationValidationFailedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionConfigurationValidationSuccessData : IFusionConfigurationPublishingResultData - { - public FusionConfigurationValidationSuccessData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionValidationResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionPublishResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionValidationResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionPublishResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionPublishResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionValidationResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionPublishResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionValidationResultData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record OperationInProgressData : ISchemaVersionValidationResultData, ISchemaVersionPublishResultData, IClientVersionValidationResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionPublishResultData, IMcpFeatureCollectionVersionValidationResultData - { - public OperationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskApprovedData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData - { - public ProcessingTaskApprovedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskIsQueuedData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData - { - public ProcessingTaskIsQueuedData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !, global::System.Int32? queuePosition = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - QueuePosition = queuePosition; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - public global::System.Int32? QueuePosition { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTaskIsReadyData : IFusionConfigurationPublishingResultData, IClientVersionPublishResultData, ISchemaVersionPublishResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData - { - public ProcessingTaskIsReadyData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ValidationInProgressData : IFusionConfigurationPublishingResultData, IClientVersionValidationResultData, ISchemaVersionValidationResultData, IOpenApiCollectionVersionValidationResultData, IMcpFeatureCollectionVersionValidationResultData - { - public ValidationInProgressData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record WaitForApprovalData : ISchemaVersionPublishResultData, IClientVersionPublishResultData, IFusionConfigurationPublishingResultData, IOpenApiCollectionVersionPublishResultData, IMcpFeatureCollectionVersionPublishResultData - { - public WaitForApprovalData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? state = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - State = state; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.ProcessingState? State { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationPublishingErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionPublishErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IProcessingErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ConcurrentOperationErrorData : IUnpublishClientErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData, ISchemaVersionPublishErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionPublishErrorData, IProcessingErrorData - { - public ConcurrentOperationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface ISchemaVersionValidationErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IFusionConfigurationValidationErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidGraphQLSchemaErrorData : IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData - { - public InvalidGraphQLSchemaErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientVersionValidationErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionVersionValidationErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionVersionValidationErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ProcessingTimeoutErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData - { - public ProcessingTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ReadyTimeoutErrorData : IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationPublishingErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData - { - public ReadyTimeoutErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UnexpectedProcessingErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IClientVersionValidationErrorData, IClientVersionPublishErrorData, IFusionConfigurationPublishingErrorData, IFusionConfigurationValidationErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, IProcessingErrorData - { - public UnexpectedProcessingErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IMcpFeatureCollectionDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record McpFeatureCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IMcpFeatureCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IMcpFeatureCollectionVersionPublishErrorData, IMcpFeatureCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData - { - public McpFeatureCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IOpenApiCollectionDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record OpenApiCollectionValidationErrorData : IFusionConfigurationDeploymentErrorData, IOpenApiCollectionDeploymentErrorData, ISchemaDeploymentErrorData, IOpenApiCollectionVersionPublishErrorData, IOpenApiCollectionVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData - { - public OpenApiCollectionValidationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IClientDeploymentErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record PersistedQueryValidationErrorData : IClientDeploymentErrorData, IFusionConfigurationDeploymentErrorData, ISchemaDeploymentErrorData, IClientVersionPublishErrorData, IClientVersionValidationErrorData, ISchemaVersionPublishErrorData, ISchemaVersionValidationErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData - { - public PersistedQueryValidationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record SchemaVersionChangeViolationErrorData : ISchemaVersionValidationErrorData, ISchemaVersionPublishErrorData, IFusionConfigurationValidationErrorData, IProcessingErrorData - { - public SchemaVersionChangeViolationErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record UploadFusionSubgraphPayloadData - { - public UploadFusionSubgraphPayloadData(global::System.String __typename, global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? fusionSubgraphVersion = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - FusionSubgraphVersion = fusionSubgraphVersion; - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::ChilliCream.Nitro.Fusion.Transport.State.FusionSubgraphVersionData? FusionSubgraphVersion { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record FusionSubgraphVersionData - { - public FusionSubgraphVersionData(global::System.String __typename, global::System.String? id = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Id = id; - } - - public global::System.String __typename { get; init; } - public global::System.String? Id { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidFusionSourceSchemaArchiveErrorData : IUploadFusionSubgraphErrorData, IErrorData - { - public InvalidFusionSourceSchemaArchiveErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial interface IUpdateStagesErrorData - { - global::System.String __typename { get; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ApiNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, ICreateApiKeyErrorData, ICreateApiKeyForApiErrorData, ICreateClientErrorData, ICreateMcpFeatureCollectionErrorData, ICreateMockSchemaErrorData, ICreateOpenApiCollectionErrorData, IDeleteApiByIdErrorData, IForceDeleteStageByApiIdErrorData, IPublishSchemaErrorData, IUpdateApiSettingsErrorData, IUpdateStagesErrorData, IUploadFusionSubgraphErrorData, IUploadSchemaErrorData, IValidateSchemaErrorData, IErrorData - { - public ApiNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record DuplicatedTagErrorData : IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IErrorData - { - public DuplicatedTagErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record InvalidSourceMetadataInputErrorData : IBeginFusionConfigurationPublishErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUploadClientErrorData, IUploadFusionSubgraphErrorData, IUploadMcpFeatureCollectionErrorData, IUploadOpenApiCollectionErrorData, IUploadSchemaErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData - { - public InvalidSourceMetadataInputErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record BeginFusionConfigurationPublishPayloadData - { - public BeginFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.String? requestId = default !, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - RequestId = requestId; - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::System.String? RequestId { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record StageNotFoundErrorData : IBeginFusionConfigurationPublishErrorData, IForceDeleteStageByApiIdErrorData, IPublishClientErrorData, IPublishMcpFeatureCollectionErrorData, IPublishOpenApiCollectionErrorData, IPublishSchemaErrorData, IUnpublishClientErrorData, IUpdateStageCompositionSettingsErrorData, IUpdateStagesErrorData, IValidateClientErrorData, IValidateMcpFeatureCollectionErrorData, IValidateOpenApiCollectionErrorData, IValidateSchemaErrorData, IErrorData - { - public StageNotFoundErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record SubgraphInvalidErrorData : IBeginFusionConfigurationPublishErrorData, IErrorData - { - public SubgraphInvalidErrorData(global::System.String __typename, global::System.String? message = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Message = message; - } - - public global::System.String __typename { get; init; } - public global::System.String? Message { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CancelFusionConfigurationCompositionPayloadData - { - public CancelFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record ValidateFusionConfigurationCompositionPayloadData - { - public ValidateFusionConfigurationCompositionPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.DataTypeGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "0.0.0.0")] - internal partial record CommitFusionConfigurationPublishPayloadData - { - public CommitFusionConfigurationPublishPayloadData(global::System.String __typename, global::System.Collections.Generic.IReadOnlyList? errors = default !) - { - this.__typename = __typename ?? throw new global::System.ArgumentNullException(nameof(__typename)); - Errors = errors; - } - - public global::System.String __typename { get; init; } - public global::System.Collections.Generic.IReadOnlyList? Errors { get; init; } - } - - // StrawberryShake.CodeGeneration.CSharp.Generators.NoStoreAccessorGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal partial class FusionApiClientStoreAccessor : global::StrawberryShake.IStoreAccessor - { - public global::StrawberryShake.IOperationStore OperationStore => throw new global::System.NotSupportedException("OperationStore is not supported in store less mode"); - public global::StrawberryShake.IEntityStore EntityStore => throw new global::System.NotSupportedException("EntityStore is not supported in store less mode"); - public global::StrawberryShake.IEntityIdSerializer EntityIdSerializer => throw new global::System.NotSupportedException("EntityIdSerializer is not supported in store less mode"); - - public global::StrawberryShake.IOperationRequestFactory GetOperationRequestFactory(global::System.Type resultType) - { - throw new global::System.NotSupportedException("GetOperationRequestFactory is not supported in store less mode"); - } - - public global::StrawberryShake.IOperationResultDataFactory GetOperationResultDataFactory(global::System.Type resultType) - { - throw new global::System.NotSupportedException("GetOperationResultDataFactory is not supported in store less mode"); - } - } -} - -namespace Microsoft.Extensions.DependencyInjection -{ - // StrawberryShake.CodeGeneration.CSharp.Generators.DependencyInjectionGenerator - [global::System.CodeDom.Compiler.GeneratedCode("StrawberryShake", "11.0.0")] - internal static partial class FusionApiClientServiceCollectionExtensions - { - public static global::StrawberryShake.IClientBuilder AddFusionApiClient(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::StrawberryShake.ExecutionStrategy strategy = global::StrawberryShake.ExecutionStrategy.NetworkOnly) - { - var serviceCollection = new global::Microsoft.Extensions.DependencyInjection.ServiceCollection(); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => - { - ConfigureClientDefault(sp, serviceCollection, strategy); - return new ClientServiceProvider(global::Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(serviceCollection)); - }); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::ChilliCream.Nitro.Fusion.Transport.State.FusionApiClientStoreAccessor()); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp))); - return new global::StrawberryShake.ClientBuilder("FusionApiClient", services, serviceCollection); - } - - private static global::Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureClientDefault(global::System.IServiceProvider parentServices, global::Microsoft.Extensions.DependencyInjection.ServiceCollection services, global::StrawberryShake.ExecutionStrategy strategy = global::StrawberryShake.ExecutionStrategy.NetworkOnly) - { - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => - { - var clientFactory = global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(parentServices); - return new global::StrawberryShake.Transport.Http.HttpConnection(() => clientFactory.CreateClient("FusionApiClient")); - }); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => new global::StrawberryShake.Serialization.SerializerResolver(global::System.Linq.Enumerable.Concat(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(parentServices), global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ClaimFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.WatchFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSourceSchemaResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.UploadFusionSourceSchemaBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.BeginFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ReleaseFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ReleaseFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.ValidateFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionDeploymentResultFactory>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::ChilliCream.Nitro.Fusion.Transport.State.CommitFusionDeploymentBuilder>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, sp => new global::StrawberryShake.StorelessOperationExecutor(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp), () => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService>(sp))); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::StrawberryShake.Json.JsonResultPatcher>(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton(services, sp => global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(sp)); - return services; - } - - private sealed class ClientServiceProvider : System.IServiceProvider, System.IDisposable - { - private readonly System.IServiceProvider _provider; - public ClientServiceProvider(System.IServiceProvider provider) - { - _provider = provider; - } - - public object? GetService(System.Type serviceType) - { - return _provider.GetService(serviceType); - } - - public void Dispose() - { - if (_provider is System.IDisposable d) - { - d.Dispose(); - } - } - } - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs deleted file mode 100644 index 58ce0a09637..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/IFusionDeploymentWorkflow.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ChilliCream.Nitro.Fusion; - -/// -/// Reconciles Fusion source schema versions and publishes Fusion configurations. -/// -public interface IFusionDeploymentWorkflow -{ - /// - /// Downloads an exact immutable source schema version and computes its canonical content - /// identity. The caller owns a non-null result and must dispose it after use. - /// - Task DownloadSourceSchemaAsync( - FusionTarget target, - FusionSourceSchemaVersion source, - CancellationToken cancellationToken); - - /// - /// Ensures that an immutable source schema version exists with the expected content. - /// - Task ReconcileSourceSchemaAsync( - FusionTarget target, - FusionSourceSchemaUpload source, - CancellationToken cancellationToken); - - /// - /// Publishes a locally composed Fusion archive and waits for a verified terminal result. - /// - Task PublishAsync( - FusionPublicationRequest request, - ReadOnlyMemory fusionArchive, - CancellationToken cancellationToken); -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs deleted file mode 100644 index e8491e82ad1..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/NitroFusionServiceCollectionExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using ChilliCream.Nitro.Fusion.Transport; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace ChilliCream.Nitro.Fusion; - -/// -/// Registers the Nitro Fusion deployment workflow. -/// -public static class NitroFusionServiceCollectionExtensions -{ - /// - /// Adds the Nitro Fusion deployment workflow and its remote transport. - /// - public static IServiceCollection AddNitroFusionDeploymentWorkflow( - this IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - services.TryAddSingleton< - IFusionDeploymentTransportFactory, - FusionApiTransportFactory>(); - services.TryAddSingleton< - IFusionDeploymentWorkflow, - FusionDeploymentWorkflow>(); - return services; - } -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql deleted file mode 100644 index 50cb60a0e28..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Operations/FusionError.graphql +++ /dev/null @@ -1,3 +0,0 @@ -fragment FusionError on Error { - message -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md deleted file mode 100644 index d09ae0a5ef5..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# ChilliCream.Nitro.Fusion - -Provides the high-level workflow used to reconcile immutable Fusion source schema versions and -publish composed Fusion configurations to Nitro. - -```csharp -services.AddNitroFusionDeploymentWorkflow(); - -var workflow = services.BuildServiceProvider() - .GetRequiredService(); -``` - -The public API uses deployment DTOs only. Nitro's generated GraphQL management types remain -internal to this package. - -`FusionSourceSchemaContent.ComputeSha256Async` computes a normalized content identity. -`IFusionDeploymentWorkflow.DownloadSourceSchemaAsync` downloads an exact name and version, -validates the archive and settings name, then returns the archive bytes with their canonical -content identity. diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs deleted file mode 100644 index 81abb62f7ea..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/FusionApiTransportFactory.cs +++ /dev/null @@ -1,461 +0,0 @@ -using System.Buffers; -using System.Net; -using System.Net.Http.Headers; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.Extensions.DependencyInjection; -using StrawberryShake; - -namespace ChilliCream.Nitro.Fusion.Transport; - -internal sealed class FusionApiTransportFactory : IFusionDeploymentTransportFactory -{ - internal const int MaxSourceSchemaArchiveSize = 128_000_000; - - public ValueTask OpenAsync( - FusionTarget target, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var services = new ServiceCollection(); - services.AddHttpClient( - FusionApiClient.ClientName, - client => ConfigureClient(client, target)); - services.AddFusionApiClient(); - - var provider = services.BuildServiceProvider(); - var apiClient = provider.GetRequiredService(); - var httpClientFactory = provider.GetRequiredService(); - - return ValueTask.FromResult( - new FusionApiTransport( - target, - provider, - apiClient, - httpClientFactory.CreateClient(FusionApiClient.ClientName))); - } - - private static void ConfigureClient(HttpClient client, FusionTarget target) - { - client.BaseAddress = target.CloudUrl; - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Accept.Add( - new MediaTypeWithQualityHeaderValue("application/json")); - client.DefaultRequestHeaders.TryAddWithoutValidation("GraphQL-Preflight", "1"); - client.DefaultRequestHeaders.TryAddWithoutValidation( - "GraphQL-Client-Version", - GetVersion()); - client.DefaultRequestHeaders.TryAddWithoutValidation( - "ccc-agent", - $"Nitro Fusion/{GetVersion()}"); - client.DefaultRequestHeaders.TryAddWithoutValidation( - "CCC-api-key", - target.ApiKey); - client.DefaultRequestVersion = new Version(2, 0); - client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; - } - - private static string GetVersion() - { - var version = typeof(FusionApiTransportFactory).Assembly.GetName().Version!; - return new Version(version.Major, version.Minor, version.Build).ToString(); - } - - private sealed class FusionApiTransport( - FusionTarget target, - ServiceProvider provider, - IFusionApiClient apiClient, - HttpClient httpClient) - : IFusionDeploymentTransport - { - public async Task DownloadSourceSchemaAsync( - string name, - string version, - CancellationToken cancellationToken) - { - const string path = - "/api/v1/apis/{0}/fusion-subgraphs/{1}/versions/{2}/download"; - var requestUri = string.Format( - path, - Uri.EscapeDataString(target.ApiId), - Uri.EscapeDataString(name), - Uri.EscapeDataString(version)); - using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); - using var response = await httpClient.SendAsync( - request, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken); - - if (response.StatusCode is HttpStatusCode.NotFound) - { - return null; - } - - EnsureSuccess(response); - return await ReadSourceSchemaArchiveAsync( - response.Content, - MaxSourceSchemaArchiveSize, - cancellationToken); - } - - public async Task UploadSourceSchemaAsync( - string version, - string archivePath, - CancellationToken cancellationToken) - { - await using var archive = File.OpenRead(archivePath); - var input = new UploadFusionSubgraphInput - { - ApiId = target.ApiId, - Tag = version, - Archive = new Upload(archive, "source-schema.zip") - }; - var result = await apiClient.UploadFusionSourceSchema.ExecuteAsync( - input, - cancellationToken); - var data = EnsureData(result).UploadFusionSubgraph; - var errors = GetErrors(data.Errors); - - return new FusionRemoteCommandResult( - data.FusionSubgraphVersion is not null && errors.Count is 0, - data.Errors?.Any(e => e.__typename is "DuplicatedTagError") is true, - errors); - } - - public async Task BeginPublishAsync( - FusionPublicationRequest request, - CancellationToken cancellationToken) - { - var input = new BeginFusionConfigurationPublishInput - { - ApiId = target.ApiId, - StageName = request.Stage, - Tag = request.ConfigurationTag, - WaitForApproval = request.WaitForApproval, - Subgraphs = request.SourceSchemas - .Select(s => new FusionSubgraphVersionInput - { - Name = s.Name, - Tag = s.Version - }) - .ToArray() - }; - var result = await apiClient.BeginFusionDeployment.ExecuteAsync( - input, - cancellationToken); - var data = EnsureData(result).BeginFusionConfigurationPublish; - - return new FusionRemoteBeginResult( - data.RequestId, - GetErrors(data.Errors)); - } - - public async IAsyncEnumerable WatchPublishAsync( - string requestId, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - using var stopSignal = new ReplaySubject(1); - await using var registration = cancellationToken.Register( - () => - { - stopSignal.OnNext(Unit.Default); - stopSignal.OnCompleted(); - }); - var subscription = apiClient.WatchFusionDeployment - .Watch(requestId, ExecutionStrategy.NetworkOnly) - .TakeUntil(stopSignal); - - await foreach (var result in subscription.ToAsyncEnumerable()) - { - var value = EnsureData(result) - .OnFusionConfigurationPublishingTaskChanged; - yield return ToRemoteEvent(value); - } - } - - public async Task ClaimPublishAsync( - string requestId, - CancellationToken cancellationToken) - { - var result = await apiClient.ClaimFusionDeployment.ExecuteAsync( - new StartFusionConfigurationCompositionInput - { - RequestId = requestId - }, - cancellationToken); - var data = EnsureData(result).StartFusionConfigurationComposition; - return ToCommandResult(data.Errors); - } - - public async Task ReleasePublishAsync( - string requestId, - CancellationToken cancellationToken) - { - var result = await apiClient.ReleaseFusionDeployment.ExecuteAsync( - new CancelFusionConfigurationCompositionInput - { - RequestId = requestId - }, - cancellationToken); - var data = EnsureData(result).CancelFusionConfigurationComposition; - return ToCommandResult(data.Errors); - } - - public async Task ValidatePublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken) - { - await using var archiveStream = OpenRead(archive); - var result = await apiClient.ValidateFusionDeployment.ExecuteAsync( - new ValidateFusionConfigurationCompositionInput - { - RequestId = requestId, - Configuration = new Upload(archiveStream, "gateway.far") - }, - cancellationToken); - var data = EnsureData(result).ValidateFusionConfigurationComposition; - return ToCommandResult(data.Errors); - } - - public async Task CommitPublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken) - { - await using var archiveStream = OpenRead(archive); - var result = await apiClient.CommitFusionDeployment.ExecuteAsync( - new CommitFusionConfigurationPublishInput - { - RequestId = requestId, - Configuration = new Upload(archiveStream, "gateway.far") - }, - cancellationToken); - var data = EnsureData(result).CommitFusionConfigurationPublish; - return ToCommandResult(data.Errors); - } - - private static Stream OpenRead(ReadOnlyMemory content) - { - if (MemoryMarshal.TryGetArray( - content, - out ArraySegment segment)) - { - return new MemoryStream( - segment.Array!, - segment.Offset, - segment.Count, - writable: false, - publiclyVisible: true); - } - - return new MemoryStream(content.ToArray(), writable: false); - } - - public async ValueTask DisposeAsync() - { - httpClient.Dispose(); - await provider.DisposeAsync(); - } - - private static FusionRemoteCommandResult ToCommandResult( - IReadOnlyList? errors) - where TError : class - { - var messages = GetErrors(errors); - return new FusionRemoteCommandResult( - messages.Count is 0, - false, - messages); - } - - private static List GetErrors( - IReadOnlyList? errors) - where TError : class - => errors? - .Select(error => error as IFusionError) - .Where(error => error is not null) - .Select(error => error!.Message) - .ToList() - ?? []; - - private static FusionRemoteEvent ToRemoteEvent( - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged value) - { - var errors = value switch - { - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationPublishingFailed failed - => failed.Errors.Select(error => error.Message).ToArray(), - IWatchFusionDeployment_OnFusionConfigurationPublishingTaskChanged_FusionConfigurationValidationFailed failed - => failed.Errors.Select(error => error.Message).ToArray(), - _ => [] - }; - var kind = value.__typename switch - { - "ProcessingTaskIsReady" => FusionRemoteEventKind.Ready, - "ProcessingTaskIsQueued" => FusionRemoteEventKind.Queued, - "FusionConfigurationValidationSuccess" - => FusionRemoteEventKind.ValidationSucceeded, - "FusionConfigurationValidationFailed" - => FusionRemoteEventKind.ValidationFailed, - "FusionConfigurationPublishingSuccess" - => FusionRemoteEventKind.PublishingSucceeded, - "FusionConfigurationPublishingFailed" - => FusionRemoteEventKind.PublishingFailed, - "WaitForApproval" => FusionRemoteEventKind.WaitingForApproval, - "ProcessingTaskApproved" => FusionRemoteEventKind.Approved, - "OperationInProgress" or "ValidationInProgress" - => FusionRemoteEventKind.InProgress, - _ => FusionRemoteEventKind.Unknown - }; - - return new FusionRemoteEvent(kind, errors); - } - - private static TData EnsureData(IOperationResult result) - where TData : class - { - if (result.Errors is { Count: > 0 } errors) - { - throw new FusionDeploymentException( - $"Nitro GraphQL request failed: {errors[0].Message}"); - } - - return result.Data - ?? throw new FusionDeploymentException( - "Nitro GraphQL request returned no data."); - } - - private static void EnsureSuccess(HttpResponseMessage response) - { - if (response.StatusCode is HttpStatusCode.Unauthorized - or HttpStatusCode.Forbidden) - { - throw new FusionDeploymentException( - "Nitro rejected the supplied API key."); - } - - if (!response.IsSuccessStatusCode) - { - throw new FusionDeploymentException( - $"Nitro returned HTTP {(int)response.StatusCode} " - + $"({response.ReasonPhrase})."); - } - } - } - - internal static Task ReadSourceSchemaArchiveAsync( - HttpContent content, - int maxArchiveSize, - CancellationToken cancellationToken) - => ReadSourceSchemaArchiveAsync( - content, - maxArchiveSize, - ArrayPool.Shared, - 81920, - cancellationToken); - - internal static async Task ReadSourceSchemaArchiveAsync( - HttpContent content, - int maxArchiveSize, - ArrayPool bufferPool, - int bufferSize, - CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(content); - ArgumentNullException.ThrowIfNull(bufferPool); - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArchiveSize); - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize); - - if (content.Headers.ContentLength is > 0 and var contentLength - && contentLength > maxArchiveSize) - { - throw ArchiveResponseTooLarge(maxArchiveSize); - } - - await using var stream = await content.ReadAsStreamAsync(cancellationToken); - var chunks = new List<(byte[] Buffer, int Length)>(); - var length = 0; - - try - { - var endOfStream = false; - while (!endOfStream) - { - var remaining = maxArchiveSize - length; - var readSize = remaining >= bufferSize - ? bufferSize - : remaining + 1; - var buffer = bufferPool.Rent(readSize); - var bufferLength = 0; - var retained = false; - - try - { - while (bufferLength < readSize) - { - var bytesRead = await stream.ReadAsync( - buffer.AsMemory(bufferLength, readSize - bufferLength), - cancellationToken); - - if (bytesRead is 0) - { - endOfStream = true; - break; - } - - bufferLength += bytesRead; - } - - if (bufferLength is 0) - { - continue; - } - - if (bufferLength > maxArchiveSize - length) - { - throw ArchiveResponseTooLarge(maxArchiveSize); - } - - length += bufferLength; - chunks.Add((buffer, bufferLength)); - retained = true; - } - finally - { - if (!retained) - { - bufferPool.Return(buffer, clearArray: true); - } - } - } - - var archive = GC.AllocateUninitializedArray(length); - var offset = 0; - foreach (var chunk in chunks) - { - chunk.Buffer.AsSpan(0, chunk.Length).CopyTo(archive.AsSpan(offset)); - offset += chunk.Length; - } - - return archive; - } - finally - { - foreach (var chunk in chunks) - { - bufferPool.Return(chunk.Buffer, clearArray: true); - } - } - } - - private static FusionDeploymentException ArchiveResponseTooLarge( - int maxArchiveSize) - => new( - "The compressed Fusion source schema archive exceeds the maximum " - + $"allowed size of {maxArchiveSize} bytes."); -} diff --git a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs b/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs deleted file mode 100644 index 5a22f5792ed..00000000000 --- a/src/Nitro/Common/src/ChilliCream.Nitro.Fusion/Transport/IFusionDeploymentTransport.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace ChilliCream.Nitro.Fusion.Transport; - -internal interface IFusionDeploymentTransportFactory -{ - ValueTask OpenAsync( - FusionTarget target, - CancellationToken cancellationToken); -} - -internal interface IFusionDeploymentTransport : IAsyncDisposable -{ - // The caller owns a non-null returned buffer and must clear it after use. - Task DownloadSourceSchemaAsync( - string name, - string version, - CancellationToken cancellationToken); - - Task UploadSourceSchemaAsync( - string version, - string archivePath, - CancellationToken cancellationToken); - - Task BeginPublishAsync( - FusionPublicationRequest request, - CancellationToken cancellationToken); - - IAsyncEnumerable WatchPublishAsync( - string requestId, - CancellationToken cancellationToken); - - Task ClaimPublishAsync( - string requestId, - CancellationToken cancellationToken); - - Task ReleasePublishAsync( - string requestId, - CancellationToken cancellationToken); - - Task ValidatePublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken); - - Task CommitPublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken); -} - -internal sealed record FusionRemoteCommandResult( - bool Succeeded, - bool IsDuplicate, - IReadOnlyList Errors) -{ - public static FusionRemoteCommandResult Success { get; } = - new(true, false, []); -} - -internal sealed record FusionRemoteBeginResult( - string? RequestId, - IReadOnlyList Errors); - -internal sealed record FusionRemoteEvent( - FusionRemoteEventKind Kind, - IReadOnlyList Errors); - -internal enum FusionRemoteEventKind -{ - Ready, - Queued, - ValidationSucceeded, - ValidationFailed, - PublishingSucceeded, - PublishingFailed, - InProgress, - WaitingForApproval, - Approved, - Unknown -} diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj b/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj deleted file mode 100644 index dd44a20ace0..00000000000 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Aspire.Tests/ChilliCream.Nitro.Aspire.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - ChilliCream.Nitro.Aspire.Tests - ChilliCream.Nitro.Aspire - net11.0;net10.0;net9.0 - - - - - - - diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj deleted file mode 100644 index e0e990035fd..00000000000 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/ChilliCream.Nitro.Fusion.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - ChilliCream.Nitro.Fusion.Tests - ChilliCream.Nitro.Fusion - net11.0;net10.0;net9.0 - - - - - - - diff --git a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs b/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs deleted file mode 100644 index 4b7ae155e97..00000000000 --- a/src/Nitro/Common/test/ChilliCream.Nitro.Fusion.Tests/FusionDeploymentWorkflowTests.cs +++ /dev/null @@ -1,699 +0,0 @@ -using System.IO.Compression; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using ChilliCream.Nitro.Fusion.Transport; -using HotChocolate.Fusion.SourceSchema.Packaging; - -namespace ChilliCream.Nitro.Fusion; - -public sealed class FusionDeploymentWorkflowTests -{ - [Fact] - public void Assembly_Should_ExposeOnlyWorkflowContract_When_Inspected() - { - var exportedTypes = typeof(IFusionDeploymentWorkflow) - .Assembly - .GetExportedTypes() - .Select(type => type.FullName) - .Order(StringComparer.Ordinal) - .ToArray(); - - string.Join(Environment.NewLine, exportedTypes) - .MatchInlineSnapshot( - """ - ChilliCream.Nitro.Fusion.FusionDeploymentException - ChilliCream.Nitro.Fusion.FusionIdentityCollisionException - ChilliCream.Nitro.Fusion.FusionIndeterminateStateException - ChilliCream.Nitro.Fusion.FusionPublicationRequest - ChilliCream.Nitro.Fusion.FusionSourceSchemaContent - ChilliCream.Nitro.Fusion.FusionSourceSchemaDownload - ChilliCream.Nitro.Fusion.FusionSourceSchemaUpload - ChilliCream.Nitro.Fusion.FusionSourceSchemaVersion - ChilliCream.Nitro.Fusion.FusionTarget - ChilliCream.Nitro.Fusion.IFusionDeploymentWorkflow - ChilliCream.Nitro.Fusion.NitroFusionServiceCollectionExtensions - """); - } - - [Fact] - public void PublishAsync_Should_AcceptFusionArchiveFromMemory_When_Inspected() - { - typeof(IFusionDeploymentWorkflow) - .GetMethod(nameof(IFusionDeploymentWorkflow.PublishAsync))! - .ToString() - .MatchInlineSnapshot( - """ - System.Threading.Tasks.Task PublishAsync(ChilliCream.Nitro.Fusion.FusionPublicationRequest, System.ReadOnlyMemory`1[System.Byte], System.Threading.CancellationToken) - """); - } - - [Fact] - public void Dispose_Should_ClearOwnedArchiveAndRejectAccess_When_Called() - { - byte[] archive = [1, 2, 3]; - var download = new FusionSourceSchemaDownload( - "products", - "20260730", - archive, - new string('A', 64)); - - Assert.Equal(new byte[] { 1, 2, 3 }, download.Archive.ToArray()); - - download.Dispose(); - - Assert.Equal(new byte[3], archive); - Assert.Throws(() => download.Archive); - - archive[0] = 9; - download.Dispose(); - Assert.Equal(new byte[] { 9, 0, 0 }, archive); - } - - [Fact] - public async Task ComputeSha256Async_Should_ReturnSameDigest_When_ContentIsNormalized() - { - var directory = CreateTemporaryDirectory(); - try - { - var firstPath = Path.Combine(directory, "first.fss"); - var secondPath = Path.Combine(directory, "second.fss"); - await CreateArchiveAsync( - firstPath, - "products", - "type Query { product: String }", - """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); - await CreateArchiveAsync( - secondPath, - "products", - """ - type Query { - product: String - } - """, - """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); - - var first = await FusionSourceSchemaContent.ComputeSha256Async( - firstPath, - "products", - TestContext.Current.CancellationToken); - var second = await FusionSourceSchemaContent.ComputeSha256Async( - secondPath, - "products", - TestContext.Current.CancellationToken); - - Assert.Equal(first, second); - first.MatchInlineSnapshot( - """ - 3B7C4FBDBEC6ECB8C072794A67BD1CB8B4DCFB5153B6D284E662A0D3CB49EC08 - """); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task DownloadSourceSchemaAsync_Should_ReturnArchiveAndDigest_When_VersionExists() - { - var directory = CreateTemporaryDirectory(); - try - { - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - remotePath, - "products", - """ - type Query { - product: String - } - """, - """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); - var remoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken); - var transport = new FakeTransport - { - RemoteArchive = remoteArchive - }; - var workflow = CreateWorkflow(transport); - - using var result = await workflow.DownloadSourceSchemaAsync( - CreateTarget(), - new FusionSourceSchemaVersion("products", "20260730"), - TestContext.Current.CancellationToken); - - Assert.NotNull(result); - Assert.Equal("products", result.Name); - Assert.Equal("20260730", result.Version); - Assert.Equal( - "3B7C4FBDBEC6ECB8C072794A67BD1CB8B4DCFB5153B6D284E662A0D3CB49EC08", - result.ContentSha256); - Assert.Equal(remoteArchive, result.Archive.ToArray()); - Assert.Equal("products", transport.LastDownloadName); - Assert.Equal("20260730", transport.LastDownloadVersion); - - var ownedArchive = Assert.Single(transport.ReturnedArchives); - result.Dispose(); - Assert.Equal(new byte[ownedArchive.Length], ownedArchive); - Assert.Throws(() => result.Archive); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task DownloadSourceSchemaAsync_Should_ReturnNull_When_VersionDoesNotExist() - { - var transport = new FakeTransport(); - var workflow = CreateWorkflow(transport); - - var result = await workflow.DownloadSourceSchemaAsync( - CreateTarget(), - new FusionSourceSchemaVersion("products", "missing"), - TestContext.Current.CancellationToken); - - Assert.Null(result); - Assert.Equal(1, transport.DownloadCount); - Assert.Equal("products", transport.LastDownloadName); - Assert.Equal("missing", transport.LastDownloadVersion); - } - - [Fact] - public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_NameDiffers() - { - var directory = CreateTemporaryDirectory(); - try - { - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - remotePath, - "inventory", - "type Query { product: String }", - """{"name":"inventory"}"""); - var transport = new FakeTransport - { - RemoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken) - }; - var workflow = CreateWorkflow(transport); - - var exception = await Assert.ThrowsAsync( - () => workflow.DownloadSourceSchemaAsync( - CreateTarget(), - new FusionSourceSchemaVersion("products", "20260730"), - TestContext.Current.CancellationToken)); - - Assert.Equal( - "The source schema settings name must exactly match 'products'.", - exception.Message); - var returnedArchive = Assert.Single(transport.ReturnedArchives); - Assert.Equal(new byte[returnedArchive.Length], returnedArchive); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task DownloadSourceSchemaAsync_Should_RejectArchive_When_DecompressedEntryIsTooLarge() - { - var directory = CreateTemporaryDirectory(); - try - { - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateOversizedSettingsArchiveAsync(remotePath); - var transport = new FakeTransport - { - RemoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken) - }; - var workflow = CreateWorkflow(transport); - - var exception = await Assert.ThrowsAsync( - () => workflow.DownloadSourceSchemaAsync( - CreateTarget(), - new FusionSourceSchemaVersion("products", "20260730"), - TestContext.Current.CancellationToken)); - - Assert.Equal( - "The Fusion source schema archive is invalid.", - exception.Message); - Assert.IsType(exception.InnerException); - var returnedArchive = Assert.Single(transport.ReturnedArchives); - Assert.Equal(new byte[returnedArchive.Length], returnedArchive); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task ReconcileSourceSchemaAsync_Should_NoOp_When_NormalizedContentMatches() - { - var directory = CreateTemporaryDirectory(); - try - { - var localPath = Path.Combine(directory, "local.fss"); - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - localPath, - "products", - "type Query { product: String }", - """{"name":"products","transports":{"http":{"url":"https://example.com"}}}"""); - await CreateArchiveAsync( - remotePath, - "products", - """ - type Query { - product: String - } - """, - """{"transports":{"http":{"url":"https://example.com"}},"name":"products"}"""); - var transport = new FakeTransport - { - RemoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken) - }; - var workflow = CreateWorkflow(transport); - - await workflow.ReconcileSourceSchemaAsync( - CreateTarget(), - await CreateUploadAsync(localPath), - TestContext.Current.CancellationToken); - - Assert.Equal(0, transport.UploadCount); - Assert.Equal(1, transport.DownloadCount); - var returnedArchive = Assert.Single(transport.ReturnedArchives); - Assert.Equal(new byte[returnedArchive.Length], returnedArchive); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task ReconcileSourceSchemaAsync_Should_ThrowCollision_When_ContentDiffers() - { - var directory = CreateTemporaryDirectory(); - try - { - var localPath = Path.Combine(directory, "local.fss"); - var remotePath = Path.Combine(directory, "remote.fss"); - await CreateArchiveAsync( - localPath, - "products", - "type Query { product: String }", - """{"name":"products"}"""); - await CreateArchiveAsync( - remotePath, - "products", - "type Query { product: Int }", - """{"name":"products"}"""); - var transport = new FakeTransport - { - RemoteArchive = await File.ReadAllBytesAsync( - remotePath, - TestContext.Current.CancellationToken) - }; - var workflow = CreateWorkflow(transport); - var upload = await CreateUploadAsync(localPath); - - var exception = await Assert.ThrowsAsync( - () => workflow.ReconcileSourceSchemaAsync( - CreateTarget(), - upload, - TestContext.Current.CancellationToken)); - - Assert.Equal( - "Source schema 'products' version '20260730' already exists " - + "with different normalized schema, settings, or extensions.", - exception.Message); - Assert.Equal(0, transport.UploadCount); - var returnedArchive = Assert.Single(transport.ReturnedArchives); - Assert.Equal(new byte[returnedArchive.Length], returnedArchive); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task ReconcileSourceSchemaAsync_Should_VerifyReadBack_When_UploadIsUncertain() - { - var directory = CreateTemporaryDirectory(); - try - { - var archivePath = Path.Combine(directory, "products.fss"); - await CreateArchiveAsync( - archivePath, - "products", - "type Query { product: String }", - """{"name":"products"}"""); - var archive = await File.ReadAllBytesAsync( - archivePath, - TestContext.Current.CancellationToken); - var transport = new FakeTransport - { - UploadException = new IOException("Connection reset."), - RemoteArchiveAfterUpload = archive - }; - var workflow = CreateWorkflow(transport); - - await workflow.ReconcileSourceSchemaAsync( - CreateTarget(), - await CreateUploadAsync(archivePath), - TestContext.Current.CancellationToken); - - Assert.Equal(1, transport.UploadCount); - Assert.Equal(2, transport.DownloadCount); - } - finally - { - Directory.Delete(directory, recursive: true); - } - } - - [Fact] - public async Task PublishAsync_Should_Commit_When_ValidationSucceeds() - { - var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); - var transport = new FakeTransport - { - Events = - [ - new(FusionRemoteEventKind.Ready, []), - new(FusionRemoteEventKind.ValidationSucceeded, []), - new(FusionRemoteEventKind.PublishingSucceeded, []) - ] - }; - var workflow = CreateWorkflow(transport); - - await workflow.PublishAsync( - CreatePublicationRequest(force: false), - fusionArchive, - TestContext.Current.CancellationToken); - - Assert.Equal( - ["begin", "watch", "claim", "validate", "commit"], - transport.Calls); - Assert.Equal(fusionArchive, transport.ValidatedArchive); - Assert.Equal(fusionArchive, transport.CommittedArchive); - } - - [Fact] - public async Task PublishAsync_Should_ReleaseAndFail_When_ValidationFailsWithoutForce() - { - var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); - var transport = new FakeTransport - { - Events = - [ - new(FusionRemoteEventKind.Ready, []), - new( - FusionRemoteEventKind.ValidationFailed, - ["Breaking schema change."]) - ] - }; - var workflow = CreateWorkflow(transport); - - var exception = await Assert.ThrowsAsync( - () => workflow.PublishAsync( - CreatePublicationRequest(force: false), - fusionArchive, - TestContext.Current.CancellationToken)); - - Assert.Equal( - "Nitro rejected the Fusion configuration validation. " - + "Breaking schema change.", - exception.Message); - Assert.Equal( - ["begin", "watch", "claim", "validate", "release"], - transport.Calls); - } - - [Fact] - public async Task PublishAsync_Should_Commit_When_ValidationFailsWithForce() - { - var fusionArchive = Encoding.UTF8.GetBytes("fusion archive"); - var transport = new FakeTransport - { - Events = - [ - new( - FusionRemoteEventKind.Ready, - []), - new( - FusionRemoteEventKind.ValidationFailed, - ["Breaking schema change."]), - new( - FusionRemoteEventKind.PublishingSucceeded, - []) - ] - }; - var workflow = CreateWorkflow(transport); - - await workflow.PublishAsync( - CreatePublicationRequest(force: true), - fusionArchive, - TestContext.Current.CancellationToken); - - Assert.Equal( - ["begin", "watch", "claim", "validate", "commit"], - transport.Calls); - } - - private static FusionDeploymentWorkflow CreateWorkflow( - FakeTransport transport) - => new(new FakeTransportFactory(transport)); - - private static FusionTarget CreateTarget() - => new( - new Uri("https://api.chillicream.com"), - "api-id", - "secret"); - - private static FusionPublicationRequest CreatePublicationRequest(bool force) - => new( - CreateTarget(), - "production", - "20260730", - [new FusionSourceSchemaVersion("products", "20260730")], - WaitForApproval: false, - Force: force, - OperationTimeout: TimeSpan.FromMinutes(1), - ApprovalTimeout: TimeSpan.FromMinutes(1)); - - private static async Task CreateUploadAsync( - string archivePath) - { - await using var stream = File.OpenRead(archivePath); - var sha256 = Convert.ToHexString( - await SHA256.HashDataAsync( - stream, - TestContext.Current.CancellationToken)); - return new FusionSourceSchemaUpload( - "products", - "20260730", - archivePath, - sha256); - } - - private static async Task CreateArchiveAsync( - string path, - string name, - string schema, - string settings) - { - using var archive = FusionSourceSchemaArchive.Create(path); - await archive.SetArchiveMetadataAsync( - new ArchiveMetadata(), - TestContext.Current.CancellationToken); - await archive.SetSchemaAsync( - Encoding.UTF8.GetBytes(schema), - TestContext.Current.CancellationToken); - using var settingsDocument = JsonDocument.Parse(settings); - await archive.SetSettingsAsync( - settingsDocument, - TestContext.Current.CancellationToken); - await archive.CommitAsync(TestContext.Current.CancellationToken); - } - - private static async Task CreateOversizedSettingsArchiveAsync(string path) - { - await using var stream = File.Create(path); -#if NET10_0_OR_GREATER - await using var archive = new ZipArchive( -#else - using var archive = new ZipArchive( -#endif - stream, - ZipArchiveMode.Create, - leaveOpen: true); - await using (var schema = archive.CreateEntry("schema.graphqls").Open()) - { - await schema.WriteAsync( - Encoding.UTF8.GetBytes("type Query { product: String }"), - TestContext.Current.CancellationToken); - } - - await using (var settings = - archive.CreateEntry("schema-settings.json").Open()) - { - await settings.WriteAsync( - Encoding.UTF8.GetBytes( - $$"""{"name":"products","padding":"{{new string('a', 512_000)}}"}"""), - TestContext.Current.CancellationToken); - } - } - - private static string CreateTemporaryDirectory() - { - var path = Path.Combine( - Path.GetTempPath(), - "nitro-fusion-tests", - Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } - - private sealed class FakeTransportFactory(FakeTransport transport) - : IFusionDeploymentTransportFactory - { - public ValueTask OpenAsync( - FusionTarget target, - CancellationToken cancellationToken) - => ValueTask.FromResult(transport); - } - - private sealed class FakeTransport : IFusionDeploymentTransport - { - public byte[]? RemoteArchive { get; set; } - - public byte[]? RemoteArchiveAfterUpload { get; set; } - - public Exception? UploadException { get; set; } - - public IReadOnlyList Events { get; init; } = []; - - public int DownloadCount { get; private set; } - - public int UploadCount { get; private set; } - - public string? LastDownloadName { get; private set; } - - public string? LastDownloadVersion { get; private set; } - - public List ReturnedArchives { get; } = []; - - public List Calls { get; } = []; - - public byte[]? ValidatedArchive { get; private set; } - - public byte[]? CommittedArchive { get; private set; } - - public Task DownloadSourceSchemaAsync( - string name, - string version, - CancellationToken cancellationToken) - { - DownloadCount++; - LastDownloadName = name; - LastDownloadVersion = version; - var archive = DownloadCount > 1 && RemoteArchiveAfterUpload is not null - ? RemoteArchiveAfterUpload - : RemoteArchive; - if (archive is null) - { - return Task.FromResult(null); - } - - var ownedArchive = archive.ToArray(); - ReturnedArchives.Add(ownedArchive); - return Task.FromResult(ownedArchive); - } - - public Task UploadSourceSchemaAsync( - string version, - string archivePath, - CancellationToken cancellationToken) - { - UploadCount++; - if (UploadException is not null) - { - throw UploadException; - } - - return Task.FromResult(FusionRemoteCommandResult.Success); - } - - public Task BeginPublishAsync( - FusionPublicationRequest request, - CancellationToken cancellationToken) - { - Calls.Add("begin"); - return Task.FromResult( - new FusionRemoteBeginResult("request-id", [])); - } - - public async IAsyncEnumerable WatchPublishAsync( - string requestId, - [EnumeratorCancellation] CancellationToken cancellationToken) - { - Calls.Add("watch"); - foreach (var @event in Events) - { - cancellationToken.ThrowIfCancellationRequested(); - yield return @event; - await Task.Yield(); - } - } - - public Task ClaimPublishAsync( - string requestId, - CancellationToken cancellationToken) - => Success("claim"); - - public Task ReleasePublishAsync( - string requestId, - CancellationToken cancellationToken) - => Success("release"); - - public Task ValidatePublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken) - { - ValidatedArchive = archive.ToArray(); - return Success("validate"); - } - - public Task CommitPublishAsync( - string requestId, - ReadOnlyMemory archive, - CancellationToken cancellationToken) - { - CommittedArchive = archive.ToArray(); - return Success("commit"); - } - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - - private Task Success(string call) - { - Calls.Add(call); - return Task.FromResult(FusionRemoteCommandResult.Success); - } - } -} From bc949aa0c45926ea77b9d282f6bd1aa45b3aeac9 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:06:22 +0200 Subject: [PATCH 08/11] Cleanup --- .../.github/workflows/deploy.yml | 7 +- examples/FusionReleasePipeline/README.md | 29 ++-- .../src/AppHost/Program.cs | 49 ++----- .../src/Fusion.Aspire/BoundedMemoryStream.cs | 94 ------------ .../src/Fusion.Aspire/FUSION_PUBLISHING.md | 29 +++- .../FUSION_PUBLISH_DEPLOY_DESIGN.md | 15 +- .../Fusion.Aspire/FusionDeploymentResource.cs | 5 +- .../src/Fusion.Aspire/FusionPipeline.cs | 26 ++-- .../Fusion.Aspire/FusionPipelineExecutor.cs | 75 +++++++--- .../FusionPipelineMemoryLimits.cs | 14 ++ .../src/Fusion.Aspire/NitroExtensions.cs | 34 ++++- .../FusionPipelineTests.cs | 135 ++++++++++++++---- .../FusionReleaseAcceptanceTests.cs | 89 +++++++++++- 13 files changed, 376 insertions(+), 225 deletions(-) delete mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineMemoryLimits.cs diff --git a/examples/FusionReleasePipeline/.github/workflows/deploy.yml b/examples/FusionReleasePipeline/.github/workflows/deploy.yml index c8016312279..078621064fc 100644 --- a/examples/FusionReleasePipeline/.github/workflows/deploy.yml +++ b/examples/FusionReleasePipeline/.github/workflows/deploy.yml @@ -24,6 +24,7 @@ jobs: group: fusion-nitro-example-invalid-replace-with-nitro-api-id-upload cancel-in-progress: false env: + Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} steps: @@ -60,13 +61,14 @@ jobs: contents: read id-token: write concurrency: - group: fusion-nitro-example-invalid-replace-with-nitro-api-id-replace-with-development-stage + group: fusion-nitro-example-invalid-replace-with-nitro-api-id-development cancel-in-progress: false env: Azure__CredentialSource: AzureCli Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} @@ -110,13 +112,14 @@ jobs: contents: read id-token: write concurrency: - group: fusion-nitro-example-invalid-replace-with-nitro-api-id-replace-with-test-stage + group: fusion-nitro-example-invalid-replace-with-nitro-api-id-test cancel-in-progress: false env: Azure__CredentialSource: AzureCli Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} + Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} diff --git a/examples/FusionReleasePipeline/README.md b/examples/FusionReleasePipeline/README.md index 95a35f7e960..a142c918d71 100644 --- a/examples/FusionReleasePipeline/README.md +++ b/examples/FusionReleasePipeline/README.md @@ -11,9 +11,7 @@ The AppHost and services reference the current `graphql-platform` checkout. Chec Every remote value is intentionally fake. Replace: - `https://nitro.example.invalid`; -- `replace-with-nitro-api-id`; -- `replace-with-development-stage`; -- `replace-with-test-stage`; and +- `replace-with-nitro-api-id`; and - the Development and Test source URLs in both `schema-settings.json` files. Provide `DEMO_NITRO_API_KEY` as a repository or organization secret for the upload job. Create the @@ -24,7 +22,10 @@ GitHub environments `Development` and `Test`, then configure these environment s - `DEMO_AZURE_TENANT_ID`; and - `DEMO_AZURE_SUBSCRIPTION_ID`. -Configure `DEMO_AZURE_LOCATION` and `DEMO_AZURE_RESOURCE_GROUP` as environment variables. The +Configure `DEMO_NITRO_STAGE`, `DEMO_AZURE_LOCATION`, and `DEMO_AZURE_RESOURCE_GROUP` as environment +variables. `DEMO_NITRO_STAGE` carries the Nitro stage of each GitHub environment and reaches the +AppHost as `Parameters__stage`, so the AppHost declares one deployment instead of one per stage. The +upload job runs outside a GitHub environment and reads the repository-level `DEMO_NITRO_STAGE`. The sample uses Azure Container Apps because it contributes the `DeployCompute` steps needed to prove source and gateway ordering. Development and Test should normally use distinct resource groups. @@ -51,15 +52,17 @@ settings environment and starts: - products at `http://localhost:5101/graphql`; and - reviews at `http://localhost:5102/graphql`. -The AppHost derives the gateway Nitro stage from the already selected composition environment. -Run mode maps to `local`, so it injects no `NITRO_*` variables and the gateway uses its local FAR. +Run mode composes for `local` and injects no `NITRO_*` variables, so the gateway uses its local FAR. +A publish composes for the stage that `Parameters__stage` supplies and passes that same stage to the +gateway as `NITRO_STAGE`. ## Release flow -All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`. The build job selects -the real Development declaration and uploads both sources as exact immutable versions: +All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`, and supply the target +stage as `Parameters__stage`. The build job uploads both sources as exact immutable versions: ```shell +export Parameters__stage="$DEMO_NITRO_STAGE" export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" @@ -76,6 +79,7 @@ distinct target using a matching selected environment. The deployment job checks out the same revision and publishes without a manifest or CI artifact: ```shell +export Parameters__stage="$DEMO_NITRO_STAGE" export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" export Parameters__nitroGatewayApiKey="$DEMO_NITRO_GATEWAY_API_KEY" @@ -95,7 +99,8 @@ versions `products@RELEASE_TAG` and `reviews@RELEASE_TAG` as a metadata-only pre deployment it downloads them again, verifies the same canonical digests, and composes the Development endpoints. -The Test job uses the same tag with `--environment Test`, which composes the Test endpoints. +The Test job uses the same tag with `--environment Test` and the Test `DEMO_NITRO_STAGE`, which +composes the Test endpoints and publishes to the Test stage. There is no artifact upload/download between jobs. The Fusion-specific publish steps never export schemas, upload source versions, write Fusion apply-state files, or resolve Aspire's output-path @@ -106,6 +111,6 @@ deployment, exact re-download and composition, source readiness, internal Nitro gateway deployment, then terminal public `fusion-publish`. The build job has a stable concurrency key for the Nitro API. Each deployment job has a stable key -for its Nitro stage and Azure target. `cancel-in-progress: false` queues writers. Add external -locking when another repository or deployment system can write the same Nitro stage or compute -target. +for its GitHub environment, which is the one stage and Azure target that environment writes. +`cancel-in-progress: false` queues writers. Add external locking when another repository or +deployment system can write the same Nitro stage or compute target. diff --git a/examples/FusionReleasePipeline/src/AppHost/Program.cs b/examples/FusionReleasePipeline/src/AppHost/Program.cs index 3936e8a9530..839ccdcbbdc 100644 --- a/examples/FusionReleasePipeline/src/AppHost/Program.cs +++ b/examples/FusionReleasePipeline/src/AppHost/Program.cs @@ -2,22 +2,17 @@ const string nitroCloudUrl = "https://nitro.example.invalid"; const string nitroApiId = "replace-with-nitro-api-id"; -const string developmentStage = "replace-with-development-stage"; -const string testStage = "replace-with-test-stage"; var builder = DistributedApplication.CreateBuilder(args); builder.AddNitro(); builder.AddAzureContainerAppEnvironment("demo-aca"); -var compositionEnvironment = builder.ExecutionContext.IsRunMode - ? "local" - : builder.Environment.EnvironmentName switch - { - "Development" => "development", - "Test" => "test", - _ => "local" - }; +// the stage and the release tag are supplied per publish, so this AppHost declares one deployment +// that serves every stage instead of one declaration per environment. +var stage = builder.AddParameter("stage"); +var tag = builder.AddParameter("tag"); +var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); var products = builder .AddProject("products") @@ -35,42 +30,22 @@ .WithGraphQLSchemaComposition( settings: new GraphQLCompositionSettings { - EnvironmentName = compositionEnvironment + // a publish composes for the stage it publishes to, a local run composes for "local". + EnvironmentName = builder.ExecutionContext.IsRunMode ? "local" : null }) .WithReference(products) .WithReference(reviews); -var tag = builder.AddParameter("tag"); -var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); - -var nitro = builder +builder .AddNitroPublishTarget("nitro") .WithNitroCloudUrl(nitroCloudUrl) .WithNitroApiId(nitroApiId) - .WithNitroApiKey(nitroApiKey); - -nitro - .AddFusionDeployment("fusion-development") - .ForEnvironment("Development") - .ToStage(developmentStage) - .WithCompositionEnvironment("development") - .WithConfigurationTag(tag); - -nitro - .AddFusionDeployment("fusion-test") - .ForEnvironment("Test") - .ToStage(testStage) - .WithCompositionEnvironment("test") + .WithNitroApiKey(nitroApiKey) + .AddFusionDeployment("fusion") + .ToStage(stage) .WithConfigurationTag(tag); -var stage = compositionEnvironment switch -{ - "development" => developmentStage, - "test" => testStage, - _ => null -}; - -if (stage is not null) +if (!builder.ExecutionContext.IsRunMode) { var nitroGatewayApiKey = builder.AddParameter( "nitroGatewayApiKey", diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs deleted file mode 100644 index 9a0560c88d7..00000000000 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/BoundedMemoryStream.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace HotChocolate.Fusion.Aspire; - -internal sealed class BoundedMemoryStream(int maximumLength) - : MemoryStream -{ - public override void SetLength(long value) - { - EnsureCapacity(value); - base.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - EnsureWrite(count); - base.Write(buffer, offset, count); - } - - public override void Write(ReadOnlySpan buffer) - { - EnsureWrite(buffer.Length); - base.Write(buffer); - } - - public override Task WriteAsync( - byte[] buffer, - int offset, - int count, - CancellationToken cancellationToken) - { - EnsureWrite(count); - return base.WriteAsync( - buffer, - offset, - count, - cancellationToken); - } - - public override ValueTask WriteAsync( - ReadOnlyMemory buffer, - CancellationToken cancellationToken = default) - { - EnsureWrite(buffer.Length); - return base.WriteAsync(buffer, cancellationToken); - } - - public override void WriteByte(byte value) - { - EnsureWrite(1); - base.WriteByte(value); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - Array.Clear(GetBuffer()); - } - - base.Dispose(disposing); - } - - private void EnsureWrite(int count) - => EnsureCapacity(checked(Position + count)); - - private void ThrowLimitExceeded() - => throw new InvalidDataException( - "The composed Fusion archive exceeds the " - + $"{maximumLength:N0}-byte in-memory size limit."); - - private void EnsureCapacity(long length) - { - if (length > maximumLength) - { - ThrowLimitExceeded(); - } - } -} - -internal sealed record FusionPipelineMemoryLimits( - int SourceArchiveBytes, - long TotalSourceArchiveBytes, - int FusionArchiveBytes) -{ - public const int DefaultSourceArchiveBytes = 128_000_000; - - public const int DefaultTotalSourceArchiveBytes = 512_000_000; - - public const int DefaultFusionArchiveBytes = 256_000_000; - - public static FusionPipelineMemoryLimits Default { get; } = new( - DefaultSourceArchiveBytes, - DefaultTotalSourceArchiveBytes, - DefaultFusionArchiveBytes); -} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md index 2fe93700c2c..dd6a6dc3d39 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md @@ -13,10 +13,29 @@ Both invocations must evaluate the same AppHost composition and use the same `ta ## AppHost declaration +One declaration serves every stage when the stage is a parameter: + ```csharp +var stage = builder.AddParameter("stage"); var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); +builder.AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products-fusion") + .WithNitroApiKey(nitroApiKey) + .AddFusionDeployment("fusion") + .ToStage(stage) + .WithConfigurationTag(tag); +``` + +Each invocation then supplies the stage the same way it supplies the tag, for example as +`Parameters__stage`. + +Declare one deployment per environment instead when the stages differ in more than their name, for +example when only production waits for approval: + +```csharp var nitro = builder.AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") .WithNitroApiId("products-fusion") @@ -40,10 +59,12 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -`ForEnvironment` selects the Aspire invocation. `ToStage` selects the Nitro stage. -`WithCompositionEnvironment` selects the environment block in each source's -`schema-settings.json`. `WithConfigurationTag` supplies both the source version and Fusion -configuration tag. `WithApproval`, `WithForce`, and `WithTimeouts` configure the publication +`ForEnvironment` restricts a declaration to one Aspire invocation. A declaration without it +publishes in every environment. `ToStage` selects the Nitro stage, either as a literal or from a +parameter that each invocation supplies. `WithCompositionEnvironment` selects the environment block +in each source's `schema-settings.json`, and defaults to the composition environment of the AppHost +followed by the resolved stage name. `WithConfigurationTag` supplies both the source version and +Fusion configuration tag. `WithApproval`, `WithForce`, and `WithTimeouts` configure the publication itself. `WithNitroCloudUrl` and `WithNitroApiId` default to the `Nitro:CloudUrl` and `Nitro:ApiId` diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md index 7e503201707..7a9c5764560 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md @@ -63,12 +63,15 @@ nitro approval: TimeSpan.FromHours(2)); ``` -`ForEnvironment` selects the Aspire invocation. `ToStage` selects the Nitro stage. -`WithCompositionEnvironment` selects the source-settings environment. `WithConfigurationTag` -supplies the immutable source version and final configuration tag. +`ForEnvironment` restricts the declaration to one Aspire invocation. `ToStage` selects the Nitro +stage, either as a literal or from a parameter. `WithCompositionEnvironment` selects the +source-settings environment. `WithConfigurationTag` supplies the immutable source version and final +configuration tag. Multiple deployment declarations can map the same AppHost composition to different Aspire -environments or Nitro stages. Ambiguous duplicate environment/API/stage mappings are rejected. +environments or Nitro stages. Ambiguous duplicate environment/API/stage mappings are rejected. A +declaration without `ForEnvironment` publishes in every environment, which is the shape to use when +the stage comes from a parameter, so one declaration serves every stage. ## Source declaration and acquisition @@ -113,8 +116,8 @@ never written to output or logs. | Cloud URL | Explicit `.WithNitroCloudUrl(...)` HTTPS origin | | API ID | Explicit `.WithNitroApiId(...)` | | API key | Secret `ParameterResource` | -| Aspire environment | Explicit `.ForEnvironment(...)` | -| Nitro stage | Explicit `.ToStage(...)` | +| Aspire environment | Optional `.ForEnvironment(...)`, every environment when absent | +| Nitro stage | Explicit `.ToStage(...)` literal or `ParameterResource` | | Rollout/source/configuration tag | `builder.AddParameter("tag")` | ## `fusion-upload` diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs index d02386bf199..280829dba01 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs @@ -3,7 +3,8 @@ namespace HotChocolate.Fusion.Aspire; /// -/// Represents an environment-specific Fusion deployment to Nitro. +/// Represents a Fusion deployment to Nitro. A deployment publishes to one Nitro stage and can be +/// restricted to a single Aspire environment. /// public sealed class FusionDeploymentResource( string name, @@ -16,6 +17,8 @@ public sealed class FusionDeploymentResource( internal string? StageName { get; set; } + internal ParameterResource? StageParameter { get; set; } + internal string? CompositionEnvironmentName { get; set; } internal string? ConfigurationTag { get; set; } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs index 566b7b3fe61..b7ec0be233f 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs @@ -37,7 +37,8 @@ internal static IReadOnlyList SelectDeployments( var deployments = model.Resources .OfType() .Where(deployment => - string.Equals( + deployment.EnvironmentName is null + || string.Equals( deployment.EnvironmentName, environmentName, StringComparison.Ordinal)) @@ -51,9 +52,9 @@ internal static IReadOnlyList SelectDeployments( var duplicate = deployments .GroupBy( deployment => ( - deployment.Nitro.CloudUrl, - deployment.Nitro.ApiId, - deployment.StageName), + CloudUrl: deployment.Nitro.CloudUrl, + ApiId: deployment.Nitro.ApiId, + StageName: GetStageKey(deployment)), FusionDeploymentKeyComparer.Instance) .FirstOrDefault(group => group.Count() > 1); @@ -67,6 +68,14 @@ internal static IReadOnlyList SelectDeployments( return deployments; } + /// + /// Gets the declaration-time identity of the stage that a deployment publishes to. A stage that + /// is supplied by a parameter is only known per publish, so the parameter itself is the + /// identity. + /// + private static string GetStageKey(FusionDeploymentResource deployment) + => deployment.StageName ?? $"{{{deployment.StageParameter!.Name}}}"; + internal static IResourceWithEndpoints GetCompositionResource( DistributedApplicationModel model) { @@ -354,13 +363,8 @@ private static Task ExecuteUploadAsync(PipelineStepContext context) private static void ValidateDeclaration( FusionDeploymentResource deployment) { - if (string.IsNullOrWhiteSpace(deployment.EnvironmentName)) - { - throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' must select an Aspire environment."); - } - - if (string.IsNullOrWhiteSpace(deployment.StageName)) + if (string.IsNullOrWhiteSpace(deployment.StageName) + && deployment.StageParameter is null) { throw new InvalidOperationException( $"Fusion deployment '{deployment.Name}' must select a Nitro stage."); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs index 09eeee7bcfb..b4851890058 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs @@ -74,6 +74,7 @@ await CreateDeploymentArtifactsAsync( deployment, sources, output, + environment, context.CancellationToken); } } @@ -112,10 +113,11 @@ await ValidateSessionStateAsync( deployment, context, context.CancellationToken); - ValidateCompositionState( + await ValidateCompositionStateAsync( state, deployment, - composition.Settings); + composition.Settings, + context.CancellationToken); VerifyMemoryDigest( state.FusionArchive, state.FusionArchiveSha256 @@ -435,11 +437,11 @@ await ValidateSessionStateAsync( context, context.CancellationToken); - var compositionEnvironment = ResolveCompositionEnvironment( + var compositionEnvironment = await ResolveCompositionEnvironmentAsync( deployment, - currentComposition.Settings); - await using var farStream = new BoundedMemoryStream( - _memoryLimits.FusionArchiveBytes); + currentComposition.Settings, + context.CancellationToken); + await using var farStream = new MemoryStream(); var logger = context.Services .GetRequiredService>(); if (!await AspireCompositionHelper.TryComposeArchivesAsync( @@ -508,10 +510,11 @@ await ValidateSessionStateAsync( deployment, context, context.CancellationToken); - ValidateCompositionState( + await ValidateCompositionStateAsync( state, deployment, - composition.Settings); + composition.Settings, + context.CancellationToken); VerifyMemoryDigest( state.FusionArchive, state.FusionArchiveSha256 @@ -519,10 +522,14 @@ await ValidateSessionStateAsync( "The composed Fusion archive has no digest."), "composed Fusion archive"); + var stage = await ResolveStageAsync( + deployment, + context.CancellationToken); + await workflow.PublishAsync( new FusionPublicationRequest( target, - deployment.StageName!, + stage, state.Tag, state.SourceIdentities .Select(source => @@ -647,9 +654,10 @@ internal async Task> MaterializeArchivesAsyn foreach (var deployment in deployments) { - var compositionEnvironment = ResolveCompositionEnvironment( + var compositionEnvironment = await ResolveCompositionEnvironmentAsync( deployment, - composition.Settings); + composition.Settings, + context.CancellationToken); var tag = await ResolveConfigurationTagAsync( deployment, context.CancellationToken); @@ -716,6 +724,7 @@ private static async Task CreateDeploymentArtifactsAsync( FusionDeploymentResource deployment, IReadOnlyList sources, string output, + string environment, CancellationToken cancellationToken) { var deploymentDirectory = GetDeploymentDirectory(output, deployment); @@ -752,8 +761,9 @@ await CreateSourceArtifactsAsync( FormatVersion: 1, CloudUrl: deployment.Nitro.CloudUrl!, ApiId: deployment.Nitro.ApiId!, - Environment: deployment.EnvironmentName!, - Stage: deployment.StageName!, + Environment: environment, + Stage: deployment.StageName + ?? $"{{{{{deployment.StageParameter!.Name}}}}}", ConfigurationTag: deployment.ConfigurationTag ?? $"{{{{{deployment.ConfigurationTagParameter!.Name}}}}}", StageOwnership: "authoritative", @@ -1008,23 +1018,24 @@ await File.ReadAllBytesAsync( } } - internal static string ResolveCompositionEnvironment( + internal static async Task ResolveCompositionEnvironmentAsync( FusionDeploymentResource deployment, - GraphQLCompositionSettings settings) + GraphQLCompositionSettings settings, + CancellationToken cancellationToken) => deployment.CompositionEnvironmentName ?? settings.EnvironmentName - ?? deployment.StageName - ?? throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' has no composition environment."); + ?? await ResolveStageAsync(deployment, cancellationToken); - private static void ValidateCompositionState( + private static async Task ValidateCompositionStateAsync( FusionDeploymentSessionState state, FusionDeploymentResource deployment, - GraphQLCompositionSettings settings) + GraphQLCompositionSettings settings, + CancellationToken cancellationToken) { - var expectedEnvironment = ResolveCompositionEnvironment( + var expectedEnvironment = await ResolveCompositionEnvironmentAsync( deployment, - settings); + settings, + cancellationToken); if (!string.Equals( state.CompositionEnvironment, expectedEnvironment, @@ -1373,6 +1384,26 @@ private static void ValidateSettingsName( return File.Exists(path) ? path : null; } + private static async Task ResolveStageAsync( + FusionDeploymentResource deployment, + CancellationToken cancellationToken) + { + var value = deployment.StageName; + if (deployment.StageParameter is not null) + { + value = await deployment.StageParameter.GetValueAsync( + cancellationToken); + } + + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException( + $"Fusion deployment '{deployment.Name}' stage resolved to an empty value."); + } + + return value; + } + private static async Task ResolveConfigurationTagAsync( FusionDeploymentResource deployment, CancellationToken cancellationToken) diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineMemoryLimits.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineMemoryLimits.cs new file mode 100644 index 00000000000..1e61dc8222d --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineMemoryLimits.cs @@ -0,0 +1,14 @@ +namespace HotChocolate.Fusion.Aspire; + +internal sealed record FusionPipelineMemoryLimits( + int SourceArchiveBytes, + long TotalSourceArchiveBytes) +{ + public const int DefaultSourceArchiveBytes = 128_000_000; + + public const int DefaultTotalSourceArchiveBytes = 512_000_000; + + public static FusionPipelineMemoryLimits Default { get; } = new( + DefaultSourceArchiveBytes, + DefaultTotalSourceArchiveBytes); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs index e2582d4cb81..00f07c74b8b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs @@ -355,7 +355,7 @@ public static IResourceBuilder WithNitroApiKey( } /// - /// Adds an environment-specific Fusion deployment that publishes to the Nitro publish target. + /// Adds a Fusion deployment that publishes to the Nitro publish target. /// /// /// The resource builder of a Nitro publish target. @@ -378,7 +378,7 @@ public static IResourceBuilder AddFusionDeployment( } /// - /// Maps the deployment to an exact Aspire environment. + /// Restricts the deployment to an exact Aspire environment. /// /// /// The resource builder of a Fusion deployment. @@ -389,6 +389,10 @@ public static IResourceBuilder AddFusionDeployment( /// /// The resource builder for chaining. /// + /// + /// When this is not configured, the deployment publishes in every Aspire environment, which is + /// the shape to use when the stage is supplied per publish. + /// public static IResourceBuilder ForEnvironment( this IResourceBuilder builder, string environmentName) @@ -420,6 +424,32 @@ public static IResourceBuilder ToStage( ArgumentException.ThrowIfNullOrWhiteSpace(stageName); builder.Resource.StageName = stageName; + builder.Resource.StageParameter = null; + return builder; + } + + /// + /// Maps the deployment to the Nitro stage that the parameter supplies. The stage is resolved + /// per publish, so one deployment declaration can serve every stage. + /// + /// + /// The resource builder of a Fusion deployment. + /// + /// + /// The parameter that supplies the name of the Nitro stage. + /// + /// + /// The resource builder for chaining. + /// + public static IResourceBuilder ToStage( + this IResourceBuilder builder, + IResourceBuilder stage) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(stage); + + builder.Resource.StageParameter = stage.Resource; + builder.Resource.StageName = null; return builder; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs index 30f5cb34496..6115f70ad2b 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs @@ -40,6 +40,107 @@ public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() Assert.Equal(["production"], deployments.Select(x => x.Name)); } + [Fact] + public void SelectDeployments_Should_SelectDeployment_When_NoEnvironmentIsDeclared() + { + // arrange + var builder = DistributedApplication.CreateBuilder(); + var stage = builder.AddParameter("stage", "development"); + builder + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") + .AddFusionDeployment("fusion") + .ToStage(stage) + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + // act + var deployments = FusionPipeline.SelectDeployments(model, "Production"); + + // assert + Assert.Equal(["fusion"], deployments.Select(x => x.Name)); + } + + [Fact] + public async Task ResolveCompositionEnvironment_Should_UseStageParameter_When_NoOverrideExists() + { + // arrange + var builder = DistributedApplication.CreateBuilder(); + var stage = builder.AddParameter("stage", "development"); + var deployment = builder + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") + .AddFusionDeployment("fusion") + .ToStage(stage) + .WithConfigurationTag("release-1"); + + // act + var environment = + await FusionPipelineExecutor.ResolveCompositionEnvironmentAsync( + deployment.Resource, + new GraphQLCompositionSettings(), + TestContext.Current.CancellationToken); + + // assert + Assert.Equal("development", environment); + } + + [Fact] + public void SelectDeployments_Should_Fail_When_TwoDeploymentsShareAStageParameter() + { + // arrange + var builder = DistributedApplication.CreateBuilder(); + var stage = builder.AddParameter("stage", "development"); + var nitro = builder + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products"); + nitro + .AddFusionDeployment("fusion-a") + .ToStage(stage) + .WithConfigurationTag("release-1"); + nitro + .AddFusionDeployment("fusion-b") + .ToStage(stage) + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + // act + var exception = Assert.Throws( + () => FusionPipeline.SelectDeployments(model, "Production")); + + // assert + Assert.Equal( + "Multiple Fusion deployments map environment 'Production' to Nitro " + + "API 'products' stage '{stage}'.", + exception.Message); + } + + [Fact] + public void SelectDeployments_Should_Fail_When_NoStageIsSelected() + { + // arrange + var builder = DistributedApplication.CreateBuilder(); + builder + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") + .AddFusionDeployment("fusion") + .WithConfigurationTag("release-1"); + var model = new DistributedApplicationModel(builder.Resources); + + // act + var exception = Assert.Throws( + () => FusionPipeline.SelectDeployments(model, "Production")); + + // assert + Assert.Equal( + "Fusion deployment 'fusion' must select a Nitro stage.", + exception.Message); + } + [Fact] public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() { @@ -396,7 +497,7 @@ public void ResolveSourceSchemaSettings_Should_UseDifferentEnvironmentOverrides_ } [Fact] - public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() + public async Task ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() { var deployment = new FusionDeploymentResource( "production", @@ -406,9 +507,10 @@ public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() }; var environment = - FusionPipelineExecutor.ResolveCompositionEnvironment( + await FusionPipelineExecutor.ResolveCompositionEnvironmentAsync( deployment, - new GraphQLCompositionSettings()); + new GraphQLCompositionSettings(), + TestContext.Current.CancellationToken); Assert.Equal("production", environment); } @@ -437,33 +539,6 @@ await File.WriteAllTextAsync( exception.Message); } - [Fact] - public void BoundedMemoryStream_Should_RejectWritesBeyondConfiguredLimit() - { - using var stream = new BoundedMemoryStream(3); - - stream.Write(new byte[] { 1, 2, 3 }); - var exception = Assert.Throws( - () => stream.WriteByte(4)); - - Assert.Equal( - "The composed Fusion archive exceeds the 3-byte in-memory size limit.", - exception.Message); - Assert.Equal(3, stream.Length); - } - - [Fact] - public void BoundedMemoryStream_Should_ClearBackingBuffer_WhenDisposed() - { - var stream = new BoundedMemoryStream(3); - stream.Write(new byte[] { 1, 2, 3 }); - var buffer = stream.GetBuffer(); - - stream.Dispose(); - - Assert.Equal(new byte[buffer.Length], buffer); - } - [Fact] public void TransferComposition_Should_ClearArchive_When_CanceledBeforeTransfer() { diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs index ca6c214a41d..5f749a131eb 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -209,6 +209,53 @@ await File.WriteAllTextAsync( Assert.Equal(runnerBTree, SnapshotTree(runnerB)); } + [Fact] + public async Task Release_Should_PublishToTheStage_That_TheParameterSupplies() + { + // arrange + // one deployment declaration that is not bound to an environment publishes to the stage + // that each publish supplies, so the same AppHost serves every stage. + using var testDirectory = new TestDirectory(); + using var nitro = await CreateSeededNitroAsync(); + var executor = FusionPipelineExecutor.Instance; + var projects = await CreateAppHostProjectStubsAsync(testDirectory.Path); + + // act + (string Stage, string Environment)[] publishes = + [ + ("development", "Development"), + ("test", "Production") + ]; + + foreach (var (stage, environment) in publishes) + { + var context = CreateContext( + CreateParameterizedModel(projects, stage), + environment, + outputPath: null, + nitro: nitro); + using var session = new FusionPipelineSession( + context.CancellationToken); + await executor.PreflightAsync(context, session); + await executor.DownloadAsync(context, session); + await executor.ComposeAsync(context, session); + await executor.PublishAsync(context, session); + } + + // assert + Assert.Equal( + ["development", "test"], + nitro.Publications.Select(publication => publication.Stage)); + Assert.All( + nitro.Publications, + publication => Assert.Equal( + [ + new FusionSourceSchemaVersion("products", "release-1"), + new FusionSourceSchemaVersion("reviews", "release-1") + ], + publication.Sources)); + } + [Fact] public async Task CreateArtifacts_Should_WriteAnOutputDirectoryThatUploadReadsBack() { @@ -430,8 +477,7 @@ await CreateAppHostProjectStubsAsync(testDirectory.Path)), nitro: nitro); var limits = new FusionPipelineMemoryLimits( SourceArchiveBytes: 2, - TotalSourceArchiveBytes: 100, - FusionArchiveBytes: 100); + TotalSourceArchiveBytes: 100); var executor = new FusionPipelineExecutor(limits); using var session = new FusionPipelineSession( context.CancellationToken, @@ -463,8 +509,7 @@ await CreateAppHostProjectStubsAsync(testDirectory.Path)), nitro: nitro); var limits = new FusionPipelineMemoryLimits( SourceArchiveBytes: 100_000, - TotalSourceArchiveBytes: 2, - FusionArchiveBytes: 100_000); + TotalSourceArchiveBytes: 2); var executor = new FusionPipelineExecutor(limits); using var session = new FusionPipelineSession( context.CancellationToken, @@ -691,6 +736,42 @@ private static DistributedApplicationModel CreateModel( projects.ReviewsProjectPath, projects.GatewayProjectPath); + private static DistributedApplicationModel CreateParameterizedModel( + (string ProductsProjectPath, + string ReviewsProjectPath, + string GatewayProjectPath) projects, + string stageName) + { + var builder = DistributedApplication.CreateBuilder(); + var tag = builder.AddParameter("tag", "release-1"); + var stage = builder.AddParameter("stage", stageName); + var apiKey = builder.AddParameter( + "nitroApiKey", + "test-api-key", + secret: true); + var products = builder + .AddProject("products", projects.ProductsProjectPath) + .WithGraphQLSchemaFile(); + var reviews = builder + .AddProject("reviews", projects.ReviewsProjectPath) + .WithGraphQLSchemaFile(); + builder + .AddProject("gateway", projects.GatewayProjectPath) + .WithReference(products) + .WithReference(reviews) + .WithGraphQLSchemaComposition(); + builder + .AddNitroPublishTarget("nitro") + .WithNitroCloudUrl("https://api.chillicream.com") + .WithNitroApiId("products") + .WithNitroApiKey(apiKey) + .AddFusionDeployment("fusion") + .ToStage(stage) + .WithConfigurationTag(tag); + + return new DistributedApplicationModel(builder.Resources); + } + private static DistributedApplicationModel CreateModel( string productsProjectPath, string reviewsProjectPath, From 10c69e2f4861e6d99fa98ce1f660f55d9ea060f3 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:40:51 +0200 Subject: [PATCH 09/11] Select Fusion deployments by declared Nitro stage instead of environment --- .../.github/workflows/deploy.yml | 4 - examples/FusionReleasePipeline/README.md | 28 +-- .../src/AppHost/Program.cs | 12 +- .../src/Fusion.Aspire/FUSION_PUBLISHING.md | 93 ++++--- .../FUSION_PUBLISH_DEPLOY_DESIGN.md | 41 ++-- .../src/Fusion.Aspire/FusionPipeline.cs | 147 ++++++------ .../Fusion.Aspire/FusionPipelineExecutor.cs | 226 +++++++----------- .../Fusion.Aspire/FusionPipelineSession.cs | 8 +- ...mentResource.cs => FusionStageResource.cs} | 20 +- .../src/Fusion.Aspire/NitroExtensions.cs | 120 +++------- .../NitroPublishTargetResource.cs | 9 + .../FusionPipelineTests.cs | 170 +++---------- .../FusionReleaseAcceptanceTests.cs | 139 +++++------ 13 files changed, 385 insertions(+), 632 deletions(-) rename src/HotChocolate/Fusion/src/Fusion.Aspire/{FusionDeploymentResource.cs => FusionStageResource.cs} (54%) diff --git a/examples/FusionReleasePipeline/.github/workflows/deploy.yml b/examples/FusionReleasePipeline/.github/workflows/deploy.yml index 078621064fc..a56e2d37448 100644 --- a/examples/FusionReleasePipeline/.github/workflows/deploy.yml +++ b/examples/FusionReleasePipeline/.github/workflows/deploy.yml @@ -24,7 +24,6 @@ jobs: group: fusion-nitro-example-invalid-replace-with-nitro-api-id-upload cancel-in-progress: false env: - Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} steps: @@ -49,7 +48,6 @@ jobs: run: >- aspire do fusion-upload --apphost "$APPHOST_PROJECT" - --environment Development --non-interactive deploy-development: @@ -98,7 +96,6 @@ jobs: run: >- aspire do fusion-publish --apphost "$APPHOST_PROJECT" - --environment Development --non-interactive deploy-test: @@ -149,5 +146,4 @@ jobs: run: >- aspire do fusion-publish --apphost "$APPHOST_PROJECT" - --environment Test --non-interactive diff --git a/examples/FusionReleasePipeline/README.md b/examples/FusionReleasePipeline/README.md index a142c918d71..2641129e431 100644 --- a/examples/FusionReleasePipeline/README.md +++ b/examples/FusionReleasePipeline/README.md @@ -23,11 +23,10 @@ GitHub environments `Development` and `Test`, then configure these environment s - `DEMO_AZURE_SUBSCRIPTION_ID`. Configure `DEMO_NITRO_STAGE`, `DEMO_AZURE_LOCATION`, and `DEMO_AZURE_RESOURCE_GROUP` as environment -variables. `DEMO_NITRO_STAGE` carries the Nitro stage of each GitHub environment and reaches the -AppHost as `Parameters__stage`, so the AppHost declares one deployment instead of one per stage. The -upload job runs outside a GitHub environment and reads the repository-level `DEMO_NITRO_STAGE`. The -sample uses Azure Container Apps because it contributes the `DeployCompute` steps needed to prove -source and gateway ordering. Development and Test should normally use distinct resource groups. +variables. `DEMO_NITRO_STAGE` carries the Nitro stage of each GitHub environment and selects one of +the stages the AppHost declares. The upload job needs no stage at all. The sample uses Azure +Container Apps because it contributes the `DeployCompute` steps needed to prove source and gateway +ordering. Development and Test should normally use distinct resource groups. The sources and gateway use external HTTP ingress. The deployment runner must reach the configured source URLs for readiness polling. The committed `.invalid` URLs deliberately fail a real release. @@ -53,28 +52,26 @@ settings environment and starts: - reviews at `http://localhost:5102/graphql`. Run mode composes for `local` and injects no `NITRO_*` variables, so the gateway uses its local FAR. -A publish composes for the stage that `Parameters__stage` supplies and passes that same stage to the +A publish composes for the stage that the `stage` parameter names and passes that same stage to the gateway as `NITRO_STAGE`. ## Release flow -All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`, and supply the target -stage as `Parameters__stage`. The build job uploads both sources as exact immutable versions: +All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`. Only the deployment +jobs also set `Parameters__stage`, because an immutable source version serves every stage. The build +job uploads both sources as exact immutable versions: ```shell -export Parameters__stage="$DEMO_NITRO_STAGE" export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" aspire do fusion-upload \ --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ - --environment Development \ --non-interactive ``` -Development and Test share the same Nitro cloud URL, API ID, source set, and tag, so this single -upload serves both. A real AppHost with distinct Nitro API targets must run `fusion-upload` once per -distinct target using a matching selected environment. +Development and Test are two stages of one Nitro api, so this single upload serves both. An AppHost +that declares several apis uploads each of them in the same invocation. The deployment job checks out the same revision and publishes without a manifest or CI artifact: @@ -90,7 +87,6 @@ export Azure__Location="$DEMO_AZURE_LOCATION" aspire do fusion-publish \ --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ - --environment Development \ --non-interactive ``` @@ -99,8 +95,8 @@ versions `products@RELEASE_TAG` and `reviews@RELEASE_TAG` as a metadata-only pre deployment it downloads them again, verifies the same canonical digests, and composes the Development endpoints. -The Test job uses the same tag with `--environment Test` and the Test `DEMO_NITRO_STAGE`, which -composes the Test endpoints and publishes to the Test stage. +The Test job uses the same tag with the Test `DEMO_NITRO_STAGE`, which composes the Test endpoints +and publishes to the Test stage. There is no artifact upload/download between jobs. The Fusion-specific publish steps never export schemas, upload source versions, write Fusion apply-state files, or resolve Aspire's output-path diff --git a/examples/FusionReleasePipeline/src/AppHost/Program.cs b/examples/FusionReleasePipeline/src/AppHost/Program.cs index 839ccdcbbdc..07c6c2583c2 100644 --- a/examples/FusionReleasePipeline/src/AppHost/Program.cs +++ b/examples/FusionReleasePipeline/src/AppHost/Program.cs @@ -8,8 +8,8 @@ builder.AddNitro(); builder.AddAzureContainerAppEnvironment("demo-aca"); -// the stage and the release tag are supplied per publish, so this AppHost declares one deployment -// that serves every stage instead of one declaration per environment. +// every stage this api publishes to is declared here by name. An invocation names one of them +// through the stage parameter, and the release tag identifies the release across all of them. var stage = builder.AddParameter("stage"); var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); @@ -36,15 +36,17 @@ .WithReference(products) .WithReference(reviews); -builder +var nitro = builder .AddNitroPublishTarget("nitro") .WithNitroCloudUrl(nitroCloudUrl) .WithNitroApiId(nitroApiId) .WithNitroApiKey(nitroApiKey) - .AddFusionDeployment("fusion") - .ToStage(stage) + .WithStageParameter(stage) .WithConfigurationTag(tag); +nitro.AddStage("development"); +nitro.AddStage("test"); + if (!builder.ExecutionContext.IsRunMode) { var nitroGatewayApiKey = builder.AddParameter( diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md index dd6a6dc3d39..f5c7c6b499b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISHING.md @@ -4,8 +4,8 @@ Fusion releases use Nitro itself as the handoff between the build job and deploy public commands are: ```shell -aspire do fusion-upload --environment -aspire do fusion-publish --environment +aspire do fusion-upload +aspire do fusion-publish ``` There is no release manifest parameter and no artifact upload or download between these commands. @@ -13,45 +13,22 @@ Both invocations must evaluate the same AppHost composition and use the same `ta ## AppHost declaration -One declaration serves every stage when the stage is a parameter: +Each api declares the stages it publishes to. An invocation names one of them: ```csharp var stage = builder.AddParameter("stage"); var tag = builder.AddParameter("tag"); var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); -builder.AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products-fusion") - .WithNitroApiKey(nitroApiKey) - .AddFusionDeployment("fusion") - .ToStage(stage) - .WithConfigurationTag(tag); -``` - -Each invocation then supplies the stage the same way it supplies the tag, for example as -`Parameters__stage`. - -Declare one deployment per environment instead when the stages differ in more than their name, for -example when only production waits for approval: - -```csharp var nitro = builder.AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") .WithNitroApiId("products-fusion") - .WithNitroApiKey(nitroApiKey); - -nitro.AddFusionDeployment("development") - .ForEnvironment("Development") - .ToStage("development") - .WithCompositionEnvironment("development") + .WithNitroApiKey(nitroApiKey) + .WithStageParameter(stage) .WithConfigurationTag(tag); -nitro.AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithCompositionEnvironment("production") - .WithConfigurationTag(tag) +nitro.AddStage("development"); +nitro.AddStage("production") .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -59,13 +36,27 @@ nitro.AddFusionDeployment("production") approval: TimeSpan.FromHours(2)); ``` -`ForEnvironment` restricts a declaration to one Aspire invocation. A declaration without it -publishes in every environment. `ToStage` selects the Nitro stage, either as a literal or from a -parameter that each invocation supplies. `WithCompositionEnvironment` selects the environment block -in each source's `schema-settings.json`, and defaults to the composition environment of the AppHost -followed by the resolved stage name. `WithConfigurationTag` supplies both the source version and -Fusion configuration tag. `WithApproval`, `WithForce`, and `WithTimeouts` configure the publication -itself. +`AddStage` declares a stage of the api. `WithStageParameter` sets the parameter that names the +stage an invocation publishes to, and the publication fails when that parameter names a stage the +api does not declare. `WithConfigurationTag` supplies both the source version and the Fusion +configuration tag, and belongs to the api because one release carries the same tag to every stage. +`WithCompositionEnvironment` selects the environment block in each source's `schema-settings.json` +and defaults to the composition environment of the AppHost followed by the stage name. `WithApproval`, +`WithForce`, and `WithTimeouts` configure the publication of a single stage. + +Each command needs only the parameters it uses, so `fusion-upload` needs a tag and `fusion-publish` +needs a tag and a stage. Values reach the AppHost through configuration, either as environment +variables or as arguments forwarded after `--`: + +```shell +Parameters__tag=v1 aspire do fusion-upload +Parameters__stage=production Parameters__tag=v1 aspire do fusion-publish + +aspire do fusion-publish -- --Parameters:stage=production --Parameters:tag=v1 +``` + +Prefer the environment for secrets such as the Nitro API key, because a command line is visible in +the process list. `WithNitroCloudUrl` and `WithNitroApiId` default to the `Nitro:CloudUrl` and `Nitro:ApiId` configuration values, or to the `NITRO_CLOUD_URL` and `NITRO_API_ID` environment variables. When @@ -77,7 +68,7 @@ resource name. Effective names must be unique and portable path segments. ## Upload -The build job checks out the source and runs one selected, real deployment environment: +The build job checks out the source and uploads once per Nitro api: ```shell export Parameters__tag="$RELEASE_TAG" @@ -85,21 +76,20 @@ export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-upload \ --apphost ./src/AppHost/AppHost.csproj \ - --environment Development \ --non-interactive ``` -`fusion-upload` depends on `fusion-artifacts`. For every source in the selected environment's -current AppHost composition it: +`fusion-upload` depends on `fusion-artifacts`. For every source in the current AppHost composition +it: 1. acquires and validates the schema and settings; 2. creates the portable source archive; 3. assigns the exact version `name@tag`; -4. rejects loopback endpoints for the selected composition environment; and -5. reconciles that immutable version on the selected Nitro API. +4. rejects loopback endpoints for the composition environment of every declared stage; and +5. reconciles that immutable version on the Nitro API. -If several deployment environments use the same Nitro cloud URL, API ID, source set, and tag, one -upload serves all of them. Run `fusion-upload` once per distinct Nitro API target otherwise. +An immutable source version serves every stage of its api, so upload takes no stage. Each declared +api is uploaded once. `aspire publish` remains an artifact-only root. It can create local Fusion source artifacts but it does not resolve a Nitro credential or mutate Nitro. Automation normally invokes `fusion-upload` @@ -111,17 +101,18 @@ Deployment jobs check out the same AppHost revision and use the same tag, but th source schema files or a build-job artifact: ```shell +export Parameters__stage=development export Parameters__tag="$RELEASE_TAG" export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-publish \ --apphost ./src/AppHost/AppHost.csproj \ - --environment Development \ --non-interactive ``` -Publish infers the complete, sorted source-name set from the current AppHost. Before source compute -is changed it downloads every exact `name@tag` from the selected Nitro API. A missing source fails +Publish resolves the stage that the stage parameter names, and fails when the api does not declare +it. It infers the complete, sorted source-name set from the current AppHost. Before source compute +is changed it downloads every exact `name@tag` from the Nitro API. A missing source fails the deployment. This preflight records only identities and canonical content digests, then clears the archive buffers before provider deployment begins. @@ -133,8 +124,7 @@ source checkout, invokes Git, or calls the source-upload API. The Fusion-specifi composition, readiness, and publication steps create no Fusion apply-state files and do not resolve Aspire's output-path service. Provider-contributed deployment dependencies may still write target artifacts or Aspire deployment state. Source archives are limited to 128,000,000 bytes each and -512,000,000 bytes in aggregate; the FAR is limited to 256,000,000 bytes. Owned buffers are cleared -after success, failure, or cancellation. +512,000,000 bytes in aggregate. Owned buffers are cleared after success, failure, or cancellation. ## Ordering @@ -181,12 +171,12 @@ jobs: - run: >- aspire do fusion-upload --apphost ./src/AppHost/AppHost.csproj - --environment Development --non-interactive deploy-development: needs: fusion-upload env: + Parameters__stage: development Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} steps: @@ -194,7 +184,6 @@ jobs: - run: >- aspire do fusion-publish --apphost ./src/AppHost/AppHost.csproj - --environment Development --non-interactive ``` diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md index 7a9c5764560..585afc2f477 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FUSION_PUBLISH_DEPLOY_DESIGN.md @@ -51,11 +51,8 @@ var nitro = builder .WithNitroApiKey(nitroApiKey); nitro - .AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") + .AddStage("production") .WithCompositionEnvironment("production") - .WithConfigurationTag(tag) .WithApproval(waitForApproval: true) .WithForce(false) .WithTimeouts( @@ -63,15 +60,13 @@ nitro approval: TimeSpan.FromHours(2)); ``` -`ForEnvironment` restricts the declaration to one Aspire invocation. `ToStage` selects the Nitro -stage, either as a literal or from a parameter. `WithCompositionEnvironment` selects the -source-settings environment. `WithConfigurationTag` supplies the immutable source version and final -configuration tag. +`AddStage` declares a stage of the api. `WithStageParameter` names the parameter that selects the +stage of an invocation, and an unknown stage name is rejected. `WithCompositionEnvironment` selects +the source-settings environment. `WithConfigurationTag` supplies the immutable source version and +final configuration tag for every stage of the api. -Multiple deployment declarations can map the same AppHost composition to different Aspire -environments or Nitro stages. Ambiguous duplicate environment/API/stage mappings are rejected. A -declaration without `ForEnvironment` publishes in every environment, which is the shape to use when -the stage comes from a parameter, so one declaration serves every stage. +An AppHost can declare several apis, and each api can declare several stages. One invocation +publishes to exactly one stage per api. ## Source declaration and acquisition @@ -116,13 +111,14 @@ never written to output or logs. | Cloud URL | Explicit `.WithNitroCloudUrl(...)` HTTPS origin | | API ID | Explicit `.WithNitroApiId(...)` | | API key | Secret `ParameterResource` | -| Aspire environment | Optional `.ForEnvironment(...)`, every environment when absent | -| Nitro stage | Explicit `.ToStage(...)` literal or `ParameterResource` | +| Nitro stages | Explicit `.AddStage(...)` per stage | +| Selected stage | `.WithStageParameter(...)`, resolved per invocation | | Rollout/source/configuration tag | `builder.AddParameter("tag")` | ## `fusion-upload` -The upload command selects the deployment declaration for the current Aspire environment. It: +The upload command writes immutable source versions, which every stage of an api shares, so it +needs no stage. It: 1. resolves the current AppHost composition and complete effective source-name set; 2. exports or reads every source and validates schema, settings, extensions, and endpoint binding; @@ -134,7 +130,7 @@ An existing version with identical canonical content is success. An existing ver different content is an immutable-version collision and fails. Partial uploads may remain orphaned if a later source fails, but no Nitro stage is changed by upload. -Use a real selected environment for the build job: +The build job needs only the release tag: ```shell export Parameters__tag="$RELEASE_TAG" @@ -142,12 +138,11 @@ export Parameters__nitroApiKey="$NITRO_API_KEY" aspire do fusion-upload \ --apphost ./src/AppHost/AppHost.csproj \ - --environment Development \ --non-interactive ``` -When Development and Test share the same Nitro cloud URL, API ID, source set, and tag, that upload -serves both. Run the command once per distinct Nitro API target otherwise. +An immutable source version serves every stage of its api, so one upload covers all of them. Each +declared api is uploaded once. ## `fusion-publish` @@ -221,8 +216,7 @@ run. Exact archive bytes are downloaded again after source compute, compared wit leased while composition, readiness, or publication reads them. Cancellation requests cleanup but does not zero an actively leased buffer until its reader unwinds. The session retains no credentials and writes no source archive, state file, apply directory, or composed FAR to disk. -Source archives are limited to 128,000,000 bytes each and 512,000,000 bytes in aggregate. The -composed FAR is limited to 256,000,000 bytes. +Source archives are limited to 128,000,000 bytes each and 512,000,000 bytes in aggregate. Compose, readiness, and publish validate: @@ -278,12 +272,12 @@ jobs: - run: >- aspire do fusion-upload --apphost ./src/AppHost/AppHost.csproj - --environment Development --non-interactive deploy-development: needs: upload env: + Parameters__stage: development Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} steps: @@ -291,12 +285,12 @@ jobs: - run: >- aspire do fusion-publish --apphost ./src/AppHost/AppHost.csproj - --environment Development --non-interactive deploy-test: needs: deploy-development env: + Parameters__stage: test Parameters__tag: ${{ env.RELEASE_TAG }} Parameters__nitroApiKey: ${{ secrets.NITRO_API_KEY }} steps: @@ -304,7 +298,6 @@ jobs: - run: >- aspire do fusion-publish --apphost ./src/AppHost/AppHost.csproj - --environment Test --non-interactive ``` diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs index b7ec0be233f..df0c4337c10 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipeline.cs @@ -30,51 +30,75 @@ public static void Configure( context => ConfigureSteps(context, topology)); } - internal static IReadOnlyList SelectDeployments( - DistributedApplicationModel model, - string environmentName) + /// + /// Gets every Nitro api that the distributed application publishes to. The sources an api + /// receives are the same for every stage, so the steps that only write immutable source + /// versions select apis instead of stages and need no stage. + /// + internal static IReadOnlyList SelectTargets( + DistributedApplicationModel model) { - var deployments = model.Resources - .OfType() - .Where(deployment => - deployment.EnvironmentName is null - || string.Equals( - deployment.EnvironmentName, - environmentName, - StringComparison.Ordinal)) + var targets = model.Resources + .OfType() + .Where(target => GetStages(model, target).Count > 0) .ToArray(); - foreach (var deployment in deployments) + foreach (var target in targets) { - ValidateDeclaration(deployment); + ValidateDeclaration(target); } - var duplicate = deployments - .GroupBy( - deployment => ( - CloudUrl: deployment.Nitro.CloudUrl, - ApiId: deployment.Nitro.ApiId, - StageName: GetStageKey(deployment)), - FusionDeploymentKeyComparer.Instance) - .FirstOrDefault(group => group.Count() > 1); + return targets; + } + + /// + /// Gets the stage that each Nitro api publishes to in this invocation, resolved from the stage + /// parameter of the api. + /// + internal static async Task> SelectStagesAsync( + DistributedApplicationModel model, + CancellationToken cancellationToken) + { + var selected = new List(); - if (duplicate is not null) + foreach (var target in SelectTargets(model)) { - throw new InvalidOperationException( - $"Multiple Fusion deployments map environment '{environmentName}' to Nitro " - + $"API '{duplicate.Key.ApiId}' stage '{duplicate.Key.StageName}'."); + var stages = GetStages(model, target); + var stageName = await target.StageParameter!.GetValueAsync(cancellationToken); + + if (string.IsNullOrWhiteSpace(stageName)) + { + throw new InvalidOperationException( + $"Nitro target '{target.Name}' stage parameter " + + $"'{target.StageParameter.Name}' resolved to an empty value."); + } + + var stage = stages.FirstOrDefault( + candidate => string.Equals( + candidate.StageName, + stageName, + StringComparison.Ordinal)); + + if (stage is null) + { + throw new InvalidOperationException( + $"Nitro target '{target.Name}' does not declare the stage '{stageName}'. " + + $"Declared stages: {string.Join(", ", stages.Select(s => s.StageName).Order(StringComparer.Ordinal))}."); + } + + selected.Add(stage); } - return deployments; + return selected; } - /// - /// Gets the declaration-time identity of the stage that a deployment publishes to. A stage that - /// is supplied by a parameter is only known per publish, so the parameter itself is the - /// identity. - /// - private static string GetStageKey(FusionDeploymentResource deployment) - => deployment.StageName ?? $"{{{deployment.StageParameter!.Name}}}"; + internal static IReadOnlyList GetStages( + DistributedApplicationModel model, + NitroPublishTargetResource target) + => model.Resources + .OfType() + .Where(stage => ReferenceEquals(stage.Nitro, target)) + .ToArray(); internal static IResourceWithEndpoints GetCompositionResource( DistributedApplicationModel model) @@ -94,14 +118,8 @@ internal static IEnumerable CreateSteps( PipelineStepFactoryContext context, FusionPipelineTopology topology) { - var environment = context.PipelineContext.Services - .GetRequiredService() - .EnvironmentName; - var deployments = SelectDeployments( - context.PipelineContext.Model, - environment); - - topology.HasDeployments = deployments.Count > 0; + topology.HasDeployments = + SelectTargets(context.PipelineContext.Model).Count > 0; var session = new FusionPipelineSession( context.PipelineContext.CancellationToken); @@ -361,29 +379,28 @@ private static Task ExecuteUploadAsync(PipelineStepContext context) => FusionPipelineExecutor.Instance.UploadAsync(context); private static void ValidateDeclaration( - FusionDeploymentResource deployment) + NitroPublishTargetResource target) { - if (string.IsNullOrWhiteSpace(deployment.StageName) - && deployment.StageParameter is null) + if (target.StageParameter is null) { throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' must select a Nitro stage."); + $"Nitro target '{target.Name}' must specify the parameter that selects the stage."); } - if (string.IsNullOrWhiteSpace(deployment.Nitro.CloudUrl)) + if (string.IsNullOrWhiteSpace(target.CloudUrl)) { throw new InvalidOperationException( - $"Nitro target '{deployment.Nitro.Name}' must specify a cloud URL."); + $"Nitro target '{target.Name}' must specify a cloud URL."); } if (!Uri.TryCreate( - deployment.Nitro.CloudUrl, + target.CloudUrl, UriKind.Absolute, out var cloudUri) || cloudUri.Scheme is not "https") { throw new InvalidOperationException( - $"Nitro target '{deployment.Nitro.Name}' cloud URL must use HTTPS."); + $"Nitro target '{target.Name}' cloud URL must use HTTPS."); } if (!string.IsNullOrEmpty(cloudUri.UserInfo) @@ -392,42 +409,22 @@ private static void ValidateDeclaration( || !string.IsNullOrEmpty(cloudUri.Fragment)) { throw new InvalidOperationException( - $"Nitro target '{deployment.Nitro.Name}' cloud URL must be an origin."); + $"Nitro target '{target.Name}' cloud URL must be an origin."); } - if (string.IsNullOrWhiteSpace(deployment.Nitro.ApiId)) + if (string.IsNullOrWhiteSpace(target.ApiId)) { throw new InvalidOperationException( - $"Nitro target '{deployment.Nitro.Name}' must specify an API ID."); + $"Nitro target '{target.Name}' must specify an API ID."); } - if (deployment.ConfigurationTagParameter is null - && string.IsNullOrWhiteSpace(deployment.ConfigurationTag)) + if (target.ConfigurationTagParameter is null + && string.IsNullOrWhiteSpace(target.ConfigurationTag)) { throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' must specify a configuration tag."); + $"Nitro target '{target.Name}' must specify a configuration tag."); } } - - private sealed class FusionDeploymentKeyComparer - : IEqualityComparer<(string? CloudUrl, string? ApiId, string? StageName)> - { - public static FusionDeploymentKeyComparer Instance { get; } = new(); - - public bool Equals( - (string? CloudUrl, string? ApiId, string? StageName) x, - (string? CloudUrl, string? ApiId, string? StageName) y) - => string.Equals(x.CloudUrl, y.CloudUrl, StringComparison.OrdinalIgnoreCase) - && string.Equals(x.ApiId, y.ApiId, StringComparison.Ordinal) - && string.Equals(x.StageName, y.StageName, StringComparison.Ordinal); - - public int GetHashCode( - (string? CloudUrl, string? ApiId, string? StageName) obj) - => HashCode.Combine( - StringComparer.OrdinalIgnoreCase.GetHashCode(obj.CloudUrl ?? ""), - StringComparer.Ordinal.GetHashCode(obj.ApiId ?? ""), - StringComparer.Ordinal.GetHashCode(obj.StageName ?? "")); - } } internal sealed class FusionPipelineTopology diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs index b4851890058..1354864a54d 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs @@ -39,14 +39,9 @@ internal FusionPipelineExecutor(FusionPipelineMemoryLimits memoryLimits) public async Task CreateArtifactsAsync(PipelineStepContext context) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( - context.Model, - environment); + var targets = FusionPipeline.SelectTargets(context.Model); - if (deployments.Count == 0) + if (targets.Count == 0) { return; } @@ -68,13 +63,13 @@ public async Task CreateArtifactsAsync(PipelineStepContext context) .GetRequiredService() .GetOutputDirectory(); - foreach (var deployment in deployments) + foreach (var target in targets) { await CreateDeploymentArtifactsAsync( - deployment, + target, + FusionPipeline.GetStages(context.Model, target), sources, output, - environment, context.CancellationToken); } } @@ -83,12 +78,9 @@ public async Task VerifyReadinessAsync( PipelineStepContext context, FusionPipelineSession session) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( + var deployments = await FusionPipeline.SelectStagesAsync( context.Model, - environment); + context.CancellationToken); if (deployments.Count == 0) { @@ -113,11 +105,10 @@ await ValidateSessionStateAsync( deployment, context, context.CancellationToken); - await ValidateCompositionStateAsync( + ValidateCompositionState( state, deployment, - composition.Settings, - context.CancellationToken); + composition.Settings); VerifyMemoryDigest( state.FusionArchive, state.FusionArchiveSha256 @@ -166,7 +157,7 @@ public async Task UploadAsync(PipelineStepContext context) var workflow = context.Services.GetRequiredService(); - foreach (var group in artifacts.GroupBy(artifact => artifact.Deployment)) + foreach (var group in artifacts.GroupBy(artifact => artifact.Target)) { var target = await ResolveTargetAsync( group.Key, @@ -191,12 +182,9 @@ public async Task PreflightAsync( PipelineStepContext context, FusionPipelineSession session) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( + var deployments = await FusionPipeline.SelectStagesAsync( context.Model, - environment); + context.CancellationToken); if (deployments.Count == 0) { return; @@ -206,7 +194,7 @@ public async Task PreflightAsync( .GetRequiredService(); var sourceNames = GetSourceNames(context.Model); var preparedStates = new List<( - FusionDeploymentResource Deployment, + FusionStageResource Deployment, FusionDeploymentSessionState State)>(deployments.Count); long totalSourceBytes = 0; var transferred = false; @@ -216,7 +204,7 @@ public async Task PreflightAsync( foreach (var deployment in deployments) { var tag = await ResolveConfigurationTagAsync( - deployment, + deployment.Nitro, context.CancellationToken); var target = await ResolveTargetAsync( deployment, @@ -297,12 +285,9 @@ public async Task DownloadAsync( PipelineStepContext context, FusionPipelineSession session) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( + var deployments = await FusionPipeline.SelectStagesAsync( context.Model, - environment); + context.CancellationToken); if (deployments.Count == 0) { return; @@ -410,12 +395,9 @@ public async Task ComposeAsync( PipelineStepContext context, FusionPipelineSession session) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( + var deployments = await FusionPipeline.SelectStagesAsync( context.Model, - environment); + context.CancellationToken); if (deployments.Count == 0) { return; @@ -437,10 +419,9 @@ await ValidateSessionStateAsync( context, context.CancellationToken); - var compositionEnvironment = await ResolveCompositionEnvironmentAsync( + var compositionEnvironment = ResolveCompositionEnvironment( deployment, - currentComposition.Settings, - context.CancellationToken); + currentComposition.Settings); await using var farStream = new MemoryStream(); var logger = context.Services .GetRequiredService>(); @@ -478,12 +459,9 @@ public async Task PublishAsync( PipelineStepContext context, FusionPipelineSession session) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( + var deployments = await FusionPipeline.SelectStagesAsync( context.Model, - environment); + context.CancellationToken); if (deployments.Count == 0) { return; @@ -510,11 +488,10 @@ await ValidateSessionStateAsync( deployment, context, context.CancellationToken); - await ValidateCompositionStateAsync( + ValidateCompositionState( state, deployment, - composition.Settings, - context.CancellationToken); + composition.Settings); VerifyMemoryDigest( state.FusionArchive, state.FusionArchiveSha256 @@ -522,14 +499,10 @@ await ValidateCompositionStateAsync( "The composed Fusion archive has no digest."), "composed Fusion archive"); - var stage = await ResolveStageAsync( - deployment, - context.CancellationToken); - await workflow.PublishAsync( new FusionPublicationRequest( target, - stage, + deployment.StageName, state.Tag, state.SourceIdentities .Select(source => @@ -631,14 +604,9 @@ await Task.Delay( internal async Task> MaterializeArchivesAsync( PipelineStepContext context) { - var environment = context.Services - .GetRequiredService() - .EnvironmentName; - var deployments = FusionPipeline.SelectDeployments( - context.Model, - environment); + var targets = FusionPipeline.SelectTargets(context.Model); - if (deployments.Count == 0) + if (targets.Count == 0) { return []; } @@ -652,16 +620,13 @@ internal async Task> MaterializeArchivesAsyn var composition = GraphQLResourceModel.GetComposition( compositionResource); - foreach (var deployment in deployments) + foreach (var target in targets) { - var compositionEnvironment = await ResolveCompositionEnvironmentAsync( - deployment, - composition.Settings, - context.CancellationToken); + var stages = FusionPipeline.GetStages(context.Model, target); var tag = await ResolveConfigurationTagAsync( - deployment, + target, context.CancellationToken); - var deploymentDirectory = GetDeploymentDirectory(output, deployment); + var deploymentDirectory = GetTargetDirectory(output, target); var materializedDirectory = IOPath.Combine( deploymentDirectory, "materialized"); @@ -687,12 +652,16 @@ await File.ReadAllTextAsync( ValidateSettingsName(name, settings); - using var resolvedSettings = - AspireCompositionHelper.ResolveSourceSchemaSettings( - settings, - compositionEnvironment); - var endpoint = GetTransportEndpoint(resolvedSettings); - RejectLoopbackEndpoint(endpoint); + // an immutable source version serves every stage of the api, so the endpoint of + // every declared stage has to be publicly reachable. + foreach (var stage in stages) + { + using var resolvedSettings = + AspireCompositionHelper.ResolveSourceSchemaSettings( + settings, + ResolveCompositionEnvironment(stage, composition.Settings)); + RejectLoopbackEndpoint(GetTransportEndpoint(resolvedSettings)); + } var archivePath = IOPath.Combine( materializedDirectory, @@ -709,7 +678,7 @@ await CreateArchiveAsync( artifacts.Add( new( - deployment, + target, name, sourceVersion, archivePath, @@ -721,18 +690,18 @@ await CreateArchiveAsync( } private static async Task CreateDeploymentArtifactsAsync( - FusionDeploymentResource deployment, + NitroPublishTargetResource target, + IReadOnlyList stages, IReadOnlyList sources, string output, - string environment, CancellationToken cancellationToken) { - var deploymentDirectory = GetDeploymentDirectory(output, deployment); + var deploymentDirectory = GetTargetDirectory(output, target); var fusionDirectory = IOPath.GetDirectoryName(deploymentDirectory)!; Directory.CreateDirectory(fusionDirectory); var temporaryDirectory = IOPath.Combine( fusionDirectory, - $".{deployment.Name}.{Guid.NewGuid():N}.tmp"); + $".{target.Name}.{Guid.NewGuid():N}.tmp"); try { @@ -759,13 +728,14 @@ await CreateSourceArtifactsAsync( var template = new FusionDeploymentTemplate( FormatVersion: 1, - CloudUrl: deployment.Nitro.CloudUrl!, - ApiId: deployment.Nitro.ApiId!, - Environment: environment, - Stage: deployment.StageName - ?? $"{{{{{deployment.StageParameter!.Name}}}}}", - ConfigurationTag: deployment.ConfigurationTag - ?? $"{{{{{deployment.ConfigurationTagParameter!.Name}}}}}", + CloudUrl: target.CloudUrl!, + ApiId: target.ApiId!, + Stages: stages + .Select(stage => stage.StageName) + .Order(StringComparer.Ordinal) + .ToArray(), + ConfigurationTag: target.ConfigurationTag + ?? $"{{{{{target.ConfigurationTagParameter!.Name}}}}}", StageOwnership: "authoritative", Sources: sourceNames.Order(StringComparer.Ordinal).ToArray()); @@ -1018,24 +988,21 @@ await File.ReadAllBytesAsync( } } - internal static async Task ResolveCompositionEnvironmentAsync( - FusionDeploymentResource deployment, - GraphQLCompositionSettings settings, - CancellationToken cancellationToken) + internal static string ResolveCompositionEnvironment( + FusionStageResource deployment, + GraphQLCompositionSettings settings) => deployment.CompositionEnvironmentName ?? settings.EnvironmentName - ?? await ResolveStageAsync(deployment, cancellationToken); + ?? deployment.StageName; - private static async Task ValidateCompositionStateAsync( + private static void ValidateCompositionState( FusionDeploymentSessionState state, - FusionDeploymentResource deployment, - GraphQLCompositionSettings settings, - CancellationToken cancellationToken) + FusionStageResource deployment, + GraphQLCompositionSettings settings) { - var expectedEnvironment = await ResolveCompositionEnvironmentAsync( + var expectedEnvironment = ResolveCompositionEnvironment( deployment, - settings, - cancellationToken); + settings); if (!string.Equals( state.CompositionEnvironment, expectedEnvironment, @@ -1093,7 +1060,7 @@ private static async Task DownloadExactSourceAsync( FusionDeploymentWorkflow workflow, FusionTarget target, - FusionDeploymentResource deployment, + FusionStageResource deployment, string sourceName, string tag, CancellationToken cancellationToken) @@ -1173,12 +1140,12 @@ await FusionSourceSchemaContent.ComputeSha256Async( private static async Task ValidatePreflightStateAsync( FusionDeploymentSessionState state, - FusionDeploymentResource deployment, + FusionStageResource deployment, PipelineStepContext context, CancellationToken cancellationToken) { var tag = await ResolveConfigurationTagAsync( - deployment, + deployment.Nitro, cancellationToken); var sourceNames = GetSourceNames(context.Model); if (!string.Equals( @@ -1221,7 +1188,7 @@ private static async Task ValidatePreflightStateAsync( private static async Task ValidateSessionStateAsync( FusionDeploymentSessionState state, - FusionDeploymentResource deployment, + FusionStageResource deployment, PipelineStepContext context, CancellationToken cancellationToken) { @@ -1311,25 +1278,31 @@ await FusionSourceSchemaContent.ComputeSha256Async( return endpoints; } + private static Task ResolveTargetAsync( + FusionStageResource deployment, + PipelineStepContext context, + CancellationToken cancellationToken) + => ResolveTargetAsync(deployment.Nitro, context, cancellationToken); + private static async Task ResolveTargetAsync( - FusionDeploymentResource deployment, + NitroPublishTargetResource target, PipelineStepContext context, CancellationToken cancellationToken) { - var apiKey = deployment.Nitro.ApiKey is null + var apiKey = target.ApiKey is null ? context.Services.GetRequiredService()["Nitro:ApiKey"] ?? context.Services.GetRequiredService()["NITRO_API_KEY"] - : await deployment.Nitro.ApiKey.GetValueAsync(cancellationToken); + : await target.ApiKey.GetValueAsync(cancellationToken); if (string.IsNullOrWhiteSpace(apiKey)) { throw new InvalidOperationException( - $"Nitro target '{deployment.Nitro.Name}' requires an API key."); + $"Nitro target '{target.Name}' requires an API key."); } return new( - new Uri(deployment.Nitro.CloudUrl!, UriKind.Absolute), - deployment.Nitro.ApiId!, + new Uri(target.CloudUrl!, UriKind.Absolute), + target.ApiId!, apiKey); } @@ -1384,41 +1357,21 @@ private static void ValidateSettingsName( return File.Exists(path) ? path : null; } - private static async Task ResolveStageAsync( - FusionDeploymentResource deployment, - CancellationToken cancellationToken) - { - var value = deployment.StageName; - if (deployment.StageParameter is not null) - { - value = await deployment.StageParameter.GetValueAsync( - cancellationToken); - } - - if (string.IsNullOrWhiteSpace(value)) - { - throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' stage resolved to an empty value."); - } - - return value; - } - private static async Task ResolveConfigurationTagAsync( - FusionDeploymentResource deployment, + NitroPublishTargetResource target, CancellationToken cancellationToken) { - var value = deployment.ConfigurationTag; - if (deployment.ConfigurationTagParameter is not null) + var value = target.ConfigurationTag; + if (target.ConfigurationTagParameter is not null) { - value = await deployment.ConfigurationTagParameter.GetValueAsync( + value = await target.ConfigurationTagParameter.GetValueAsync( cancellationToken); } if (string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException( - $"Fusion deployment '{deployment.Name}' configuration tag resolved to an empty value."); + $"Nitro target '{target.Name}' configuration tag resolved to an empty value."); } ValidatePathSegment(value, "configuration tag"); @@ -1541,10 +1494,10 @@ await JsonSerializer.SerializeAsync( } } - private static string GetDeploymentDirectory( + private static string GetTargetDirectory( string output, - FusionDeploymentResource deployment) - => IOPath.Combine(output, "fusion", deployment.Name); + NitroPublishTargetResource target) + => IOPath.Combine(output, "fusion", target.Name); private static void DeleteDirectoryBestEffort(string path) { @@ -1565,7 +1518,7 @@ private static void DeleteDirectoryBestEffort(string path) } internal sealed record FusionSourceArtifact( - FusionDeploymentResource Deployment, + NitroPublishTargetResource Target, string Name, string Version, string ArchivePath, @@ -1575,8 +1528,7 @@ internal sealed record FusionDeploymentTemplate( int FormatVersion, string CloudUrl, string ApiId, - string Environment, - string Stage, + IReadOnlyList Stages, string ConfigurationTag, string StageOwnership, IReadOnlyList Sources); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs index 4f1987bedbc..2bb0d63d2a7 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineSession.cs @@ -4,7 +4,7 @@ internal sealed class FusionPipelineSession : IDisposable { private readonly object _sync = new(); private readonly Dictionary< - FusionDeploymentResource, + FusionStageResource, FusionDeploymentSessionState> _deployments = []; private readonly FusionPipelineMemoryLimits _memoryLimits; private readonly CancellationToken _cancellationToken; @@ -36,12 +36,12 @@ internal int DeploymentCount public void SetAll( IReadOnlyList<( - FusionDeploymentResource Deployment, + FusionStageResource Deployment, FusionDeploymentSessionState State)> deployments) { ArgumentNullException.ThrowIfNull(deployments); - var uniqueDeployments = new HashSet(); + var uniqueDeployments = new HashSet(); foreach (var (deployment, _) in deployments) { if (!uniqueDeployments.Add(deployment)) @@ -79,7 +79,7 @@ public void SetAll( } public FusionDeploymentSessionState GetState( - FusionDeploymentResource deployment) + FusionStageResource deployment) { ArgumentNullException.ThrowIfNull(deployment); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionStageResource.cs similarity index 54% rename from src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs rename to src/HotChocolate/Fusion/src/Fusion.Aspire/FusionStageResource.cs index 280829dba01..593a215a269 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionDeploymentResource.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionStageResource.cs @@ -3,28 +3,24 @@ namespace HotChocolate.Fusion.Aspire; /// -/// Represents a Fusion deployment to Nitro. A deployment publishes to one Nitro stage and can be -/// restricted to a single Aspire environment. +/// Represents a Nitro stage that a distributed application publishes its Fusion configuration to. +/// Every invocation publishes to exactly one declared stage of a Nitro api. /// -public sealed class FusionDeploymentResource( +public sealed class FusionStageResource( string name, + string stageName, NitroPublishTargetResource nitro) : Resource(name) { internal NitroPublishTargetResource Nitro { get; } = nitro; - internal string? EnvironmentName { get; set; } - - internal string? StageName { get; set; } - - internal ParameterResource? StageParameter { get; set; } + /// + /// The name of the Nitro stage. It is also the value that selects this stage for an invocation. + /// + internal string StageName { get; } = stageName; internal string? CompositionEnvironmentName { get; set; } - internal string? ConfigurationTag { get; set; } - - internal ParameterResource? ConfigurationTagParameter { get; set; } - internal bool WaitForApproval { get; set; } internal bool Force { get; set; } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs index 00f07c74b8b..a99fe277ad8 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroExtensions.cs @@ -355,101 +355,55 @@ public static IResourceBuilder WithNitroApiKey( } /// - /// Adds a Fusion deployment that publishes to the Nitro publish target. + /// Declares a Nitro stage that this api publishes to. Every stage an api publishes to is + /// declared once, and each invocation selects one of them by name. /// /// /// The resource builder of a Nitro publish target. /// - /// - /// The name of the deployment resource. - /// - /// - /// The resource builder of the deployment for chaining. - /// - public static IResourceBuilder AddFusionDeployment( - this IResourceBuilder builder, - string name) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(name); - - var resource = new FusionDeploymentResource(name, builder.Resource); - return builder.ApplicationBuilder.AddResource(resource); - } - - /// - /// Restricts the deployment to an exact Aspire environment. - /// - /// - /// The resource builder of a Fusion deployment. - /// - /// - /// The name of the Aspire environment. - /// - /// - /// The resource builder for chaining. - /// - /// - /// When this is not configured, the deployment publishes in every Aspire environment, which is - /// the shape to use when the stage is supplied per publish. - /// - public static IResourceBuilder ForEnvironment( - this IResourceBuilder builder, - string environmentName) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrWhiteSpace(environmentName); - - builder.Resource.EnvironmentName = environmentName; - return builder; - } - - /// - /// Maps the deployment to an exact Nitro stage. - /// - /// - /// The resource builder of a Fusion deployment. - /// /// - /// The name of the Nitro stage that the deployment publishes to. + /// The name of the Nitro stage. /// /// - /// The resource builder for chaining. + /// The resource builder of the stage for chaining. /// - public static IResourceBuilder ToStage( - this IResourceBuilder builder, + public static IResourceBuilder AddStage( + this IResourceBuilder builder, string stageName) { ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrWhiteSpace(stageName); - builder.Resource.StageName = stageName; - builder.Resource.StageParameter = null; - return builder; + var resource = new FusionStageResource( + $"{builder.Resource.Name}-{stageName}", + stageName, + builder.Resource); + return builder.ApplicationBuilder.AddResource(resource); } /// - /// Maps the deployment to the Nitro stage that the parameter supplies. The stage is resolved - /// per publish, so one deployment declaration can serve every stage. + /// Sets the parameter that names the declared stage an invocation publishes to. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Nitro publish target. /// /// - /// The parameter that supplies the name of the Nitro stage. + /// The parameter that supplies the name of the stage. /// /// /// The resource builder for chaining. /// - public static IResourceBuilder ToStage( - this IResourceBuilder builder, + /// + /// The publication fails when the parameter names a stage that the api does not declare. + /// + public static IResourceBuilder WithStageParameter( + this IResourceBuilder builder, IResourceBuilder stage) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(stage); builder.Resource.StageParameter = stage.Resource; - builder.Resource.StageName = null; return builder; } @@ -457,7 +411,7 @@ public static IResourceBuilder ToStage( /// Selects the exact schema-settings.json environment used for composition. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Fusion stage. /// /// /// The name of the settings environment. @@ -469,8 +423,8 @@ public static IResourceBuilder ToStage( /// When this is not configured, an environment selected by the GraphQL composition is used, /// followed by the Nitro stage name. /// - public static IResourceBuilder WithCompositionEnvironment( - this IResourceBuilder builder, + public static IResourceBuilder WithCompositionEnvironment( + this IResourceBuilder builder, string environmentName) { ArgumentNullException.ThrowIfNull(builder); @@ -485,7 +439,7 @@ public static IResourceBuilder WithCompositionEnvironm /// configuration tag of the deployment. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Nitro publish target. /// /// /// The parameter that supplies the release tag. @@ -493,8 +447,8 @@ public static IResourceBuilder WithCompositionEnvironm /// /// The resource builder for chaining. /// - public static IResourceBuilder WithConfigurationTag( - this IResourceBuilder builder, + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, IResourceBuilder configurationTag) { ArgumentNullException.ThrowIfNull(builder); @@ -510,7 +464,7 @@ public static IResourceBuilder WithConfigurationTag( /// configuration tag of the deployment. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Nitro publish target. /// /// /// The release tag. @@ -518,8 +472,8 @@ public static IResourceBuilder WithConfigurationTag( /// /// The resource builder for chaining. /// - public static IResourceBuilder WithConfigurationTag( - this IResourceBuilder builder, + public static IResourceBuilder WithConfigurationTag( + this IResourceBuilder builder, string configurationTag) { ArgumentNullException.ThrowIfNull(builder); @@ -534,7 +488,7 @@ public static IResourceBuilder WithConfigurationTag( /// Configures whether Nitro waits for approval before the configuration is committed. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Fusion stage. /// /// /// Specifies whether the publication waits for approval. @@ -542,8 +496,8 @@ public static IResourceBuilder WithConfigurationTag( /// /// The resource builder for chaining. /// - public static IResourceBuilder WithApproval( - this IResourceBuilder builder, + public static IResourceBuilder WithApproval( + this IResourceBuilder builder, bool waitForApproval) { ArgumentNullException.ThrowIfNull(builder); @@ -555,7 +509,7 @@ public static IResourceBuilder WithApproval( /// Configures whether validation failures may be forced. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Fusion stage. /// /// /// Specifies whether the publication proceeds despite validation failures. @@ -563,8 +517,8 @@ public static IResourceBuilder WithApproval( /// /// The resource builder for chaining. /// - public static IResourceBuilder WithForce( - this IResourceBuilder builder, + public static IResourceBuilder WithForce( + this IResourceBuilder builder, bool force) { ArgumentNullException.ThrowIfNull(builder); @@ -576,7 +530,7 @@ public static IResourceBuilder WithForce( /// Configures the operation and approval timeouts of the deployment. /// /// - /// The resource builder of a Fusion deployment. + /// The resource builder of a Fusion stage. /// /// /// The time a single remote operation may take. @@ -587,8 +541,8 @@ public static IResourceBuilder WithForce( /// /// The resource builder for chaining. /// - public static IResourceBuilder WithTimeouts( - this IResourceBuilder builder, + public static IResourceBuilder WithTimeouts( + this IResourceBuilder builder, TimeSpan operation, TimeSpan approval) { diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs index 20093fa2b0e..35e578abd02 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/NitroPublishTargetResource.cs @@ -12,4 +12,13 @@ public sealed class NitroPublishTargetResource(string name) : Resource(name) internal string? ApiId => this.GetNitroApiId(); internal ParameterResource? ApiKey { get; set; } + + /// + /// The parameter that names the declared stage an invocation publishes to. + /// + internal ParameterResource? StageParameter { get; set; } + + internal string? ConfigurationTag { get; set; } + + internal ParameterResource? ConfigurationTagParameter { get; set; } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs index 6115f70ad2b..912767c8cd6 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionPipelineTests.cs @@ -14,112 +14,61 @@ namespace HotChocolate.Fusion.Aspire; public sealed class FusionPipelineTests { [Fact] - public void SelectDeployments_Should_SelectOnlyMatchingEnvironment() - { - var builder = DistributedApplication.CreateBuilder(); - var nitro = builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products"); - nitro - .AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithConfigurationTag("release-1"); - nitro - .AddFusionDeployment("staging") - .ForEnvironment("Staging") - .ToStage("staging") - .WithConfigurationTag("release-1"); - var model = new DistributedApplicationModel(builder.Resources); - - var deployments = FusionPipeline.SelectDeployments( - model, - "Production"); - - Assert.Equal(["production"], deployments.Select(x => x.Name)); - } - - [Fact] - public void SelectDeployments_Should_SelectDeployment_When_NoEnvironmentIsDeclared() + public async Task SelectStages_Should_SelectTheStage_That_TheParameterNames() { // arrange var builder = DistributedApplication.CreateBuilder(); - var stage = builder.AddParameter("stage", "development"); - builder + var stage = builder.AddParameter("stage", "production"); + var nitro = builder .AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") .WithNitroApiId("products") - .AddFusionDeployment("fusion") - .ToStage(stage) + .WithStageParameter(stage) .WithConfigurationTag("release-1"); + nitro.AddStage("production"); + nitro.AddStage("staging"); var model = new DistributedApplicationModel(builder.Resources); // act - var deployments = FusionPipeline.SelectDeployments(model, "Production"); - - // assert - Assert.Equal(["fusion"], deployments.Select(x => x.Name)); - } - - [Fact] - public async Task ResolveCompositionEnvironment_Should_UseStageParameter_When_NoOverrideExists() - { - // arrange - var builder = DistributedApplication.CreateBuilder(); - var stage = builder.AddParameter("stage", "development"); - var deployment = builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products") - .AddFusionDeployment("fusion") - .ToStage(stage) - .WithConfigurationTag("release-1"); - - // act - var environment = - await FusionPipelineExecutor.ResolveCompositionEnvironmentAsync( - deployment.Resource, - new GraphQLCompositionSettings(), - TestContext.Current.CancellationToken); + var stages = await FusionPipeline.SelectStagesAsync( + model, + TestContext.Current.CancellationToken); // assert - Assert.Equal("development", environment); + Assert.Equal(["production"], stages.Select(x => x.StageName)); } [Fact] - public void SelectDeployments_Should_Fail_When_TwoDeploymentsShareAStageParameter() + public async Task SelectStages_Should_Fail_When_TheStageIsNotDeclared() { // arrange var builder = DistributedApplication.CreateBuilder(); - var stage = builder.AddParameter("stage", "development"); + var stage = builder.AddParameter("stage", "prod"); var nitro = builder .AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products"); - nitro - .AddFusionDeployment("fusion-a") - .ToStage(stage) - .WithConfigurationTag("release-1"); - nitro - .AddFusionDeployment("fusion-b") - .ToStage(stage) + .WithNitroApiId("products") + .WithStageParameter(stage) .WithConfigurationTag("release-1"); + nitro.AddStage("production"); + nitro.AddStage("staging"); var model = new DistributedApplicationModel(builder.Resources); // act - var exception = Assert.Throws( - () => FusionPipeline.SelectDeployments(model, "Production")); + var exception = await Assert.ThrowsAsync( + () => FusionPipeline.SelectStagesAsync( + model, + TestContext.Current.CancellationToken)); // assert Assert.Equal( - "Multiple Fusion deployments map environment 'Production' to Nitro " - + "API 'products' stage '{stage}'.", + "Nitro target 'nitro' does not declare the stage 'prod'. " + + "Declared stages: production, staging.", exception.Message); } [Fact] - public void SelectDeployments_Should_Fail_When_NoStageIsSelected() + public void SelectTargets_Should_Fail_When_NoStageParameterIsConfigured() { // arrange var builder = DistributedApplication.CreateBuilder(); @@ -127,17 +76,17 @@ public void SelectDeployments_Should_Fail_When_NoStageIsSelected() .AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") .WithNitroApiId("products") - .AddFusionDeployment("fusion") - .WithConfigurationTag("release-1"); + .WithConfigurationTag("release-1") + .AddStage("production"); var model = new DistributedApplicationModel(builder.Resources); // act var exception = Assert.Throws( - () => FusionPipeline.SelectDeployments(model, "Production")); + () => FusionPipeline.SelectTargets(model)); // assert Assert.Equal( - "Fusion deployment 'fusion' must select a Nitro stage.", + "Nitro target 'nitro' must specify the parameter that selects the stage.", exception.Message); } @@ -158,27 +107,6 @@ public void WithCloudUrl_Should_Fail_WhenUrlContainsCaseSensitivePath() exception.Message); } - [Fact] - public void SelectDeployments_Should_ReturnEmpty_WhenEnvironmentDoesNotMatch() - { - var builder = DistributedApplication.CreateBuilder(); - builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products") - .AddFusionDeployment("production") - .ForEnvironment("Production") - .ToStage("production") - .WithConfigurationTag("release-1"); - var model = new DistributedApplicationModel(builder.Resources); - - var deployments = FusionPipeline.SelectDeployments( - model, - "Development"); - - Assert.Empty(deployments); - } - [Fact] public void GetSourceNames_Should_Fail_WhenEffectiveNamesAreDuplicated() { @@ -217,35 +145,6 @@ public void GetSourceNames_Should_Fail_WhenEffectiveNamesAreDuplicated() exception.Message); } - [Fact] - public void SelectDeployments_Should_Fail_WhenMappingIsAmbiguous() - { - var builder = DistributedApplication.CreateBuilder(); - var nitro = builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products"); - nitro - .AddFusionDeployment("production-a") - .ForEnvironment("Production") - .ToStage("production") - .WithConfigurationTag("release-1"); - nitro - .AddFusionDeployment("production-b") - .ForEnvironment("Production") - .ToStage("production") - .WithConfigurationTag("release-1"); - var model = new DistributedApplicationModel(builder.Resources); - - var exception = Assert.Throws( - () => FusionPipeline.SelectDeployments(model, "Production")); - - Assert.Equal( - "Multiple Fusion deployments map environment 'Production' to Nitro " - + "API 'products' stage 'production'.", - exception.Message); - } - [Fact] public void CreateSteps_Should_WireArtifactAndRemoteRoots() { @@ -497,20 +396,17 @@ public void ResolveSourceSchemaSettings_Should_UseDifferentEnvironmentOverrides_ } [Fact] - public async Task ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() + public void ResolveCompositionEnvironment_Should_UseStage_WhenNoOverrideExists() { - var deployment = new FusionDeploymentResource( + var deployment = new FusionStageResource( + "nitro-production", "production", - new NitroPublishTargetResource("nitro")) - { - StageName = "production" - }; + new NitroPublishTargetResource("nitro")); var environment = - await FusionPipelineExecutor.ResolveCompositionEnvironmentAsync( + FusionPipelineExecutor.ResolveCompositionEnvironment( deployment, - new GraphQLCompositionSettings(), - TestContext.Current.CancellationToken); + new GraphQLCompositionSettings()); Assert.Equal("production", environment); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs index 5f749a131eb..b1b29ec8065 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -106,7 +106,8 @@ await File.WriteAllTextAsync( CreateModel( runnerCProjects.ProductsProjectPath, runnerCProjects.ReviewsProjectPath, - runnerCProjects.GatewayProjectPath), + runnerCProjects.GatewayProjectPath, + stageName: "test"), "Test", outputPath: null, nitro: nitro); @@ -117,9 +118,9 @@ await File.WriteAllTextAsync( Assert.Equal(1, stagingSession.DeploymentCount); Assert.Equal(1, productionSession.DeploymentCount); var stagingDeployment = Assert.Single( - FusionPipeline.SelectDeployments( + await FusionPipeline.SelectStagesAsync( stagingModel, - "Development")); + TestContext.Current.CancellationToken)); var preflightState = stagingSession.GetState(stagingDeployment); Assert.Equal(0, preflightState.SourceArchiveBytes); Assert.Throws( @@ -230,7 +231,7 @@ public async Task Release_Should_PublishToTheStage_That_TheParameterSupplies() foreach (var (stage, environment) in publishes) { var context = CreateContext( - CreateParameterizedModel(projects, stage), + CreateModel(projects, stage), environment, outputPath: null, nitro: nitro); @@ -304,35 +305,35 @@ await executor.UploadAsync( NormalizeTree(artifactTree).MatchInlineSnapshot( """ fusion - fusion/development - fusion/development/nitro-deployment-template.json - fusion/development/sources - fusion/development/sources/products - fusion/development/sources/products/provenance.json - fusion/development/sources/products/schema-settings.template.json - fusion/development/sources/products/schema.graphqls - fusion/development/sources/reviews - fusion/development/sources/reviews/provenance.json - fusion/development/sources/reviews/schema-settings.template.json - fusion/development/sources/reviews/schema.graphqls + fusion/nitro + fusion/nitro/nitro-deployment-template.json + fusion/nitro/sources + fusion/nitro/sources/products + fusion/nitro/sources/products/provenance.json + fusion/nitro/sources/products/schema-settings.template.json + fusion/nitro/sources/products/schema.graphqls + fusion/nitro/sources/reviews + fusion/nitro/sources/reviews/provenance.json + fusion/nitro/sources/reviews/schema-settings.template.json + fusion/nitro/sources/reviews/schema.graphqls """); NormalizeTree(SnapshotTree(output)).MatchInlineSnapshot( """ fusion - fusion/development - fusion/development/materialized - fusion/development/materialized/products-release-1.zip - fusion/development/materialized/reviews-release-1.zip - fusion/development/nitro-deployment-template.json - fusion/development/sources - fusion/development/sources/products - fusion/development/sources/products/provenance.json - fusion/development/sources/products/schema-settings.template.json - fusion/development/sources/products/schema.graphqls - fusion/development/sources/reviews - fusion/development/sources/reviews/provenance.json - fusion/development/sources/reviews/schema-settings.template.json - fusion/development/sources/reviews/schema.graphqls + fusion/nitro + fusion/nitro/materialized + fusion/nitro/materialized/products-release-1.zip + fusion/nitro/materialized/reviews-release-1.zip + fusion/nitro/nitro-deployment-template.json + fusion/nitro/sources + fusion/nitro/sources/products + fusion/nitro/sources/products/provenance.json + fusion/nitro/sources/products/schema-settings.template.json + fusion/nitro/sources/products/schema.graphqls + fusion/nitro/sources/reviews + fusion/nitro/sources/reviews/provenance.json + fusion/nitro/sources/reviews/schema-settings.template.json + fusion/nitro/sources/reviews/schema.graphqls """); Assert.Equal( [ @@ -360,7 +361,9 @@ await CreateAppHostProjectStubsAsync(testDirectory.Path)), await executor.DownloadAsync(context, session); await executor.ComposeAsync(context, session); var deployment = Assert.Single( - FusionPipeline.SelectDeployments(context.Model, "Development")); + await FusionPipeline.SelectStagesAsync( + context.Model, + TestContext.Current.CancellationToken)); var state = session.GetState(deployment); var sourceBuffers = state.Sources .Select(source => source.Archive) @@ -407,7 +410,9 @@ await CreateAppHostProjectStubsAsync(testDirectory.Path)), await executor.DownloadAsync(context, session); await executor.ComposeAsync(context, session); var deployment = Assert.Single( - FusionPipeline.SelectDeployments(context.Model, "Development")); + await FusionPipeline.SelectStagesAsync( + context.Model, + TestContext.Current.CancellationToken)); var state = session.GetState(deployment); var sourceBuffers = state.Sources .Select(source => source.Archive) @@ -597,7 +602,9 @@ public async Task SetAll_Should_ClearOwnedBuffers_WhenCancellationPrecedesTransf var model = CreateModel( await CreateAppHostProjectStubsAsync(testDirectory.Path)); var deployment = Assert.Single( - FusionPipeline.SelectDeployments(model, "Development")); + await FusionPipeline.SelectStagesAsync( + model, + TestContext.Current.CancellationToken)); using var cancellationSource = new CancellationTokenSource(); using var session = new FusionPipelineSession( cancellationSource.Token); @@ -622,7 +629,9 @@ public async Task Cancel_Should_ClearOwnedBuffers_WhenStateWasTransferred() var model = CreateModel( await CreateAppHostProjectStubsAsync(testDirectory.Path)); var deployment = Assert.Single( - FusionPipeline.SelectDeployments(model, "Development")); + await FusionPipeline.SelectStagesAsync( + model, + TestContext.Current.CancellationToken)); using var cancellationSource = new CancellationTokenSource(); using var session = new FusionPipelineSession( cancellationSource.Token); @@ -730,55 +739,23 @@ private static IReadOnlyDictionary ReadSourceUrls( private static DistributedApplicationModel CreateModel( (string ProductsProjectPath, string ReviewsProjectPath, - string GatewayProjectPath) projects) + string GatewayProjectPath) projects, + string stageName = "development") => CreateModel( projects.ProductsProjectPath, projects.ReviewsProjectPath, - projects.GatewayProjectPath); - - private static DistributedApplicationModel CreateParameterizedModel( - (string ProductsProjectPath, - string ReviewsProjectPath, - string GatewayProjectPath) projects, - string stageName) - { - var builder = DistributedApplication.CreateBuilder(); - var tag = builder.AddParameter("tag", "release-1"); - var stage = builder.AddParameter("stage", stageName); - var apiKey = builder.AddParameter( - "nitroApiKey", - "test-api-key", - secret: true); - var products = builder - .AddProject("products", projects.ProductsProjectPath) - .WithGraphQLSchemaFile(); - var reviews = builder - .AddProject("reviews", projects.ReviewsProjectPath) - .WithGraphQLSchemaFile(); - builder - .AddProject("gateway", projects.GatewayProjectPath) - .WithReference(products) - .WithReference(reviews) - .WithGraphQLSchemaComposition(); - builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl("https://api.chillicream.com") - .WithNitroApiId("products") - .WithNitroApiKey(apiKey) - .AddFusionDeployment("fusion") - .ToStage(stage) - .WithConfigurationTag(tag); - - return new DistributedApplicationModel(builder.Resources); - } + projects.GatewayProjectPath, + stageName); private static DistributedApplicationModel CreateModel( string productsProjectPath, string reviewsProjectPath, - string gatewayProjectPath) + string gatewayProjectPath, + string stageName = "development") { var builder = DistributedApplication.CreateBuilder(); var tag = builder.AddParameter("tag", "release-1"); + var stage = builder.AddParameter("stage", stageName); var apiKey = builder.AddParameter( "nitroApiKey", "test-api-key", @@ -798,19 +775,15 @@ private static DistributedApplicationModel CreateModel( .AddNitroPublishTarget("nitro") .WithNitroCloudUrl("https://api.chillicream.com") .WithNitroApiId("products") - .WithNitroApiKey(apiKey); - nitro - .AddFusionDeployment("development") - .ForEnvironment("Development") - .ToStage("development") - .WithCompositionEnvironment("development") + .WithNitroApiKey(apiKey) + .WithStageParameter(stage) .WithConfigurationTag(tag); nitro - .AddFusionDeployment("test") - .ForEnvironment("Test") - .ToStage("test") - .WithCompositionEnvironment("test") - .WithConfigurationTag(tag); + .AddStage("development") + .WithCompositionEnvironment("development"); + nitro + .AddStage("test") + .WithCompositionEnvironment("test"); return new DistributedApplicationModel(builder.Resources); } From f50be1da683fdac30dcda6b0ac6590a88ebe29e8 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:30:20 +0200 Subject: [PATCH 10/11] Remove example --- .../.github/workflows/deploy.yml | 149 ------------------ examples/FusionReleasePipeline/.gitignore | 6 - .../Directory.Build.props | 9 -- .../Directory.Packages.props | 13 -- .../FusionReleasePipeline.slnx | 8 - examples/FusionReleasePipeline/README.md | 112 ------------- .../src/AppHost/AppHost.csproj | 21 --- .../src/AppHost/Program.cs | 63 -------- .../AppHost/Properties/launchSettings.json | 20 --- .../src/Gateway/Gateway.csproj | 11 -- .../src/Gateway/Program.cs | 50 ------ .../Gateway/Properties/launchSettings.json | 14 -- .../src/Products/Products.csproj | 9 -- .../src/Products/Program.cs | 32 ---- .../Products/Properties/launchSettings.json | 14 -- .../src/Products/schema-settings.json | 19 --- .../src/Products/schema.graphqls | 14 -- .../src/Reviews/Program.cs | 33 ---- .../Reviews/Properties/launchSettings.json | 14 -- .../src/Reviews/Reviews.csproj | 9 -- .../src/Reviews/schema-settings.json | 19 --- .../src/Reviews/schema.graphqls | 15 -- 22 files changed, 654 deletions(-) delete mode 100644 examples/FusionReleasePipeline/.github/workflows/deploy.yml delete mode 100644 examples/FusionReleasePipeline/.gitignore delete mode 100644 examples/FusionReleasePipeline/Directory.Build.props delete mode 100644 examples/FusionReleasePipeline/Directory.Packages.props delete mode 100644 examples/FusionReleasePipeline/FusionReleasePipeline.slnx delete mode 100644 examples/FusionReleasePipeline/README.md delete mode 100644 examples/FusionReleasePipeline/src/AppHost/AppHost.csproj delete mode 100644 examples/FusionReleasePipeline/src/AppHost/Program.cs delete mode 100644 examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json delete mode 100644 examples/FusionReleasePipeline/src/Gateway/Gateway.csproj delete mode 100644 examples/FusionReleasePipeline/src/Gateway/Program.cs delete mode 100644 examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json delete mode 100644 examples/FusionReleasePipeline/src/Products/Products.csproj delete mode 100644 examples/FusionReleasePipeline/src/Products/Program.cs delete mode 100644 examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json delete mode 100644 examples/FusionReleasePipeline/src/Products/schema-settings.json delete mode 100644 examples/FusionReleasePipeline/src/Products/schema.graphqls delete mode 100644 examples/FusionReleasePipeline/src/Reviews/Program.cs delete mode 100644 examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json delete mode 100644 examples/FusionReleasePipeline/src/Reviews/Reviews.csproj delete mode 100644 examples/FusionReleasePipeline/src/Reviews/schema-settings.json delete mode 100644 examples/FusionReleasePipeline/src/Reviews/schema.graphqls diff --git a/examples/FusionReleasePipeline/.github/workflows/deploy.yml b/examples/FusionReleasePipeline/.github/workflows/deploy.yml deleted file mode 100644 index a56e2d37448..00000000000 --- a/examples/FusionReleasePipeline/.github/workflows/deploy.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: Deploy Fusion release - -on: - workflow_dispatch: - push: - branches: - - main - -permissions: - contents: read - -env: - DEMO_ROOT: examples/FusionReleasePipeline - APPHOST_PROJECT: examples/FusionReleasePipeline/src/AppHost/AppHost.csproj - RELEASE_TAG: ${{ github.sha }} - -jobs: - fusion-build: - name: Build and upload Fusion release - runs-on: ubuntu-latest - permissions: - contents: read - concurrency: - group: fusion-nitro-example-invalid-replace-with-nitro-api-id-upload - cancel-in-progress: false - env: - Parameters__tag: ${{ env.RELEASE_TAG }} - Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 10.0.x - 11.0.100-preview.6.26359.118 - - - name: Install Aspire CLI - run: dotnet tool install --global Aspire.Cli --version 13.4.6 - - - name: Restore - run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" - - - name: Build - run: dotnet build "$DEMO_ROOT/FusionReleasePipeline.slnx" --no-restore - - - name: Upload immutable Fusion sources - run: >- - aspire do fusion-upload - --apphost "$APPHOST_PROJECT" - --non-interactive - - deploy-development: - name: Deploy Development - needs: fusion-build - runs-on: ubuntu-latest - environment: Development - permissions: - contents: read - id-token: write - concurrency: - group: fusion-nitro-example-invalid-replace-with-nitro-api-id-development - cancel-in-progress: false - env: - Azure__CredentialSource: AzureCli - Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} - Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} - Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} - Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} - Parameters__tag: ${{ env.RELEASE_TAG }} - Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} - Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 10.0.x - 11.0.100-preview.6.26359.118 - - - name: Install Aspire CLI - run: dotnet tool install --global Aspire.Cli --version 13.4.6 - - - name: Sign in to Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.DEMO_AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.DEMO_AZURE_TENANT_ID }} - subscription-id: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} - - - name: Restore - run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" - - - name: Deploy Development - run: >- - aspire do fusion-publish - --apphost "$APPHOST_PROJECT" - --non-interactive - - deploy-test: - name: Deploy Test - needs: - - fusion-build - - deploy-development - runs-on: ubuntu-latest - environment: Test - permissions: - contents: read - id-token: write - concurrency: - group: fusion-nitro-example-invalid-replace-with-nitro-api-id-test - cancel-in-progress: false - env: - Azure__CredentialSource: AzureCli - Azure__Location: ${{ vars.DEMO_AZURE_LOCATION }} - Azure__ResourceGroup: ${{ vars.DEMO_AZURE_RESOURCE_GROUP }} - Azure__SubscriptionId: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} - Parameters__stage: ${{ vars.DEMO_NITRO_STAGE }} - Parameters__tag: ${{ env.RELEASE_TAG }} - Parameters__nitroApiKey: ${{ secrets.DEMO_NITRO_API_KEY }} - Parameters__nitroGatewayApiKey: ${{ secrets.DEMO_NITRO_GATEWAY_API_KEY }} - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 10.0.x - 11.0.100-preview.6.26359.118 - - - name: Install Aspire CLI - run: dotnet tool install --global Aspire.Cli --version 13.4.6 - - - name: Sign in to Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.DEMO_AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.DEMO_AZURE_TENANT_ID }} - subscription-id: ${{ secrets.DEMO_AZURE_SUBSCRIPTION_ID }} - - - name: Restore - run: dotnet restore "$DEMO_ROOT/FusionReleasePipeline.slnx" - - - name: Deploy Test - run: >- - aspire do fusion-publish - --apphost "$APPHOST_PROJECT" - --non-interactive diff --git a/examples/FusionReleasePipeline/.gitignore b/examples/FusionReleasePipeline/.gitignore deleted file mode 100644 index 676d86a1620..00000000000 --- a/examples/FusionReleasePipeline/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -**/bin/ -**/obj/ -.aspire/ -aspire-output/ -aspire.config.json -src/Gateway/gateway.far diff --git a/examples/FusionReleasePipeline/Directory.Build.props b/examples/FusionReleasePipeline/Directory.Build.props deleted file mode 100644 index 989f54b608f..00000000000 --- a/examples/FusionReleasePipeline/Directory.Build.props +++ /dev/null @@ -1,9 +0,0 @@ - - - net10.0 - enable - enable - true - NU1901;NU1902;NU1903;NU1904 - - diff --git a/examples/FusionReleasePipeline/Directory.Packages.props b/examples/FusionReleasePipeline/Directory.Packages.props deleted file mode 100644 index 4fdb9eb3261..00000000000 --- a/examples/FusionReleasePipeline/Directory.Packages.props +++ /dev/null @@ -1,13 +0,0 @@ - - - true - - - - - - - - - - diff --git a/examples/FusionReleasePipeline/FusionReleasePipeline.slnx b/examples/FusionReleasePipeline/FusionReleasePipeline.slnx deleted file mode 100644 index 23d71096442..00000000000 --- a/examples/FusionReleasePipeline/FusionReleasePipeline.slnx +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/FusionReleasePipeline/README.md b/examples/FusionReleasePipeline/README.md deleted file mode 100644 index 2641129e431..00000000000 --- a/examples/FusionReleasePipeline/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# Fusion release pipeline example - -This sample contains two Hot Chocolate source schemas, a Fusion gateway, an Aspire AppHost, and a -GitHub Actions workflow using the split Fusion release commands. - -The AppHost and services reference the current `graphql-platform` checkout. Checked-in -`schema.graphqls` and `schema-settings.json` files make the upload input deterministic. - -## Before a real deployment - -Every remote value is intentionally fake. Replace: - -- `https://nitro.example.invalid`; -- `replace-with-nitro-api-id`; and -- the Development and Test source URLs in both `schema-settings.json` files. - -Provide `DEMO_NITRO_API_KEY` as a repository or organization secret for the upload job. Create the -GitHub environments `Development` and `Test`, then configure these environment secrets: - -- `DEMO_NITRO_GATEWAY_API_KEY`; -- `DEMO_AZURE_CLIENT_ID`; -- `DEMO_AZURE_TENANT_ID`; and -- `DEMO_AZURE_SUBSCRIPTION_ID`. - -Configure `DEMO_NITRO_STAGE`, `DEMO_AZURE_LOCATION`, and `DEMO_AZURE_RESOURCE_GROUP` as environment -variables. `DEMO_NITRO_STAGE` carries the Nitro stage of each GitHub environment and selects one of -the stages the AppHost declares. The upload job needs no stage at all. The sample uses Azure -Container Apps because it contributes the `DeployCompute` steps needed to prove source and gateway -ordering. Development and Test should normally use distinct resource groups. - -The sources and gateway use external HTTP ingress. The deployment runner must reach the configured -source URLs for readiness polling. The committed `.invalid` URLs deliberately fail a real release. - -The nested `.github/workflows/deploy.yml` is documentation while this example is in the monorepo. -GitHub discovers workflows only from the repository-root `.github/workflows` directory. - -## Run locally - -From the repository root: - -```shell -dotnet restore examples/FusionReleasePipeline/FusionReleasePipeline.slnx -dotnet build examples/FusionReleasePipeline/FusionReleasePipeline.slnx --no-restore -dotnet run --project examples/FusionReleasePipeline/src/AppHost/AppHost.csproj -``` - -Run mode composes `examples/FusionReleasePipeline/src/Gateway/gateway.far` with the `local` -settings environment and starts: - -- gateway at `http://localhost:5100/graphql`; -- products at `http://localhost:5101/graphql`; and -- reviews at `http://localhost:5102/graphql`. - -Run mode composes for `local` and injects no `NITRO_*` variables, so the gateway uses its local FAR. -A publish composes for the stage that the `stage` parameter names and passes that same stage to the -gateway as `NITRO_STAGE`. - -## Release flow - -All jobs use one `RELEASE_TAG`, exposed to the AppHost as `Parameters__tag`. Only the deployment -jobs also set `Parameters__stage`, because an immutable source version serves every stage. The build -job uploads both sources as exact immutable versions: - -```shell -export Parameters__tag="$RELEASE_TAG" -export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" - -aspire do fusion-upload \ - --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ - --non-interactive -``` - -Development and Test are two stages of one Nitro api, so this single upload serves both. An AppHost -that declares several apis uploads each of them in the same invocation. - -The deployment job checks out the same revision and publishes without a manifest or CI artifact: - -```shell -export Parameters__stage="$DEMO_NITRO_STAGE" -export Parameters__tag="$RELEASE_TAG" -export Parameters__nitroApiKey="$DEMO_NITRO_API_KEY" -export Parameters__nitroGatewayApiKey="$DEMO_NITRO_GATEWAY_API_KEY" -export Azure__CredentialSource=AzureCli -export Azure__SubscriptionId="$DEMO_AZURE_SUBSCRIPTION_ID" -export Azure__ResourceGroup="$DEMO_AZURE_RESOURCE_GROUP" -export Azure__Location="$DEMO_AZURE_LOCATION" - -aspire do fusion-publish \ - --apphost examples/FusionReleasePipeline/src/AppHost/AppHost.csproj \ - --non-interactive -``` - -`fusion-publish` infers `products` and `reviews` from the AppHost, downloads the exact source -versions `products@RELEASE_TAG` and `reviews@RELEASE_TAG` as a metadata-only preflight. After source -deployment it downloads them again, verifies the same canonical digests, and composes the -Development endpoints. - -The Test job uses the same tag with the Test `DEMO_NITRO_STAGE`, which composes the Test endpoints -and publishes to the Test stage. - -There is no artifact upload/download between jobs. The Fusion-specific publish steps never export -schemas, upload source versions, write Fusion apply-state files, or resolve Aspire's output-path -service. Exact archives and the FAR remain in bounded invocation memory and are cleared after -completion. Azure deployment dependencies can still write target artifacts and Aspire deployment -state according to the provider configuration. The safe order is exact preflight, source -deployment, exact re-download and composition, source readiness, internal Nitro stage publication, -gateway deployment, then terminal public `fusion-publish`. - -The build job has a stable concurrency key for the Nitro API. Each deployment job has a stable key -for its GitHub environment, which is the one stage and Azure target that environment writes. -`cancel-in-progress: false` queues writers. Add external locking when another repository or -deployment system can write the same Nitro stage or compute target. diff --git a/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj b/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj deleted file mode 100644 index 6ae8786528e..00000000000 --- a/examples/FusionReleasePipeline/src/AppHost/AppHost.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - true - FusionReleasePipeline.AppHost - - - - - - - - - - - - diff --git a/examples/FusionReleasePipeline/src/AppHost/Program.cs b/examples/FusionReleasePipeline/src/AppHost/Program.cs deleted file mode 100644 index 07c6c2583c2..00000000000 --- a/examples/FusionReleasePipeline/src/AppHost/Program.cs +++ /dev/null @@ -1,63 +0,0 @@ -using HotChocolate.Fusion.Aspire; - -const string nitroCloudUrl = "https://nitro.example.invalid"; -const string nitroApiId = "replace-with-nitro-api-id"; - -var builder = DistributedApplication.CreateBuilder(args); - -builder.AddNitro(); -builder.AddAzureContainerAppEnvironment("demo-aca"); - -// every stage this api publishes to is declared here by name. An invocation names one of them -// through the stage parameter, and the release tag identifies the release across all of them. -var stage = builder.AddParameter("stage"); -var tag = builder.AddParameter("tag"); -var nitroApiKey = builder.AddParameter("nitroApiKey", secret: true); - -var products = builder - .AddProject("products") - .WithExternalHttpEndpoints() - .WithGraphQLSchemaFile(); - -var reviews = builder - .AddProject("reviews") - .WithExternalHttpEndpoints() - .WithGraphQLSchemaFile(); - -var gateway = builder - .AddProject("gateway") - .WithExternalHttpEndpoints() - .WithGraphQLSchemaComposition( - settings: new GraphQLCompositionSettings - { - // a publish composes for the stage it publishes to, a local run composes for "local". - EnvironmentName = builder.ExecutionContext.IsRunMode ? "local" : null - }) - .WithReference(products) - .WithReference(reviews); - -var nitro = builder - .AddNitroPublishTarget("nitro") - .WithNitroCloudUrl(nitroCloudUrl) - .WithNitroApiId(nitroApiId) - .WithNitroApiKey(nitroApiKey) - .WithStageParameter(stage) - .WithConfigurationTag(tag); - -nitro.AddStage("development"); -nitro.AddStage("test"); - -if (!builder.ExecutionContext.IsRunMode) -{ - var nitroGatewayApiKey = builder.AddParameter( - "nitroGatewayApiKey", - secret: true); - - gateway - .WithEnvironment("NITRO_URL", nitroCloudUrl) - .WithEnvironment("NITRO_API_ID", nitroApiId) - .WithEnvironment("NITRO_STAGE", stage) - .WithEnvironment("NITRO_API_KEY", nitroGatewayApiKey); -} - -builder.Build().Run(); diff --git a/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json deleted file mode 100644 index 8a07dc9196a..00000000000 --- a/examples/FusionReleasePipeline/src/AppHost/Properties/launchSettings.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:15210", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19210", - "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20210", - "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true", - "Parameters__tag": "local-release", - "Parameters__nitroApiKey": "replace-with-a-real-nitro-api-key" - } - } - } -} diff --git a/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj b/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj deleted file mode 100644 index fc8dcaf8fb3..00000000000 --- a/examples/FusionReleasePipeline/src/Gateway/Gateway.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - FusionReleasePipeline.Gateway - - - - - - - - diff --git a/examples/FusionReleasePipeline/src/Gateway/Program.cs b/examples/FusionReleasePipeline/src/Gateway/Program.cs deleted file mode 100644 index 6a542f62613..00000000000 --- a/examples/FusionReleasePipeline/src/Gateway/Program.cs +++ /dev/null @@ -1,50 +0,0 @@ -using ChilliCream.Nitro; -using ChilliCream.Nitro.Fusion; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddHttpClient("Fusion"); - -var nitroApiId = builder.Configuration["NITRO_API_ID"]; -var nitroApiKey = builder.Configuration["NITRO_API_KEY"]; -var nitroStage = builder.Configuration["NITRO_STAGE"]; -var nitroUrl = builder.Configuration["NITRO_URL"]; -var useNitro = !string.IsNullOrWhiteSpace(nitroApiId) - || !string.IsNullOrWhiteSpace(nitroApiKey) - || !string.IsNullOrWhiteSpace(nitroStage) - || !string.IsNullOrWhiteSpace(nitroUrl); - -if (useNitro) -{ - if (string.IsNullOrWhiteSpace(nitroApiId) - || string.IsNullOrWhiteSpace(nitroApiKey) - || string.IsNullOrWhiteSpace(nitroStage) - || string.IsNullOrWhiteSpace(nitroUrl)) - { - throw new InvalidOperationException( - "NITRO_URL, NITRO_API_ID, NITRO_API_KEY, and NITRO_STAGE " - + "must all be configured for a deployed gateway."); - } - - builder.Services - .AddNitro(options => - { - options.ApiId = nitroApiId; - options.ApiKey = nitroApiKey; - options.Stage = nitroStage; - options.ServerUrl = nitroUrl; - }) - .AddFusion(); -} -else -{ - builder - .AddGraphQLGateway() - .AddFileSystemConfiguration("gateway.far"); -} - -var app = builder.Build(); - -app.MapGraphQLHttp(); - -app.Run(); diff --git a/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json deleted file mode 100644 index 411cd328b7c..00000000000 --- a/examples/FusionReleasePipeline/src/Gateway/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5100", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/examples/FusionReleasePipeline/src/Products/Products.csproj b/examples/FusionReleasePipeline/src/Products/Products.csproj deleted file mode 100644 index 76ce2bdf02f..00000000000 --- a/examples/FusionReleasePipeline/src/Products/Products.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - FusionReleasePipeline.Products - - - - - - diff --git a/examples/FusionReleasePipeline/src/Products/Program.cs b/examples/FusionReleasePipeline/src/Products/Program.cs deleted file mode 100644 index 08bd0e6ec6e..00000000000 --- a/examples/FusionReleasePipeline/src/Products/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using HotChocolate.Types.Relay; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services - .AddGraphQLServer() - .AddQueryType(); - -var app = builder.Build(); - -app.MapGraphQL(); - -return await app.RunWithGraphQLCommandsAsync(args); - -public sealed class Query -{ - private static readonly Product[] s_products = - [ - new("p-1", "Mechanical Keyboard", 149.00), - new("p-2", "GraphQL Mug", 18.50) - ]; - - public IReadOnlyList GetProducts() => s_products; - - public Product? GetProduct([ID] string id) - => s_products.FirstOrDefault(product => product.Id == id); -} - -public sealed record Product( - [property: ID] string Id, - string Name, - double Price); diff --git a/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json deleted file mode 100644 index a8c43d30861..00000000000 --- a/examples/FusionReleasePipeline/src/Products/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5101", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/examples/FusionReleasePipeline/src/Products/schema-settings.json b/examples/FusionReleasePipeline/src/Products/schema-settings.json deleted file mode 100644 index c9e7cec8217..00000000000 --- a/examples/FusionReleasePipeline/src/Products/schema-settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "products", - "transports": { - "http": { - "url": "{{PRODUCTS_URL}}/graphql" - } - }, - "environments": { - "local": { - "PRODUCTS_URL": "http://localhost:5101" - }, - "development": { - "PRODUCTS_URL": "https://products.development.example.invalid" - }, - "test": { - "PRODUCTS_URL": "https://products.test.example.invalid" - } - } -} diff --git a/examples/FusionReleasePipeline/src/Products/schema.graphqls b/examples/FusionReleasePipeline/src/Products/schema.graphqls deleted file mode 100644 index 85c74ceb631..00000000000 --- a/examples/FusionReleasePipeline/src/Products/schema.graphqls +++ /dev/null @@ -1,14 +0,0 @@ -schema { - query: Query -} - -type Product { - id: ID! - name: String! - price: Float! -} - -type Query { - product(id: ID!): Product - products: [Product!]! -} diff --git a/examples/FusionReleasePipeline/src/Reviews/Program.cs b/examples/FusionReleasePipeline/src/Reviews/Program.cs deleted file mode 100644 index 20fc476e8f6..00000000000 --- a/examples/FusionReleasePipeline/src/Reviews/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -using HotChocolate.Types.Relay; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services - .AddGraphQLServer() - .AddQueryType(); - -var app = builder.Build(); - -app.MapGraphQL(); - -return await app.RunWithGraphQLCommandsAsync(args); - -public sealed class Query -{ - private static readonly Review[] s_reviews = - [ - new("r-1", "p-1", 5, "Excellent for long coding sessions."), - new("r-2", "p-2", 4, "A dependable mug with a good handle.") - ]; - - public IReadOnlyList GetReviews() => s_reviews; - - public Review? GetReview([ID] string id) - => s_reviews.FirstOrDefault(review => review.Id == id); -} - -public sealed record Review( - [property: ID] string Id, - [property: ID] string ProductId, - int Stars, - string Commentary); diff --git a/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json b/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json deleted file mode 100644 index 8e46e0dbcc7..00000000000 --- a/examples/FusionReleasePipeline/src/Reviews/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5102", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj b/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj deleted file mode 100644 index ff93d35d023..00000000000 --- a/examples/FusionReleasePipeline/src/Reviews/Reviews.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - FusionReleasePipeline.Reviews - - - - - - diff --git a/examples/FusionReleasePipeline/src/Reviews/schema-settings.json b/examples/FusionReleasePipeline/src/Reviews/schema-settings.json deleted file mode 100644 index b0e6d519388..00000000000 --- a/examples/FusionReleasePipeline/src/Reviews/schema-settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "reviews", - "transports": { - "http": { - "url": "{{REVIEWS_URL}}/graphql" - } - }, - "environments": { - "local": { - "REVIEWS_URL": "http://localhost:5102" - }, - "development": { - "REVIEWS_URL": "https://reviews.development.example.invalid" - }, - "test": { - "REVIEWS_URL": "https://reviews.test.example.invalid" - } - } -} diff --git a/examples/FusionReleasePipeline/src/Reviews/schema.graphqls b/examples/FusionReleasePipeline/src/Reviews/schema.graphqls deleted file mode 100644 index db15f7a4282..00000000000 --- a/examples/FusionReleasePipeline/src/Reviews/schema.graphqls +++ /dev/null @@ -1,15 +0,0 @@ -schema { - query: Query -} - -type Query { - review(id: ID!): Review - reviews: [Review!]! -} - -type Review { - commentary: String! - id: ID! - productId: ID! - stars: Int! -} From 74354192d451391c6c79633ff35630a246705658 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:47:56 +0200 Subject: [PATCH 11/11] Consume Nitro stage composition settings in Aspire fusion publish --- .../Fusion.Aspire/AspireCompositionHelper.cs | 49 +++- .../Fusion.Aspire/FusionPipelineExecutor.cs | 12 + .../Nitro/FusionDeploymentWorkflow.cs | 48 ++++ .../FusionOperationUnsupportedException.cs | 12 + .../src/Fusion.Aspire/Nitro/NitroFusionApi.cs | 138 +++++++++++ .../src/Fusion.Aspire/Nitro/NitroGraphQL.cs | 21 +- .../Fusion.Aspire/Nitro/NitroGraphQLResult.cs | 16 +- .../Nitro/NitroOperationDocuments.cs | 19 ++ .../Nitro/NitroStageCompositionSettings.cs | 60 +++++ .../Operations/BeginFusionDeployment.graphql | 4 +- .../BeginFusionDeployment.graphql.sha256 | 2 +- .../Operations/ClaimFusionDeployment.graphql | 4 +- .../ClaimFusionDeployment.graphql.sha256 | 2 +- .../Operations/CommitFusionDeployment.graphql | 4 +- .../CommitFusionDeployment.graphql.sha256 | 2 +- .../GetFusionStageCompositionSettings.graphql | 14 ++ ...ionStageCompositionSettings.graphql.sha256 | 1 + .../ReleaseFusionDeployment.graphql | 4 +- .../ReleaseFusionDeployment.graphql.sha256 | 2 +- .../UploadFusionSourceSchema.graphql | 4 +- .../UploadFusionSourceSchema.graphql.sha256 | 2 +- .../ValidateFusionDeployment.graphql | 4 +- .../ValidateFusionDeployment.graphql.sha256 | 2 +- .../AspireCompositionHelperTests.cs | 133 ++++++++++- .../FusionReleaseAcceptanceTests.cs | 13 + .../Nitro/NitroOperationDocumentsTests.cs | 25 +- .../NitroStageCompositionSettingsTests.cs | 225 ++++++++++++++++++ 27 files changed, 791 insertions(+), 31 deletions(-) create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionOperationUnsupportedException.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroStageCompositionSettings.cs create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql create mode 100644 src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql.sha256 create mode 100644 src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroStageCompositionSettingsTests.cs diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs index 8592bf29035..d92de3d5559 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/AspireCompositionHelper.cs @@ -60,6 +60,7 @@ public static async Task TryComposeArchivesAsync( [.. sourceSchemas], environmentName, settings, + stageSettings: null, SettingsComposerOptions.Default, logger, cancellationToken); @@ -73,11 +74,38 @@ public static async Task TryComposeArchivesAsync( } } + /// + /// Composes the given source schema archives into a fusion archive, resolving the source + /// schema settings against . + /// + /// + /// The stream that the composed fusion archive is written to. + /// + /// + /// The source schema archives that are composed. + /// + /// + /// The environment that the settings of the source schemas resolve against. + /// + /// + /// The composition settings of the gateway. + /// + /// + /// The composition settings that the deployment target declares. They only fill values that + /// leaves unset. + /// + /// + /// The logger that receives the composition diagnostics. + /// + /// + /// The cancellation token. + /// internal static async Task TryComposeArchivesAsync( Stream fusionArchive, IReadOnlyList archives, string environmentName, GraphQLCompositionSettings settings, + CompositionSettings? stageSettings, ILogger logger, CancellationToken cancellationToken) { @@ -98,6 +126,7 @@ internal static async Task TryComposeArchivesAsync( [.. sourceSchemas], environmentName, settings, + stageSettings, SettingsComposerOptions.Default, logger, cancellationToken); @@ -216,6 +245,7 @@ public static Task TryComposeAsync( newSourceSchemas, environment, settings, + stageSettings: null, settingsComposerOptions, logger, cancellationToken); @@ -227,6 +257,7 @@ private static async Task ComposeAsync( ImmutableArray newSourceSchemas, string environment, GraphQLCompositionSettings settings, + CompositionSettings? stageSettings, SettingsComposerOptions settingsComposerOptions, ILogger logger, CancellationToken cancellationToken) @@ -240,6 +271,7 @@ private static async Task ComposeAsync( newSourceSchemas, environment, settings, + stageSettings, settingsComposerOptions, logger, cancellationToken); @@ -250,6 +282,7 @@ private static async Task ComposeAsync( ImmutableArray newSourceSchemas, string environment, GraphQLCompositionSettings settings, + CompositionSettings? stageSettings, SettingsComposerOptions settingsComposerOptions, ILogger logger, CancellationToken cancellationToken) @@ -257,7 +290,7 @@ private static async Task ComposeAsync( ArgumentException.ThrowIfNullOrWhiteSpace(environment); var compositionLog = new CompositionLog(); - var compositionSettings = CreateCompositionSettings(settings); + var compositionSettings = CreateCompositionSettings(settings, stageSettings); var sourceSchemas = newSourceSchemas.ToDictionary( s => s.Name, s => (s.Schema, s.SchemaSettings)); @@ -417,10 +450,16 @@ internal static JsonDocument ResolveSourceSchemaSettings( return JsonSerializer.SerializeToDocument(resolvedSettings); } + /// + /// Creates the composition settings of a composition. The settings that the distributed + /// application declares win, only fills the values that they + /// leave unset. + /// internal static CompositionSettings CreateCompositionSettings( - GraphQLCompositionSettings settings) + GraphQLCompositionSettings settings, + CompositionSettings? stageSettings) { - return new CompositionSettings + var localSettings = new CompositionSettings { Merger = { @@ -437,6 +476,10 @@ internal static CompositionSettings CreateCompositionSettings( }, Preprocessor = { ExcludeByTag = settings.ExcludeByTag?.ToHashSet() } }; + + return stageSettings is null + ? localSettings + : localSettings.MergeInto(stageSettings); } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs index 1354864a54d..ad72e9f792c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/FusionPipelineExecutor.cs @@ -403,6 +403,8 @@ public async Task ComposeAsync( return; } + var workflow = context.Services + .GetRequiredService(); var compositionResource = FusionPipeline.GetCompositionResource( context.Model); var currentComposition = GraphQLResourceModel.GetComposition( @@ -425,6 +427,15 @@ await ValidateSessionStateAsync( await using var farStream = new MemoryStream(); var logger = context.Services .GetRequiredService>(); + var target = await ResolveTargetAsync( + deployment, + context, + context.CancellationToken); + var stageSettings = await workflow.TryGetStageCompositionSettingsAsync( + target, + deployment.StageName, + logger, + context.CancellationToken); if (!await AspireCompositionHelper.TryComposeArchivesAsync( farStream, state.Sources.Select( @@ -434,6 +445,7 @@ await ValidateSessionStateAsync( .ToArray(), compositionEnvironment, currentComposition.Settings, + stageSettings?.ToCompositionSettings(), logger, context.CancellationToken)) { diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs index 2a2cca4948b..dbdb048306b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionDeploymentWorkflow.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using Microsoft.Extensions.Logging; namespace HotChocolate.Fusion.Aspire.Nitro; @@ -56,6 +57,53 @@ internal sealed class FusionDeploymentWorkflow(NitroFusionApi api) } } + /// + /// Reads the composition settings that a stage declares. A Nitro server that does not know + /// the operation is reported as a warning and yields null, so that the composition + /// continues with the settings of the distributed application alone. + /// + /// + /// The Nitro api that the stage belongs to. + /// + /// + /// The name of the stage. + /// + /// + /// The logger that receives a warning when the Nitro server does not support the operation. + /// + /// + /// The cancellation token. + /// + public async Task TryGetStageCompositionSettingsAsync( + FusionTarget target, + string stageName, + ILogger logger, + CancellationToken cancellationToken) + { + ValidateTarget(target); + ArgumentException.ThrowIfNullOrWhiteSpace(stageName); + ArgumentNullException.ThrowIfNull(logger); + + try + { + return await api.GetStageCompositionSettingsAsync( + target, + stageName, + cancellationToken); + } + catch (FusionOperationUnsupportedException exception) + { + logger.LogWarning( + "The composition settings of stage {StageName} could not be downloaded, so the " + + "composition only uses the settings of the distributed application. Update the " + + "Nitro server to the latest version. {Message}", + stageName, + exception.Message); + + return null; + } + } + /// /// Ensures that an immutable source schema version exists with the expected content. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionOperationUnsupportedException.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionOperationUnsupportedException.cs new file mode 100644 index 00000000000..e827241cf89 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/FusionOperationUnsupportedException.cs @@ -0,0 +1,12 @@ +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// The Nitro server does not know an operation that the Fusion deployment workflow sends. +/// +internal sealed class FusionOperationUnsupportedException : FusionDeploymentException +{ + public FusionOperationUnsupportedException(string message) + : base(message) + { + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs index 28188154bcb..accb5b893e3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroFusionApi.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json; +using HotChocolate.Fusion.Options; using HotChocolate.Transport; using HotChocolate.Transport.Http; @@ -22,6 +23,11 @@ internal sealed class NitroFusionApi : IDisposable private const string CommittedConfigurationFileName = "gateway.far"; private const string ArchiveContentType = "application/zip"; + /// + /// The GraphQL error code that a server reports for a field that its schema does not define. + /// + private const string UnknownFieldErrorCode = "HC0020"; + private readonly HttpClient _httpClient; private readonly bool _disposeHttpClient; private readonly GraphQLHttpClient _client; @@ -138,6 +144,62 @@ public async Task UploadSourceSchemaAsync( errors); } + /// + /// Reads the composition settings that a stage declares, or returns null when the api, + /// the stage, or its composition settings do not exist. + /// + /// + /// The Nitro server does not know the operation. + /// + /// + /// Nitro could not be reached, or it rejected the request. + /// + public async Task GetStageCompositionSettingsAsync( + FusionTarget target, + string stageName, + CancellationToken cancellationToken) + { + var variables = new Dictionary + { + ["apiId"] = target.ApiId, + ["stageName"] = stageName + }; + + var result = await NitroGraphQL.SendWithRetryAsync( + _client, + CreateRequest( + target, + NitroOperationDocuments.GetStageCompositionSettingsOperationName, + variables, + enableFileUploads: false), + // reading the composition settings has no effect, so it is safe to send again. + retryTransientFailures: true, + NitroGraphQL.DefaultRequestTimeout, + cancellationToken); + + if (result.Failure is not null) + { + // a Nitro server that predates the stage composition settings answers with a field + // validation error, which is the only failure the caller can compose without. + throw result.FailureCode is UnknownFieldErrorCode + ? new FusionOperationUnsupportedException(result.Failure) + : new FusionDeploymentException(result.Failure); + } + + if (result.Data.ValueKind is not JsonValueKind.Object + || !result.Data.TryGetProperty("apiById", out var api) + || api.ValueKind is not JsonValueKind.Object + || !api.TryGetProperty("stage", out var stage) + || stage.ValueKind is not JsonValueKind.Object + || !stage.TryGetProperty("compositionSettings", out var settings) + || settings.ValueKind is not JsonValueKind.Object) + { + return null; + } + + return ReadStageCompositionSettings(settings); + } + /// /// Opens a publication request. /// @@ -355,6 +417,8 @@ private static OperationRequest CreateBody( => NitroOperationDocuments.GetReleaseDeploymentOperationId(), NitroOperationDocuments.WatchDeploymentOperationName => NitroOperationDocuments.GetWatchDeploymentOperationId(), + NitroOperationDocuments.GetStageCompositionSettingsOperationName + => NitroOperationDocuments.GetStageCompositionSettingsOperationId(), _ => throw UnknownOperation(operationName) }; @@ -379,6 +443,8 @@ private static OperationRequest CreateBody( => NitroOperationDocuments.GetReleaseDeploymentDocument(), NitroOperationDocuments.WatchDeploymentOperationName => NitroOperationDocuments.GetWatchDeploymentDocument(), + NitroOperationDocuments.GetStageCompositionSettingsOperationName + => NitroOperationDocuments.GetStageCompositionSettingsDocument(), _ => throw UnknownOperation(operationName) }; @@ -459,6 +525,78 @@ or FusionRemoteEventKind.ValidationFailed return new FusionRemoteEvent(kind, errors); } + private static NitroStageCompositionSettings ReadStageCompositionSettings(JsonElement settings) + => new() + { + CacheControlMergeBehavior = ReadDirectiveMergeBehavior( + settings, + "cacheControlMergeBehavior"), + EnableGlobalObjectIdentification = ReadBoolean( + settings, + "enableGlobalObjectIdentification"), + ExcludeByTag = ReadStrings(settings, "excludeByTag"), + NodeResolution = ReadNodeResolution(settings, "nodeResolution"), + RemoveUnreferencedDefinitions = ReadBoolean( + settings, + "removeUnreferencedDefinitions"), + TagMergeBehavior = ReadDirectiveMergeBehavior(settings, "tagMergeBehavior") + }; + + private static DirectiveMergeBehavior? ReadDirectiveMergeBehavior( + JsonElement value, + string propertyName) + => GetString(value, propertyName) switch + { + null => null, + "IGNORE" => DirectiveMergeBehavior.Ignore, + "INCLUDE" => DirectiveMergeBehavior.Include, + "INCLUDE_PRIVATE" => DirectiveMergeBehavior.IncludePrivate, + var behavior => throw UnknownSettingValue(propertyName, behavior) + }; + + private static NodeResolution? ReadNodeResolution(JsonElement value, string propertyName) + => GetString(value, propertyName) switch + { + null => null, + "GATEWAY" => NodeResolution.Gateway, + "SOURCE_SCHEMA" => NodeResolution.SourceSchema, + var nodeResolution => throw UnknownSettingValue(propertyName, nodeResolution) + }; + + private static bool? ReadBoolean(JsonElement value, string propertyName) + => value.TryGetProperty(propertyName, out var property) + && property.ValueKind is JsonValueKind.True or JsonValueKind.False + ? property.GetBoolean() + : null; + + private static IReadOnlyList? ReadStrings(JsonElement value, string propertyName) + { + if (!value.TryGetProperty(propertyName, out var property) + || property.ValueKind is not JsonValueKind.Array) + { + return null; + } + + var values = new List(property.GetArrayLength()); + + foreach (var item in property.EnumerateArray()) + { + if (item.ValueKind is JsonValueKind.String) + { + values.Add(item.GetString()!); + } + } + + return values; + } + + private static FusionDeploymentException UnknownSettingValue( + string propertyName, + string value) + => new( + $"Nitro returned the unknown composition settings value '{value}' " + + $"for '{propertyName}'."); + private static FusionRemoteCommandResult ToCommandResult(JsonElement payload) { var errors = GetErrors(payload); diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs index e6c67e687a2..61ce2d56c7e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQL.cs @@ -87,7 +87,9 @@ public static async Task SendWithRetryAsync( && errors.ValueKind is JsonValueKind.Array && errors.GetArrayLength() > 0) { - return NitroGraphQLResult.Failed(DescribeErrors(errors)); + return NitroGraphQLResult.Failed( + DescribeErrors(errors), + ReadErrorCode(errors)); } if (!root.TryGetProperty("data", out var data)) @@ -346,6 +348,23 @@ private static async Task SubscribeCoreAsync( return (null, "Nitro ended the subscription without a result."); } + /// + /// Reads the error code that Nitro reported for the first error, or null when the + /// error carries no code. + /// + private static string? ReadErrorCode(JsonElement errors) + { + var error = errors[0]; + + return error.ValueKind is JsonValueKind.Object + && error.TryGetProperty("extensions", out var extensions) + && extensions.ValueKind is JsonValueKind.Object + && extensions.TryGetProperty("code", out var code) + && code.ValueKind is JsonValueKind.String + ? code.GetString() + : null; + } + private static string DescribeErrors(JsonElement errors) { var error = errors[0]; diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs index d55d41fbd59..e01642805d9 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroGraphQLResult.cs @@ -8,10 +8,11 @@ namespace HotChocolate.Fusion.Aspire.Nitro; /// internal sealed class NitroGraphQLResult { - private NitroGraphQLResult(JsonElement data, string? failure) + private NitroGraphQLResult(JsonElement data, string? failure, string? failureCode) { Data = data; Failure = failure; + FailureCode = failureCode; } /// @@ -24,9 +25,18 @@ private NitroGraphQLResult(JsonElement data, string? failure) /// public string? Failure { get; } + /// + /// Gets the error code that Nitro reported for the failure, or null when Nitro + /// reported no code. + /// + public string? FailureCode { get; } + public static NitroGraphQLResult Success(JsonElement data) - => new(data, null); + => new(data, null, null); public static NitroGraphQLResult Failed(string failure) - => new(default, failure); + => new(default, failure, null); + + public static NitroGraphQLResult Failed(string failure, string? failureCode) + => new(default, failure, failureCode); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs index e0f8603651e..fb04931c5a0 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroOperationDocuments.cs @@ -23,6 +23,8 @@ internal static class NitroOperationDocuments private const string CommitDeploymentHashFile = "CommitFusionDeployment.graphql.sha256"; private const string ReleaseDeploymentHashFile = "ReleaseFusionDeployment.graphql.sha256"; private const string WatchDeploymentHashFile = "WatchFusionDeployment.graphql.sha256"; + private const string GetStageCompositionSettingsHashFile = + "GetFusionStageCompositionSettings.graphql.sha256"; private static string? s_resolveApiNameHash; private static string? s_validateSchemaHash; @@ -36,6 +38,7 @@ internal static class NitroOperationDocuments private static string? s_commitDeploymentHash; private static string? s_releaseDeploymentHash; private static string? s_watchDeploymentHash; + private static string? s_getStageCompositionSettingsHash; #else private const string ResolveApiNameFile = "ResolveNitroApiName.graphql"; private const string ValidateSchemaFile = "ValidateNitroSchema.graphql"; @@ -49,6 +52,8 @@ internal static class NitroOperationDocuments private const string CommitDeploymentFile = "CommitFusionDeployment.graphql"; private const string ReleaseDeploymentFile = "ReleaseFusionDeployment.graphql"; private const string WatchDeploymentFile = "WatchFusionDeployment.graphql"; + private const string GetStageCompositionSettingsFile = + "GetFusionStageCompositionSettings.graphql"; private static string? s_resolveApiName; private static string? s_validateSchema; @@ -62,6 +67,7 @@ internal static class NitroOperationDocuments private static string? s_commitDeployment; private static string? s_releaseDeployment; private static string? s_watchDeployment; + private static string? s_getStageCompositionSettings; #endif /// @@ -108,6 +114,12 @@ internal static class NitroOperationDocuments /// public const string WatchDeploymentOperationName = "WatchFusionDeployment"; + /// + /// The operation name of the document that reads the composition settings of a stage. + /// + public const string GetStageCompositionSettingsOperationName = + "GetFusionStageCompositionSettings"; + #if NITRO_PERSISTED_OPERATIONS /// /// Gets the persisted operation id of the document that resolves the name of an api by its id. @@ -147,6 +159,10 @@ public static string GetReleaseDeploymentOperationId() public static string GetWatchDeploymentOperationId() => s_watchDeploymentHash ??= ReadDocument(WatchDeploymentHashFile).Trim(); + + public static string GetStageCompositionSettingsOperationId() + => s_getStageCompositionSettingsHash ??= + ReadDocument(GetStageCompositionSettingsHashFile).Trim(); #else /// /// Gets the document that resolves the name of an api by its id. @@ -186,6 +202,9 @@ public static string GetReleaseDeploymentDocument() public static string GetWatchDeploymentDocument() => s_watchDeployment ??= ReadDocument(WatchDeploymentFile); + + public static string GetStageCompositionSettingsDocument() + => s_getStageCompositionSettings ??= ReadDocument(GetStageCompositionSettingsFile); #endif private static string ReadDocument(string fileName) diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroStageCompositionSettings.cs b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroStageCompositionSettings.cs new file mode 100644 index 00000000000..f97e4fa8058 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/NitroStageCompositionSettings.cs @@ -0,0 +1,60 @@ +using HotChocolate.Fusion.Options; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +/// +/// The composition settings that a Nitro stage declares. A value that the stage does not declare +/// is null. +/// +internal sealed record NitroStageCompositionSettings +{ + /// + /// Gets the merge behavior for the cache control directive. + /// + public DirectiveMergeBehavior? CacheControlMergeBehavior { get; init; } + + /// + /// Gets a value indicating whether global object identification is enabled. + /// + public bool? EnableGlobalObjectIdentification { get; init; } + + /// + /// Gets the tags whose schema elements are excluded before the source schemas are merged. + /// + public IReadOnlyList? ExcludeByTag { get; init; } + + /// + /// Gets the schema that resolves the node field. + /// + public NodeResolution? NodeResolution { get; init; } + + /// + /// Gets a value indicating whether definitions that no schema element references are removed. + /// + public bool? RemoveUnreferencedDefinitions { get; init; } + + /// + /// Gets the merge behavior for the tag directive. + /// + public DirectiveMergeBehavior? TagMergeBehavior { get; init; } + + /// + /// Creates the composition settings that these stage settings describe. + /// + public CompositionSettings ToCompositionSettings() + => new() + { + Merger = new CompositionSettings.MergerSettings + { + CacheControlMergeBehavior = CacheControlMergeBehavior, + EnableGlobalObjectIdentification = EnableGlobalObjectIdentification, + NodeResolution = NodeResolution, + RemoveUnreferencedDefinitions = RemoveUnreferencedDefinitions, + TagMergeBehavior = TagMergeBehavior + }, + Preprocessor = new CompositionSettings.PreprocessorSettings + { + ExcludeByTag = ExcludeByTag is null ? null : [.. ExcludeByTag] + } + }; +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql index f19ceb468dc..217aee9257f 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql @@ -3,7 +3,9 @@ mutation BeginFusionDeployment($input: BeginFusionConfigurationPublishInput!) { requestId errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 index 58680920128..83d9bf5b910 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/BeginFusionDeployment.graphql.sha256 @@ -1 +1 @@ -d858bf7df2ee5df613f2e699446657dd3bd489b9bd1d246eff7bf021c31307c7 +ceab775204b86a28d147d5799f39b5b05cd2e7abb93d337df315432f5b8a32ac diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql index 079b97a2853..926d4b56ed9 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql @@ -2,7 +2,9 @@ mutation ClaimFusionDeployment($input: StartFusionConfigurationCompositionInput! startFusionConfigurationComposition(input: $input) { errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 index f4ae644f5f8..564d57796b9 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ClaimFusionDeployment.graphql.sha256 @@ -1 +1 @@ -5b818ded677899729e77ceeba1fc6ae179a08cf7982d158fcc2731f798614046 +1efc9d275a44832a90a1a5227e280de66582e21e4e83bc89ae08fe8f0b3c69e1 diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql index 10d94e1f18d..c69673ad57e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql @@ -2,7 +2,9 @@ mutation CommitFusionDeployment($input: CommitFusionConfigurationPublishInput!) commitFusionConfigurationPublish(input: $input) { errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 index bf46ad93883..4a18e695530 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/CommitFusionDeployment.graphql.sha256 @@ -1 +1 @@ -f66f07ad033aff0963053bd465ff33463b48b2c4888a30b7db7dfbb8301f48ec +74a2f5e237fb7213158efed61f9b3e48d420bb3fe45b2f0bce82ecff64319edd diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql new file mode 100644 index 00000000000..36abc7e840e --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql @@ -0,0 +1,14 @@ +query GetFusionStageCompositionSettings($apiId: ID!, $stageName: String!) { + apiById(id: $apiId) { + stage(name: $stageName) { + compositionSettings { + cacheControlMergeBehavior + enableGlobalObjectIdentification + excludeByTag + nodeResolution + removeUnreferencedDefinitions + tagMergeBehavior + } + } + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql.sha256 new file mode 100644 index 00000000000..c34592a4df4 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/GetFusionStageCompositionSettings.graphql.sha256 @@ -0,0 +1 @@ +5cf6fc33bd9b6b535673f8adf0163b1ec7daaa7c8ed25240f77a9e814a7b6b7a diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql index 80fba9c927b..1f162172fbe 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql @@ -2,7 +2,9 @@ mutation ReleaseFusionDeployment($input: CancelFusionConfigurationCompositionInp cancelFusionConfigurationComposition(input: $input) { errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 index 234bc482a29..8afd1d46236 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ReleaseFusionDeployment.graphql.sha256 @@ -1 +1 @@ -808c0252d2713db0f747a5c6d8d9e56806db032489daac04d9b075c1baef06a2 +0ce52927ce212dfd36404a8a74db96c06be1e791845c526d8c0b0a8ded1a4a61 diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql index 92c813a6ae4..d3ce59f5998 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql @@ -5,7 +5,9 @@ mutation UploadFusionSourceSchema($input: UploadFusionSubgraphInput!) { } errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 index 62072fc63ff..1ef3321891a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/UploadFusionSourceSchema.graphql.sha256 @@ -1 +1 @@ -f091e081d5fea136c590aa34b6f37d391d69844c23f3a61f5e728ba97fed951f +1114c593d527acede0596dc456f59242f899101ed4c089314adaa60cdddcff79 diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql index 2f1aed8fb76..89f5335de4a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql @@ -2,7 +2,9 @@ mutation ValidateFusionDeployment($input: ValidateFusionConfigurationComposition validateFusionConfigurationComposition(input: $input) { errors { __typename - message + ... on Error { + message + } } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 index 62a8e794ef5..07dc162d26c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 +++ b/src/HotChocolate/Fusion/src/Fusion.Aspire/Nitro/Operations/ValidateFusionDeployment.graphql.sha256 @@ -1 +1 @@ -de83bb4fc363ce531c18138aa35938387f46a81b1258f8882dd7c67b6c499a5e +e5d213965c3e14e470ba31e9c238147ebd35fcec42b636960a7c2dcc535e1325 diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs index 6b07f5dac24..9c197c7b792 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/AspireCompositionHelperTests.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using HotChocolate.Fusion.Aspire.Nitro; using HotChocolate.Fusion.Options; using HotChocolate.Fusion.Packaging; using HotChocolate.Fusion.SourceSchema.Packaging; @@ -171,7 +172,9 @@ public void CreateCompositionSettings_Should_MapNodeResolution( NodeResolution = nodeResolution }; - var compositionSettings = AspireCompositionHelper.CreateCompositionSettings(settings); + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings: null); Assert.True(compositionSettings.Merger.EnableGlobalObjectIdentification); Assert.Equal(nodeResolution, compositionSettings.Merger.NodeResolution); @@ -189,7 +192,9 @@ public void CreateCompositionSettings_Should_MapShareableFieldRuntimeTypeRouting ShareableFieldRuntimeTypeRouting = routing }; - var compositionSettings = AspireCompositionHelper.CreateCompositionSettings(settings); + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings: null); Assert.Equal( routing, @@ -208,7 +213,9 @@ public void CreateCompositionSettings_Should_MapAllowNonResolvableInterfaceObjec AllowNonResolvableInterfaceObjects = allow }; - var compositionSettings = AspireCompositionHelper.CreateCompositionSettings(settings); + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings: null); Assert.Equal( allow, @@ -230,7 +237,9 @@ public void CreateCompositionSettings_Should_MapAllUserFacingSettings() ShareableFieldRuntimeTypeRouting.CommonRuntimeTypes, TagMergeBehavior = DirectiveMergeBehavior.Include }; - var compositionSettings = AspireCompositionHelper.CreateCompositionSettings(settings); + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings: null); using var document = JsonSerializer.SerializeToDocument( compositionSettings, SettingsJsonSerializerContext.Default.CompositionSettings); @@ -265,6 +274,111 @@ public void CreateCompositionSettings_Should_MapAllUserFacingSettings() """); } + [Fact] + public void CreateCompositionSettings_Should_UseStageSettings_When_SettingsAreUnset() + { + // arrange + var settings = new GraphQLCompositionSettings + { + TagMergeBehavior = DirectiveMergeBehavior.Include + }; + var stageSettings = new NitroStageCompositionSettings + { + CacheControlMergeBehavior = DirectiveMergeBehavior.IncludePrivate, + EnableGlobalObjectIdentification = true, + ExcludeByTag = ["internal"], + NodeResolution = NodeResolution.SourceSchema, + RemoveUnreferencedDefinitions = true, + TagMergeBehavior = DirectiveMergeBehavior.Ignore + }; + + // act + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings.ToCompositionSettings()); + + // assert + SerializeSettings(compositionSettings) + .MatchInlineSnapshot( + """ + { + "preprocessor": { + "excludeByTag": [ + "internal" + ] + }, + "merger": { + "addFusionDefinitions": null, + "cacheControlMergeBehavior": "IncludePrivate", + "enableGlobalObjectIdentification": true, + "nodeResolution": "SourceSchema", + "removeUnreferencedDefinitions": true, + "tagMergeBehavior": "Include" + }, + "satisfiability": { + "includeSatisfiabilityPaths": null + }, + "apolloFederationCompatibility": { + "allowNonResolvableInterfaceObjects": null, + "shareableFieldRuntimeTypeRouting": null + } + } + """); + } + + [Fact] + public void CreateCompositionSettings_Should_KeepSettings_When_StageSettingsDeclareThemToo() + { + // arrange + var settings = new GraphQLCompositionSettings + { + CacheControlMergeBehavior = DirectiveMergeBehavior.Ignore, + EnableGlobalObjectIdentification = false, + ExcludeByTag = new HashSet { "local" }, + NodeResolution = NodeResolution.Gateway + }; + var stageSettings = new NitroStageCompositionSettings + { + CacheControlMergeBehavior = DirectiveMergeBehavior.IncludePrivate, + EnableGlobalObjectIdentification = true, + ExcludeByTag = ["stage"], + NodeResolution = NodeResolution.SourceSchema + }; + + // act + var compositionSettings = AspireCompositionHelper.CreateCompositionSettings( + settings, + stageSettings.ToCompositionSettings()); + + // assert + SerializeSettings(compositionSettings) + .MatchInlineSnapshot( + """ + { + "preprocessor": { + "excludeByTag": [ + "local" + ] + }, + "merger": { + "addFusionDefinitions": null, + "cacheControlMergeBehavior": "Ignore", + "enableGlobalObjectIdentification": false, + "nodeResolution": "Gateway", + "removeUnreferencedDefinitions": null, + "tagMergeBehavior": null + }, + "satisfiability": { + "includeSatisfiabilityPaths": null + }, + "apolloFederationCompatibility": { + "allowNonResolvableInterfaceObjects": null, + "shareableFieldRuntimeTypeRouting": null + } + } + """); + } + [Fact] public void BuildLocalUrlOverrides_Should_UseConfiguredPath_When_SettingsDefineHttpUrl() { @@ -657,6 +771,17 @@ type Product @key(fields: "id") @extends { } } + private static string SerializeSettings(CompositionSettings settings) + { + using var document = JsonSerializer.SerializeToDocument( + settings, + SettingsJsonSerializerContext.Default.CompositionSettings); + + return JsonSerializer.Serialize( + document.RootElement, + new JsonSerializerOptions { WriteIndented = true }); + } + private static async Task CreateSourceArchiveAsync(string archivePath) { using var settings = CreateSourceSettings(); diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs index b1b29ec8065..fd7b3c46271 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/FusionReleaseAcceptanceTests.cs @@ -1029,6 +1029,11 @@ public FakeNitro() public Exception? BeginException { get; set; } + /// + /// Gets or sets the composition settings that the stage declares, as JSON. + /// + public string StageCompositionSettingsJson { get; set; } = "null"; + public void Seed(string name, string version, byte[] archive) => _sources[$"{name}@{version}"] = archive; @@ -1097,6 +1102,9 @@ protected override async Task SendAsync( case NitroOperationDocuments.ReleaseDeploymentOperationName: return Json(CommandResult("cancelFusionConfigurationComposition")); + case NitroOperationDocuments.GetStageCompositionSettingsOperationName: + return Json(StageCompositionSettings(StageCompositionSettingsJson)); + default: throw new InvalidOperationException( $"The Nitro operation '{operationName}' is not scripted."); @@ -1239,6 +1247,11 @@ private static void AssertFileName(UploadedFile? file, string expected) Assert.Equal(expected, file.FileName); } + private static string StageCompositionSettings(string settingsJson) + => "{\"data\":{\"apiById\":{\"stage\":{\"compositionSettings\":" + + settingsJson + + "}}}}"; + private static string CommandResult(string fieldName) => "{\"data\":{\"" + fieldName + "\":{\"errors\":[]}}}"; diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs index 5f1164df97c..bb9ab66fb83 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroOperationDocumentsTests.cs @@ -36,31 +36,35 @@ public sealed class NitroOperationDocumentsTests }, { NitroOperationDocuments.UploadSourceSchemaOperationName, - "f091e081d5fea136c590aa34b6f37d391d69844c23f3a61f5e728ba97fed951f" + "1114c593d527acede0596dc456f59242f899101ed4c089314adaa60cdddcff79" }, { NitroOperationDocuments.BeginDeploymentOperationName, - "d858bf7df2ee5df613f2e699446657dd3bd489b9bd1d246eff7bf021c31307c7" + "ceab775204b86a28d147d5799f39b5b05cd2e7abb93d337df315432f5b8a32ac" }, { NitroOperationDocuments.ClaimDeploymentOperationName, - "5b818ded677899729e77ceeba1fc6ae179a08cf7982d158fcc2731f798614046" + "1efc9d275a44832a90a1a5227e280de66582e21e4e83bc89ae08fe8f0b3c69e1" }, { NitroOperationDocuments.ValidateDeploymentOperationName, - "de83bb4fc363ce531c18138aa35938387f46a81b1258f8882dd7c67b6c499a5e" + "e5d213965c3e14e470ba31e9c238147ebd35fcec42b636960a7c2dcc535e1325" }, { NitroOperationDocuments.CommitDeploymentOperationName, - "f66f07ad033aff0963053bd465ff33463b48b2c4888a30b7db7dfbb8301f48ec" + "74a2f5e237fb7213158efed61f9b3e48d420bb3fe45b2f0bce82ecff64319edd" }, { NitroOperationDocuments.ReleaseDeploymentOperationName, - "808c0252d2713db0f747a5c6d8d9e56806db032489daac04d9b075c1baef06a2" + "0ce52927ce212dfd36404a8a74db96c06be1e791845c526d8c0b0a8ded1a4a61" }, { NitroOperationDocuments.WatchDeploymentOperationName, "25fff4ef7a751b2d9b1008f353dc23c89b8aed8978334407d3aa9a83806839b1" + }, + { + NitroOperationDocuments.GetStageCompositionSettingsOperationName, + "5cf6fc33bd9b6b535673f8adf0163b1ec7daaa7c8ed25240f77a9e814a7b6b7a" } }; @@ -98,6 +102,9 @@ ... on Api { [InlineData("CommitFusionDeployment", "mutation CommitFusionDeployment(")] [InlineData("ReleaseFusionDeployment", "mutation ReleaseFusionDeployment(")] [InlineData("WatchFusionDeployment", "subscription WatchFusionDeployment(")] + [InlineData( + "GetFusionStageCompositionSettings", + "query GetFusionStageCompositionSettings(")] public void OperationName_Should_MatchTheOperationInTheDocument( string operationName, string expectedDeclaration) @@ -153,6 +160,8 @@ private static string GetDocument(string operationName) => NitroOperationDocuments.GetReleaseDeploymentDocument(), NitroOperationDocuments.WatchDeploymentOperationName => NitroOperationDocuments.GetWatchDeploymentDocument(), + NitroOperationDocuments.GetStageCompositionSettingsOperationName + => NitroOperationDocuments.GetStageCompositionSettingsDocument(), _ => throw new ArgumentOutOfRangeException(nameof(operationName)) }; #endif @@ -198,9 +207,9 @@ private static string GetOperationId(string operationName) => NitroOperationDocuments.GetReleaseDeploymentOperationId(), NitroOperationDocuments.WatchDeploymentOperationName => NitroOperationDocuments.GetWatchDeploymentOperationId(), + NitroOperationDocuments.GetStageCompositionSettingsOperationName + => NitroOperationDocuments.GetStageCompositionSettingsOperationId(), _ => throw new ArgumentOutOfRangeException(nameof(operationName)) }; -======= ->>>>>>> origin/main #endif } diff --git a/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroStageCompositionSettingsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroStageCompositionSettingsTests.cs new file mode 100644 index 00000000000..e553f07c91d --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Aspire.Tests/Nitro/NitroStageCompositionSettingsTests.cs @@ -0,0 +1,225 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Http; + +namespace HotChocolate.Fusion.Aspire.Nitro; + +public sealed class NitroStageCompositionSettingsTests : IAsyncLifetime +{ + private FakeNitroServer _server = null!; + + public async ValueTask InitializeAsync() => _server = await FakeNitroServer.StartAsync(); + + public async ValueTask DisposeAsync() => await _server.DisposeAsync(); + + [Fact] + public async Task GetStageCompositionSettingsAsync_Should_SendTheOperation_When_ItIsCalled() + { + // arrange + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """{"data":{"apiById":{"stage":{"compositionSettings":null}}}}"""); + using var api = CreateApi(); + + // act + await api.GetStageCompositionSettingsAsync( + CreateTarget(), + "production", + TestContext.Current.CancellationToken); + + // assert + var request = Assert.Single(_server.Requests); + + Assert.Equal("nitro-api-key", request.Headers[NitroRequestHeaders.ApiKey]); +#if !NITRO_PERSISTED_OPERATIONS + request.Body.MatchInlineSnapshot( + """ + {"query":"query GetFusionStageCompositionSettings($apiId: ID!, $stageName: String!) {\n apiById(id: $apiId) {\n stage(name: $stageName) {\n compositionSettings {\n cacheControlMergeBehavior\n enableGlobalObjectIdentification\n excludeByTag\n nodeResolution\n removeUnreferencedDefinitions\n tagMergeBehavior\n }\n }\n }\n}\n","operationName":"GetFusionStageCompositionSettings","variables":{"apiId":"api-1","stageName":"production"}} + """); +#else + request.Body.MatchInlineSnapshot( + """ + {"id":"5cf6fc33bd9b6b535673f8adf0163b1ec7daaa7c8ed25240f77a9e814a7b6b7a","operationName":"GetFusionStageCompositionSettings","variables":{"apiId":"api-1","stageName":"production"}} + """); +#endif + } + + [Fact] + public async Task GetStageCompositionSettingsAsync_Should_ReadSettings_When_StageDeclaresThem() + { + // arrange + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """ + {"data":{"apiById":{"stage":{"compositionSettings":{ + "cacheControlMergeBehavior":"INCLUDE_PRIVATE", + "enableGlobalObjectIdentification":true, + "excludeByTag":["internal"], + "nodeResolution":"SOURCE_SCHEMA", + "removeUnreferencedDefinitions":true, + "tagMergeBehavior":"IGNORE"}}}}} + """); + using var api = CreateApi(); + + // act + var settings = await api.GetStageCompositionSettingsAsync( + CreateTarget(), + "production", + TestContext.Current.CancellationToken); + + // assert + Assert.NotNull(settings); + JsonSerializer.Serialize( + JsonSerializer.SerializeToDocument( + settings.ToCompositionSettings(), + SettingsJsonSerializerContext.Default.CompositionSettings) + .RootElement, + new JsonSerializerOptions { WriteIndented = true }) + .MatchInlineSnapshot( + """ + { + "preprocessor": { + "excludeByTag": [ + "internal" + ] + }, + "merger": { + "addFusionDefinitions": null, + "cacheControlMergeBehavior": "IncludePrivate", + "enableGlobalObjectIdentification": true, + "nodeResolution": "SourceSchema", + "removeUnreferencedDefinitions": true, + "tagMergeBehavior": "Ignore" + }, + "satisfiability": { + "includeSatisfiabilityPaths": null + }, + "apolloFederationCompatibility": { + "allowNonResolvableInterfaceObjects": null, + "shareableFieldRuntimeTypeRouting": null + } + } + """); + } + + [Fact] + public async Task GetStageCompositionSettingsAsync_Should_ReturnNull_When_StageIsUnknown() + { + // arrange + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """{"data":{"apiById":{"stage":null}}}"""); + using var api = CreateApi(); + + // act + var settings = await api.GetStageCompositionSettingsAsync( + CreateTarget(), + "production", + TestContext.Current.CancellationToken); + + // assert + Assert.Null(settings); + } + + [Fact] + public async Task GetStageCompositionSettingsAsync_Should_Throw_When_ServerRejectsTheRequest() + { + // arrange + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """{"errors":[{"message":"The current user is not authorized."}]}"""); + using var api = CreateApi(); + + // act + var exception = await Assert.ThrowsAsync( + () => api.GetStageCompositionSettingsAsync( + CreateTarget(), + "production", + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro returned GraphQL errors: The current user is not authorized.", + exception.Message); + } + + [Fact] + public async Task GetStageCompositionSettingsAsync_Should_Throw_When_FieldIsUnknown() + { + // arrange + // a Nitro server that predates the stage composition settings rejects the field + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """ + {"errors":[{"message":"The field `compositionSettings` does not exist.", + "extensions":{"code":"HC0020"}}]} + """); + using var api = CreateApi(); + + // act + var exception = await Assert.ThrowsAsync( + () => api.GetStageCompositionSettingsAsync( + CreateTarget(), + "production", + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro returned GraphQL errors: The field `compositionSettings` does not exist.", + exception.Message); + } + + [Fact] + public async Task + TryGetStageCompositionSettingsAsync_Should_WarnAndYieldNull_When_FieldIsUnknown() + { + // arrange + _server.GraphQLHandler = _ => FakeNitroResponse.Json( + """ + {"errors":[{"message":"The field `compositionSettings` does not exist.", + "extensions":{"code":"HC0020"}}]} + """); + using var api = CreateApi(); + var workflow = new FusionDeploymentWorkflow(api); + var logger = new RecordingLogger(); + + // act + var settings = await workflow.TryGetStageCompositionSettingsAsync( + CreateTarget(), + "production", + logger, + TestContext.Current.CancellationToken); + + // assert + Assert.Null(settings); + Assert.Equal( + "The composition settings of stage production could not be downloaded, so the " + + "composition only uses the settings of the distributed application. Update the " + + "Nitro server to the latest version. Nitro returned GraphQL errors: The field " + + "`compositionSettings` does not exist.", + Assert.Single(logger.Entries).Message); + } + + [Fact] + public async Task TryGetStageCompositionSettingsAsync_Should_Throw_When_ServerFails() + { + // arrange + _server.GraphQLHandler = _ => + FakeNitroResponse.Status(StatusCodes.Status500InternalServerError); + using var api = CreateApi(); + var workflow = new FusionDeploymentWorkflow(api); + + // act + var exception = await Assert.ThrowsAsync( + () => workflow.TryGetStageCompositionSettingsAsync( + CreateTarget(), + "production", + new RecordingLogger(), + TestContext.Current.CancellationToken)); + + // assert + Assert.Equal( + "Nitro returned HTTP 500 (InternalServerError).", + exception.Message); + } + + private static NitroFusionApi CreateApi() + => new(new HttpClient(), disposeHttpClient: true); + + private FusionTarget CreateTarget() + => new(_server.BaseAddress, "api-1", "nitro-api-key"); +}