Skip to content

Commit e66036f

Browse files
[Fusion] Support validate command (#8992)
1 parent 5ce9a20 commit e66036f

File tree

2 files changed

+102
-49
lines changed

2 files changed

+102
-49
lines changed

src/Nitro/CommandLine/src/CommandLine.Cloud/Commands/Fusion/FusionValidateCommand.cs

Lines changed: 101 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.IO.Compression;
12
using System.Reactive;
23
using System.Reactive.Linq;
34
using System.Reactive.Subjects;
@@ -6,6 +7,7 @@
67
using ChilliCream.Nitro.CommandLine.Cloud.Option;
78
using ChilliCream.Nitro.CommandLine.Cloud.Option.Binders;
89
using ChilliCream.Nitro.CommandLine.Fusion.Compatibility;
10+
using HotChocolate.Fusion.Packaging;
911
using StrawberryShake;
1012
using static ChilliCream.Nitro.CommandLine.Cloud.ThrowHelper;
1113

@@ -15,9 +17,7 @@ internal sealed class FusionValidateCommand : Command
1517
{
1618
public FusionValidateCommand() : base("validate")
1719
{
18-
Description = "Validates a fusion configuration against a stage."
19-
+ System.Environment.NewLine
20-
+ "This only works for Fusion v1 at the moment.";
20+
Description = "Validates the composed GraphQL schema of a fusion configuration against a stage.";
2121

2222
AddOption(Opt<StageNameOption>.Instance);
2323
AddOption(Opt<ApiIdOption>.Instance);
@@ -42,7 +42,7 @@ private static async Task<int> ExecuteAsync(
4242
FileInfo configFile,
4343
CancellationToken ct)
4444
{
45-
console.Title($"Validate to {stage.EscapeMarkup()}");
45+
console.Title($"Validate against {stage.EscapeMarkup()}");
4646

4747
var isValid = false;
4848

@@ -63,13 +63,29 @@ await console
6363

6464
async Task ValidateSchema(StatusContext? ctx)
6565
{
66-
console.Log("Initialized");
6766
console.Log($"Reading file [blue]{configFile.FullName.EscapeMarkup()}[/]");
6867

69-
var stream = FileHelpers.CreateFileStream(configFile);
68+
await using var stream = FileHelpers.CreateFileStream(configFile);
7069

71-
await using var package = FusionGraphPackage.Open(stream, FileAccess.Read);
72-
var schemaStream = await LoadSchemaFile(package, ct);
70+
Stream schemaStream;
71+
IDisposable disposableArchive;
72+
73+
if (IsFarFormat(stream))
74+
{
75+
var archive = FusionArchive.Open(stream, leaveOpen: true);
76+
77+
schemaStream = await LoadSchemaFile(archive, ct);
78+
79+
disposableArchive = archive;
80+
}
81+
else
82+
{
83+
var package = FusionGraphPackage.Open(stream, FileAccess.Read);
84+
85+
schemaStream = await LoadSchemaFile(package, ct);
86+
87+
disposableArchive = package;
88+
}
7389

7490
var input = new ValidateSchemaInput
7591
{
@@ -80,50 +96,60 @@ async Task ValidateSchema(StatusContext? ctx)
8096

8197
console.Log("Create validation request");
8298

83-
var requestId = await ValidateAsync(console, client, input, ct);
99+
try
100+
{
101+
var requestId = await ValidateAsync(console, client, input, ct);
84102

85-
console.Log($"Validation request created [grey](ID: {requestId.EscapeMarkup()})[/]");
103+
disposableArchive.Dispose();
86104

87-
using var stopSignal = new Subject<Unit>();
105+
console.Log($"Validation request created [grey](ID: {requestId.EscapeMarkup()})[/]");
88106

89-
var subscription = client.OnSchemaVersionValidationUpdated
90-
.Watch(requestId, ExecutionStrategy.NetworkOnly)
91-
.TakeUntil(stopSignal);
107+
using var stopSignal = new Subject<Unit>();
92108

93-
await foreach (var x in subscription.ToAsyncEnumerable().WithCancellation(ct))
94-
{
95-
if (x.Errors is { Count: > 0 } errors)
96-
{
97-
console.PrintErrorsAndExit(errors);
98-
throw Exit("No request id returned");
99-
}
109+
var subscription = client.OnSchemaVersionValidationUpdated
110+
.Watch(requestId, ExecutionStrategy.NetworkOnly)
111+
.TakeUntil(stopSignal);
100112

101-
switch (x.Data?.OnSchemaVersionValidationUpdate)
113+
await foreach (var x in subscription.ToAsyncEnumerable().WithCancellation(ct))
102114
{
103-
case ISchemaVersionValidationFailed { Errors: var schemaErrors }:
104-
console.Error.WriteLine("The schema is invalid:");
105-
console.PrintErrorsAndExit(schemaErrors);
106-
stopSignal.OnNext(Unit.Default);
107-
break;
108-
109-
case ISchemaVersionValidationSuccess:
110-
isValid = true;
111-
stopSignal.OnNext(Unit.Default);
112-
113-
console.Success("Schema validation succeeded.");
114-
break;
115-
116-
case IOperationInProgress:
117-
case IValidationInProgress:
118-
ctx?.Status("The validation is in progress.");
119-
break;
120-
121-
default:
122-
ctx?.Status(
123-
"This is an unknown response, upgrade Nitro CLI to the latest version.");
124-
break;
115+
if (x.Errors is { Count: > 0 } errors)
116+
{
117+
console.PrintErrorsAndExit(errors);
118+
throw Exit("No request id returned");
119+
}
120+
121+
switch (x.Data?.OnSchemaVersionValidationUpdate)
122+
{
123+
case ISchemaVersionValidationFailed { Errors: var schemaErrors }:
124+
console.Error.WriteLine("The schema is invalid:");
125+
console.PrintErrorsAndExit(schemaErrors);
126+
stopSignal.OnNext(Unit.Default);
127+
break;
128+
129+
case ISchemaVersionValidationSuccess:
130+
isValid = true;
131+
stopSignal.OnNext(Unit.Default);
132+
133+
console.Success("Schema validation succeeded.");
134+
break;
135+
136+
case IOperationInProgress:
137+
case IValidationInProgress:
138+
ctx?.Status("The validation is in progress.");
139+
break;
140+
141+
default:
142+
ctx?.Status(
143+
"This is an unknown response, upgrade Nitro CLI to the latest version.");
144+
break;
145+
}
125146
}
126147
}
148+
finally
149+
{
150+
disposableArchive.Dispose();
151+
await schemaStream.DisposeAsync();
152+
}
127153
}
128154
}
129155

@@ -147,18 +173,45 @@ private static async Task<string> ValidateAsync(
147173
return data.ValidateSchema.Id;
148174
}
149175

150-
private static async Task<MemoryStream> LoadSchemaFile(
151-
FusionGraphPackage package,
152-
CancellationToken ct)
176+
private static async Task<Stream> LoadSchemaFile(FusionArchive archive, CancellationToken ct)
177+
{
178+
var latestVersion = await archive.GetLatestSupportedGatewayFormatAsync(ct);
179+
var configuration = await archive.TryGetGatewayConfigurationAsync(latestVersion, ct);
180+
181+
if (configuration is null)
182+
{
183+
throw new InvalidOperationException(
184+
$"Failed to retrieve gateway configuration from the fusion archive (format version: {latestVersion}). "
185+
+ $"The archive may be corrupted, unsupported, or missing required configuration.");
186+
}
187+
188+
return await configuration.OpenReadSchemaAsync(ct);
189+
}
190+
191+
private static async Task<Stream> LoadSchemaFile(FusionGraphPackage package, CancellationToken ct)
153192
{
154193
var schemaNode = await package.GetSchemaAsync(ct);
155194

156195
var schemaFileStream = new MemoryStream();
157196
await using var streamWriter = new StreamWriter(schemaFileStream, leaveOpen: true);
158197
await streamWriter.WriteAsync(schemaNode.ToString());
159-
streamWriter.Flush();
198+
await streamWriter.FlushAsync(ct);
160199
schemaFileStream.Position = 0;
161200

162201
return schemaFileStream;
163202
}
203+
204+
public static bool IsFarFormat(Stream stream)
205+
{
206+
try
207+
{
208+
using var zip = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
209+
210+
return zip.GetEntry("archive-metadata.json") is not null;
211+
}
212+
catch
213+
{
214+
return false;
215+
}
216+
}
164217
}

src/Nitro/CommandLine/src/CommandLine.Cloud/Commands/Stages/EditStagesCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ CancellationToken cancellationToken
4444
{
4545
console.WriteOperationTitle();
4646

47-
const string apiMessage = "For which api do you want to create a client?";
47+
const string apiMessage = "For which api do you want to edit the stages?";
4848

4949
var apiId = await context.GetOrSelectApiId(apiMessage);
5050

0 commit comments

Comments
 (0)