@@ -26,6 +26,25 @@ public sealed record SchemaAnalysisResult(
2626 JsonSchema PrimarySchema ,
2727 string RootTypeName ) ;
2828
29+ /// <summary>
30+ /// Represents a schema input that can be either a file path or raw content.
31+ /// </summary>
32+ public sealed record SchemaInput
33+ {
34+ public required string Name { get ; init ; }
35+ public string ? FilePath { get ; init ; }
36+ public string ? Content { get ; init ; }
37+
38+ public static SchemaInput FromFile ( string filePath ) =>
39+ new ( ) { Name = Path . GetFileName ( filePath ) , FilePath = filePath } ;
40+
41+ public static SchemaInput FromContent ( string name , string content ) =>
42+ new ( ) { Name = name , Content = content } ;
43+
44+ public async Task < string > ReadContentAsync ( CancellationToken cancellationToken = default ) =>
45+ Content ?? await File . ReadAllTextAsync ( FilePath ! , cancellationToken ) ;
46+ }
47+
2948/// <summary>
3049/// Analyzes JSON schemas to determine shared vs variant-specific types.
3150/// Types are considered shared only if they have the same name AND same content hash.
@@ -44,83 +63,97 @@ public SchemaAnalyzer(ILogger logger)
4463 /// Analyzes multiple schemas to determine which types are shared (exist in all with identical content)
4564 /// vs variant-specific (exist in only some schemas or have different content).
4665 /// </summary>
47- public async Task < SchemaAnalysisResult > AnalyzeAsync (
66+ public Task < SchemaAnalysisResult > AnalyzeAsync (
4867 List < string > schemaPaths ,
4968 string currentVariant ,
5069 CancellationToken cancellationToken = default )
5170 {
52- // Determine primary schema path
53- var primarySchemaPath = DeterminePrimarySchemaPath ( schemaPaths , currentVariant ) ;
71+ var inputs = schemaPaths . Select ( SchemaInput . FromFile ) . ToList ( ) ;
72+ return AnalyzeAsync ( inputs , currentVariant , cancellationToken ) ;
73+ }
74+
75+ /// <summary>
76+ /// Analyzes multiple schemas to determine which types are shared (exist in all with identical content)
77+ /// vs variant-specific (exist in only some schemas or have different content).
78+ /// </summary>
79+ public async Task < SchemaAnalysisResult > AnalyzeAsync (
80+ List < SchemaInput > schemaInputs ,
81+ string currentVariant ,
82+ CancellationToken cancellationToken = default )
83+ {
84+ var schemaNames = schemaInputs . Select ( s => s . Name ) . ToList ( ) ;
85+
86+ // Determine primary schema name
87+ var primarySchemaName = DeterminePrimarySchemaName ( schemaNames , currentVariant ) ;
5488
5589 // Parse all schemas and extract type hashes
56- var ( parsedSchemas , allSchemaTypes ) = await ParseSchemasAsync ( schemaPaths , cancellationToken ) ;
90+ var ( parsedSchemas , allSchemaTypes ) = await ParseSchemasAsync ( schemaInputs , cancellationToken ) ;
5791
5892 // Extract root type name from the first schema's title
5993 var rootTypeName = parsedSchemas . Values . FirstOrDefault ( ) ? . Title ?? Constants . DefaultRootTypeName ;
6094
6195 // Get the primary schema (already parsed)
62- var primarySchema = parsedSchemas [ primarySchemaPath ] ;
96+ var primarySchema = parsedSchemas [ primarySchemaName ] ;
6397
6498 // With only one schema, we can't determine what's shared
65- if ( schemaPaths . Count < 2 )
99+ if ( schemaInputs . Count < 2 )
66100 {
67- return new SchemaAnalysisResult ( [ ] , [ ] , [ ] , primarySchemaPath , primarySchema , rootTypeName ) ;
101+ return new SchemaAnalysisResult ( [ ] , [ ] , [ ] , primarySchemaName , primarySchema , rootTypeName ) ;
68102 }
69103
70104 // Categorize types as shared or conflicting
71105 var ( sharedTypes , conflictingTypes ) = CategorizeTypes ( allSchemaTypes , rootTypeName ) ;
72106
73107 // Variant-specific types = types in current variant schema that aren't shared or conflicting
74108 var variantTypes = DetermineVariantTypes (
75- schemaPaths ,
109+ schemaNames ,
76110 currentVariant ,
77111 parsedSchemas ,
78112 sharedTypes ,
79113 conflictingTypes ,
80114 rootTypeName ) ;
81115
82- return new SchemaAnalysisResult ( sharedTypes , variantTypes , conflictingTypes , primarySchemaPath , primarySchema , rootTypeName ) ;
116+ return new SchemaAnalysisResult ( sharedTypes , variantTypes , conflictingTypes , primarySchemaName , primarySchema , rootTypeName ) ;
83117 }
84118
85119 /// <summary>
86- /// Determines the primary schema path based on the current variant.
120+ /// Determines the primary schema name based on the current variant.
87121 /// </summary>
88- private static string DeterminePrimarySchemaPath ( List < string > schemaPaths , string currentVariant )
122+ private static string DeterminePrimarySchemaName ( List < string > schemaNames , string currentVariant )
89123 {
90124 if ( string . IsNullOrEmpty ( currentVariant ) )
91125 {
92- return schemaPaths [ 0 ] ;
126+ return schemaNames [ 0 ] ;
93127 }
94128
95- var variantSchema = schemaPaths . FirstOrDefault ( p => Path . GetFileName ( p )
96- . Contains (
97- currentVariant ,
98- StringComparison . OrdinalIgnoreCase ) ) ;
129+ var variantSchema = schemaNames . FirstOrDefault ( name => name . Contains (
130+ currentVariant ,
131+ StringComparison . OrdinalIgnoreCase ) ) ;
99132
100- return variantSchema ?? schemaPaths [ 0 ] ;
133+ return variantSchema ?? schemaNames [ 0 ] ;
101134 }
102135
103136 /// <summary>
104137 /// Parses all schemas and extracts type hashes.
105138 /// </summary>
106139 private async Task < ( Dictionary < string , JsonSchema > parsedSchemas , List < Dictionary < string , string > > allSchemaTypes ) >
107- ParseSchemasAsync ( List < string > schemaPaths , CancellationToken cancellationToken )
140+ ParseSchemasAsync ( List < SchemaInput > schemaInputs , CancellationToken cancellationToken )
108141 {
109142 var parsedSchemas = new Dictionary < string , JsonSchema > ( ) ;
110143 var allSchemaTypes = new List < Dictionary < string , string > > ( ) ;
111144
112- foreach ( var schemaPath in schemaPaths )
145+ foreach ( var input in schemaInputs )
113146 {
114147 cancellationToken . ThrowIfCancellationRequested ( ) ;
115148
116- var schemaJson = await File . ReadAllTextAsync ( schemaPath , cancellationToken ) ;
149+ var schemaJson = await input . ReadContentAsync ( cancellationToken ) ;
117150 var schema = await JsonSchema . FromJsonAsync ( schemaJson , cancellationToken ) ;
118- parsedSchemas [ schemaPath ] = schema ;
151+ parsedSchemas [ input . Name ] = schema ;
119152
120153 var types = ExtractTypeHashes ( schema ) ;
121154 allSchemaTypes . Add ( types ) ;
122155
123- _logger . LogDebug ( " {SchemaFile}: {TypeCount} types" , Path . GetFileName ( schemaPath ) , types . Count ) ;
156+ _logger . LogDebug ( " {SchemaFile}: {TypeCount} types" , input . Name , types . Count ) ;
124157 }
125158
126159 return ( parsedSchemas , allSchemaTypes ) ;
@@ -175,20 +208,19 @@ private static (HashSet<string> sharedTypes, HashSet<string> conflictingTypes) C
175208 /// Determines variant-specific types (types not shared or conflicting).
176209 /// </summary>
177210 private HashSet < string > DetermineVariantTypes (
178- List < string > schemaPaths ,
211+ List < string > schemaNames ,
179212 string currentVariant ,
180213 Dictionary < string , JsonSchema > parsedSchemas ,
181214 HashSet < string > sharedTypes ,
182215 HashSet < string > conflictingTypes ,
183216 string rootTypeName )
184217 {
185- var currentVariantSchemaPath = schemaPaths . FirstOrDefault ( p => Path . GetFileName ( p )
186- . Contains (
187- currentVariant ,
188- StringComparison . OrdinalIgnoreCase ) )
189- ?? schemaPaths [ 0 ] ;
218+ var currentVariantSchemaName = schemaNames . FirstOrDefault ( name => name . Contains (
219+ currentVariant ,
220+ StringComparison . OrdinalIgnoreCase ) )
221+ ?? schemaNames [ 0 ] ;
190222
191- var currentTypes = ExtractTypeHashes ( parsedSchemas [ currentVariantSchemaPath ] ) ;
223+ var currentTypes = ExtractTypeHashes ( parsedSchemas [ currentVariantSchemaName ] ) ;
192224
193225 var variantTypes = new HashSet < string > ( currentTypes . Keys ) ;
194226 variantTypes . ExceptWith ( sharedTypes ) ;
0 commit comments