@@ -457,5 +457,126 @@ private async Task InnerApplySchemaTransformersAsync(IOpenApiSchema inputSchema,
457457 }
458458
459459 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+ }
461582}
0 commit comments