@@ -21,6 +21,7 @@ public static class OpenApiPathHelper
2121 [
2222 // Order matters: null policies first, then transformations.
2323 new V2UnsupportedPathPolicy ( ) ,
24+ new V2RequestBodyToBodyParameterPolicy ( ) ,
2425 new V2ComponentRenamePolicy ( ) ,
2526 new V2ResponseContentUnwrappingPolicy ( ) ,
2627 new V2HeaderSchemaUnwrappingPolicy ( ) ,
@@ -187,7 +188,6 @@ internal sealed class V2UnsupportedPathPolicy : IOpenApiPathRepresentationPolicy
187188 OpenApiConstants . Servers ,
188189 OpenApiConstants . Callbacks ,
189190 OpenApiConstants . Links ,
190- OpenApiConstants . RequestBody ,
191191 } ;
192192
193193 public bool TryGetVersionedPath ( string [ ] segments , out string ? result )
@@ -238,6 +238,69 @@ public bool TryGetVersionedPath(string[] segments, out string? result)
238238 }
239239}
240240
241+ /// <summary>
242+ /// Maps v3 <c>requestBody</c> paths to their v2 <c>in:body</c> parameter equivalents.
243+ /// <list type="bullet">
244+ /// <item><c>.../requestBody</c> → <c>.../parameters/0</c></item>
245+ /// <item><c>.../requestBody/content/{mediaType}/schema/**</c> → <c>.../parameters/0/schema/**</c></item>
246+ /// <item><c>.../requestBody/{prop}</c> → <c>.../parameters/0/{prop}</c></item>
247+ /// </list>
248+ /// </summary>
249+ internal sealed class V2RequestBodyToBodyParameterPolicy : IOpenApiPathRepresentationPolicy
250+ {
251+ public bool TryGetVersionedPath ( string [ ] segments , out string ? result )
252+ {
253+ result = null ;
254+
255+ // Find requestBody segment
256+ var requestBodyIndex = - 1 ;
257+ for ( var i = 0 ; i < segments . Length ; i ++ )
258+ {
259+ if ( string . Equals ( segments [ i ] , OpenApiConstants . RequestBody , StringComparison . Ordinal ) )
260+ {
261+ requestBodyIndex = i ;
262+ break ;
263+ }
264+ }
265+
266+ if ( requestBodyIndex < 0 )
267+ {
268+ return false ;
269+ }
270+
271+ // +1 because requestBody (1 segment) is replaced by parameters/0 (2 segments)
272+ var buffer = new string [ segments . Length + 1 ] ;
273+ var written = 0 ;
274+
275+ // Copy segments before requestBody
276+ for ( var i = 0 ; i < requestBodyIndex ; i ++ )
277+ {
278+ buffer [ written ++ ] = segments [ i ] ;
279+ }
280+
281+ // Replace requestBody with parameters/0
282+ buffer [ written ++ ] = OpenApiConstants . Parameters ;
283+ buffer [ written ++ ] = "0" ;
284+
285+ // Skip content/{mediaType} if both segments immediately follow requestBody
286+ var next = requestBodyIndex + 1 ;
287+ if ( next + 1 < segments . Length &&
288+ string . Equals ( segments [ next ] , OpenApiConstants . Content , StringComparison . Ordinal ) )
289+ {
290+ next += 2 ; // skip "content" and "{mediaType}"
291+ }
292+
293+ // Copy remaining segments
294+ for ( var i = next ; i < segments . Length ; i ++ )
295+ {
296+ buffer [ written ++ ] = segments [ i ] ;
297+ }
298+
299+ result = OpenApiPathHelper . BuildPath ( buffer , written ) ;
300+ return true ;
301+ }
302+ }
303+
241304/// <summary>
242305/// Returns null for paths that have no equivalent in OpenAPI v3.0.
243306/// Covers: webhooks (added in v3.1).
0 commit comments