1+ using System . IO . Compression ;
12using System . Reactive ;
23using System . Reactive . Linq ;
34using System . Reactive . Subjects ;
67using ChilliCream . Nitro . CommandLine . Cloud . Option ;
78using ChilliCream . Nitro . CommandLine . Cloud . Option . Binders ;
89using ChilliCream . Nitro . CommandLine . Fusion . Compatibility ;
10+ using HotChocolate . Fusion . Packaging ;
911using StrawberryShake ;
1012using 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}
0 commit comments