@@ -143,6 +143,33 @@ internal static string BuildPath(string[] segments, int length)
143143#endif
144144 }
145145
146+ /// <summary>
147+ /// HTTP method names used to identify operation-level structural positions in the path.
148+ /// </summary>
149+ internal static readonly HashSet < string > HttpMethods = new ( StringComparer . OrdinalIgnoreCase )
150+ {
151+ "get" , "post" , "put" , "delete" , "patch" , "options" , "head" , "trace" ,
152+ } ;
153+
154+ /// <summary>
155+ /// Returns <c>true</c> if any segment before <paramref name="exclusiveUpperBound"/> indicates
156+ /// that the path has descended into a JSON Schema property tree — meaning subsequent segment
157+ /// names are schema property/key names rather than OpenAPI structural keywords.
158+ /// </summary>
159+ internal static bool IsSchemaContext ( string [ ] segments , int exclusiveUpperBound )
160+ {
161+ for ( var i = 0 ; i < exclusiveUpperBound ; i ++ )
162+ {
163+ if ( string . Equals ( segments [ i ] , OpenApiConstants . Properties , StringComparison . Ordinal ) ||
164+ string . Equals ( segments [ i ] , OpenApiConstants . AdditionalProperties , StringComparison . Ordinal ) )
165+ {
166+ return true ;
167+ }
168+ }
169+
170+ return false ;
171+ }
172+
146173 /// <summary>
147174 /// Copies segments into the target buffer, skipping a contiguous range.
148175 /// Returns the number of segments written.
@@ -166,8 +193,7 @@ internal static int CopySkipping(string[] source, int sourceLength, string[] tar
166193
167194/// <summary>
168195/// Returns null for paths that have no equivalent in OpenAPI v2 (Swagger).
169- /// Covers: servers, webhooks, callbacks, links, requestBody (inline),
170- /// encoding, and unsupported component types.
196+ /// Covers: servers, webhooks, callbacks, links, encoding, and unsupported component types.
171197/// </summary>
172198internal sealed class V2UnsupportedPathPolicy : IOpenApiPathRepresentationPolicy
173199{
@@ -219,7 +245,14 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
219245 {
220246 var segment = segments [ i ] ;
221247
222- // servers, callbacks, links, requestBody at any nested level
248+ // Skip segments that are inside a schema property tree — they are property names,
249+ // not structural OpenAPI keywords.
250+ if ( OpenApiPathHelper . IsSchemaContext ( segments , i ) )
251+ {
252+ continue ;
253+ }
254+
255+ // servers, callbacks, links at any nested level
223256 if ( UnsupportedSegments . Contains ( segment ) )
224257 {
225258 return true ;
@@ -252,11 +285,13 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
252285 {
253286 result = null ;
254287
255- // Find requestBody segment
288+ // Find requestBody immediately after an HTTP method — operation-level requestBody only.
289+ // This prevents false positives when "requestBody" is a schema property name.
256290 var requestBodyIndex = - 1 ;
257- for ( var i = 0 ; i < segments . Length ; i ++ )
291+ for ( var i = 1 ; i < segments . Length ; i ++ )
258292 {
259- if ( string . Equals ( segments [ i ] , OpenApiConstants . RequestBody , StringComparison . Ordinal ) )
293+ if ( string . Equals ( segments [ i ] , OpenApiConstants . RequestBody , StringComparison . Ordinal ) &&
294+ OpenApiPathHelper . HttpMethods . Contains ( segments [ i - 1 ] ) )
260295 {
261296 requestBodyIndex = i ;
262297 break ;
@@ -364,6 +399,7 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
364399 // Content unwrapping: skip "content" and "{mediaType}" after "responses/{code}"
365400 if ( string . Equals ( segments [ i ] , OpenApiConstants . Content , StringComparison . Ordinal ) &&
366401 i >= 3 &&
402+ ! OpenApiPathHelper . IsSchemaContext ( segments , i ) &&
367403 string . Equals ( segments [ i - 2 ] , OpenApiConstants . Responses , StringComparison . Ordinal ) &&
368404 i + 1 < segments . Length )
369405 {
@@ -374,6 +410,7 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
374410 // Header schema unwrapping: skip "schema" after "headers/{name}"
375411 if ( string . Equals ( segments [ i ] , OpenApiConstants . Schema , StringComparison . Ordinal ) &&
376412 i >= 3 &&
413+ ! OpenApiPathHelper . IsSchemaContext ( segments , i ) &&
377414 string . Equals ( segments [ i - 2 ] , OpenApiConstants . Headers , StringComparison . Ordinal ) )
378415 {
379416 continue ;
@@ -397,11 +434,15 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
397434 {
398435 result = null ;
399436
400- // Find: responses / {code} / content / {mediaType}
437+ // Find: {method} / responses / {code} / content / {mediaType}
438+ // Require the HTTP method immediately before "responses" to anchor this to a real
439+ // operation response, preventing false positives from schema properties named "responses"
440+ // or from extension paths that happen to match the pattern.
401441 var contentIndex = - 1 ;
402- for ( var i = 0 ; i < segments . Length - 3 ; i ++ )
442+ for ( var i = 1 ; i < segments . Length - 3 ; i ++ )
403443 {
404444 if ( string . Equals ( segments [ i ] , OpenApiConstants . Responses , StringComparison . Ordinal ) &&
445+ OpenApiPathHelper . HttpMethods . Contains ( segments [ i - 1 ] ) &&
405446 string . Equals ( segments [ i + 2 ] , OpenApiConstants . Content , StringComparison . Ordinal ) )
406447 {
407448 contentIndex = i + 2 ;
@@ -433,10 +474,12 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
433474 result = null ;
434475
435476 // Find: headers / {name} / schema
477+ // Guard against schema property trees where "headers" is just a property name.
436478 var schemaIndex = - 1 ;
437479 for ( var i = 0 ; i < segments . Length - 2 ; i ++ )
438480 {
439481 if ( string . Equals ( segments [ i ] , OpenApiConstants . Headers , StringComparison . Ordinal ) &&
482+ ! OpenApiPathHelper . IsSchemaContext ( segments , i ) &&
440483 string . Equals ( segments [ i + 2 ] , OpenApiConstants . Schema , StringComparison . Ordinal ) )
441484 {
442485 schemaIndex = i + 2 ;
0 commit comments