@@ -457,5 +457,126 @@ private async Task InnerApplySchemaTransformersAsync(IOpenApiSchema inputSchema,
457
457
}
458
458
459
459
private JsonNode CreateSchema ( OpenApiSchemaKey key )
460
- => JsonSchemaExporter . GetJsonSchemaAsNode ( _jsonSerializerOptions , key . Type , _configuration ) ;
460
+ {
461
+ var schema = JsonSchemaExporter . GetJsonSchemaAsNode ( _jsonSerializerOptions , key . Type , _configuration ) ;
462
+ return ResolveReferences ( schema , schema ) ;
463
+ }
464
+
465
+ private static JsonNode ResolveReferences ( JsonNode node , JsonNode rootSchema )
466
+ {
467
+ return ResolveReferencesRecursive ( node , rootSchema ) ;
468
+ }
469
+
470
+ private static JsonNode ResolveReferencesRecursive ( JsonNode node , JsonNode rootSchema )
471
+ {
472
+ if ( node is JsonObject jsonObject )
473
+ {
474
+ if ( jsonObject . TryGetPropertyValue ( OpenApiConstants . RefKeyword , out var refNode ) &&
475
+ refNode is JsonValue refValue &&
476
+ refValue . TryGetValue < string > ( out var refString ) &&
477
+ refString . StartsWith ( OpenApiConstants . RefPrefix , StringComparison . Ordinal ) )
478
+ {
479
+ try
480
+ {
481
+ // Resolve the reference path to the actual schema content
482
+ // to avoid relative references
483
+ var resolvedNode = ResolveReference ( refString , rootSchema ) ;
484
+ if ( resolvedNode != null )
485
+ {
486
+ return resolvedNode . DeepClone ( ) ;
487
+ }
488
+ }
489
+ catch ( InvalidOperationException )
490
+ {
491
+ // If resolution fails due to invalid path, return the original reference
492
+ // This maintains backward compatibility while preventing crashes
493
+ }
494
+
495
+ // If resolution fails, return the original reference
496
+ return node ;
497
+ }
498
+
499
+ // Process all properties recursively
500
+ var newObject = new JsonObject ( ) ;
501
+ foreach ( var property in jsonObject )
502
+ {
503
+ if ( property . Value != null )
504
+ {
505
+ var processedValue = ResolveReferencesRecursive ( property . Value , rootSchema ) ;
506
+ newObject [ property . Key ] = processedValue ? . DeepClone ( ) ;
507
+ }
508
+ else
509
+ {
510
+ newObject [ property . Key ] = null ;
511
+ }
512
+ }
513
+ return newObject ;
514
+ }
515
+ else if ( node is JsonArray jsonArray )
516
+ {
517
+ var newArray = new JsonArray ( ) ;
518
+ for ( var i = 0 ; i < jsonArray . Count ; i ++ )
519
+ {
520
+ if ( jsonArray [ i ] != null )
521
+ {
522
+ var processedValue = ResolveReferencesRecursive ( jsonArray [ i ] ! , rootSchema ) ;
523
+ newArray . Add ( processedValue ? . DeepClone ( ) ) ;
524
+ }
525
+ else
526
+ {
527
+ newArray . Add ( null ) ;
528
+ }
529
+ }
530
+ return newArray ;
531
+ }
532
+
533
+ // Return non-$ref nodes as-is
534
+ return node ;
535
+ }
536
+
537
+ private static JsonNode ? ResolveReference ( string refPath , JsonNode rootSchema )
538
+ {
539
+ if ( string . IsNullOrWhiteSpace ( refPath ) )
540
+ {
541
+ throw new InvalidOperationException ( "Reference path cannot be null or empty." ) ;
542
+ }
543
+
544
+ if ( ! refPath . StartsWith ( OpenApiConstants . RefPrefix , StringComparison . Ordinal ) )
545
+ {
546
+ throw new InvalidOperationException ( $ "Only fragment references (starting with '{ OpenApiConstants . RefPrefix } ') are supported. Found: { refPath } ") ;
547
+ }
548
+
549
+ var path = refPath . TrimStart ( '#' , '/' ) ;
550
+ if ( string . IsNullOrEmpty ( path ) )
551
+ {
552
+ return rootSchema ;
553
+ }
554
+
555
+ var segments = path . Split ( '/' ) ;
556
+ var current = rootSchema ;
557
+
558
+ for ( var i = 0 ; i < segments . Length ; i ++ )
559
+ {
560
+ var segment = segments [ i ] ;
561
+ if ( current is JsonObject currentObject )
562
+ {
563
+ if ( currentObject . TryGetPropertyValue ( segment , out var nextNode ) && nextNode != null )
564
+ {
565
+ current = nextNode ;
566
+ }
567
+ else
568
+ {
569
+ var partialPath = string . Join ( '/' , segments . Take ( i + 1 ) ) ;
570
+ throw new InvalidOperationException ( $ "Failed to resolve reference '{ refPath } ': path segment '{ segment } ' not found at '#{ partialPath } '") ;
571
+ }
572
+ }
573
+ else
574
+ {
575
+ var partialPath = string . Join ( '/' , segments . Take ( i ) ) ;
576
+ throw new InvalidOperationException ( $ "Failed to resolve reference '{ refPath } ': cannot navigate beyond '#{ partialPath } ' - expected object but found { current ? . GetType ( ) . Name ?? "null" } ") ;
577
+ }
578
+ }
579
+
580
+ return current ;
581
+ }
461
582
}
0 commit comments