@@ -96,7 +96,7 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
9696 let (| NoMediaType | _ |)( content : IDictionary < string , OpenApiMediaType >) =
9797 if isNull content || content.Count = 0 then Some() else None
9898
99- let payloadMime , parameters =
99+ let payloadMime , parameters , ctArgIndex =
100100 /// handles de-duplicating Swagger parameter names if the same parameter name
101101 /// appears in multiple locations in a given operation definition.
102102 let uniqueParamName usedNames ( param : IOpenApiParameter ) =
@@ -147,18 +147,15 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
147147
148148 let payloadTy = bodyFormatAndParam |> Option.map fst |> Option.defaultValue NoData
149149
150- let orderedParameters =
151- let required , optional =
152- [ yield ! openApiParameters
153- if bodyFormatAndParam.IsSome then
154- yield bodyFormatAndParam.Value |> snd ]
155- |> List.distinctBy( fun op -> op.Name, op.In)
156- |> List.partition(_. Required)
150+ let requiredOpenApiParams , optionalOpenApiParams =
151+ [ yield ! openApiParameters
152+ if bodyFormatAndParam.IsSome then
153+ yield bodyFormatAndParam.Value |> snd ]
154+ |> List.distinctBy( fun op -> op.Name, op.In)
155+ |> List.partition(_. Required)
157156
158- List.append required optional
159-
160- let usedNames , providedParameters =
161- (( Set.empty, []), orderedParameters)
157+ let buildProvidedParameters usedNames ( paramList : IOpenApiParameter list ) =
158+ (( usedNames, []), paramList)
162159 ||> List.fold( fun ( names , parameters ) current ->
163160 let names , paramName = uniqueParamName names current
164161
@@ -173,26 +170,37 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
173170 ProvidedParameter( paramName, paramType, false , paramDefaultValue)
174171
175172 ( names, providedParam :: parameters))
176- |> fun ( names , ps ) -> names, List.rev ps
173+ |> fun ( finalNames , ps ) -> finalNames, List.rev ps
174+
175+ let namesAfterRequired , requiredProvidedParams =
176+ buildProvidedParameters Set.empty requiredOpenApiParams
177177
178- let parameters =
178+ let _ , optionalProvidedParams =
179+ buildProvidedParameters namesAfterRequired optionalOpenApiParams
180+
181+ let ctArgIndex , parameters =
179182 if includeCancellationToken then
180- // Find a unique name for the CancellationToken parameter that doesn't
181- // conflict with any OpenAPI parameter already in use.
182- let ctParamName =
183- Seq.initInfinite( fun i ->
184- if i = 0 then
185- " cancellationToken"
186- else
187- $" cancellationToken{i}" )
188- |> Seq.find( fun n -> not ( Set.contains n usedNames))
183+ // Collect all used param names to generate a unique CT parameter name
184+ let usedNames =
185+ ( requiredProvidedParams @ optionalProvidedParams)
186+ |> List.map( fun p -> p.Name)
187+ |> Set.ofList
188+
189+ let rec findUniqueName candidate n =
190+ if Set.contains candidate usedNames then
191+ findUniqueName $" cancellationToken{n}" ( n + 1 )
192+ else
193+ candidate
189194
190- let ctParam = ProvidedParameter( ctParamName, typeof< Threading.CancellationToken>)
191- providedParameters @ [ ctParam ]
195+ let ctName = findUniqueName " cancellationToken" 1
196+ let ctParam = ProvidedParameter( ctName, typeof< Threading.CancellationToken>)
197+ // CT is inserted after required params so it never follows optional params
198+ let ctArgIndex = List.length requiredProvidedParams
199+ ctArgIndex, requiredProvidedParams @ [ ctParam ] @ optionalProvidedParams
192200 else
193- providedParameters
201+ - 1 , requiredProvidedParams @ optionalProvidedParams
194202
195- payloadTy.ToMediaType(), parameters
203+ payloadTy.ToMediaType(), parameters, ctArgIndex
196204
197205 // find the inner type value
198206 let retMimeAndTy =
@@ -278,16 +286,21 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
278286 // Locates parameters matching the arguments
279287 let mutable payloadExp = None
280288
281- // When the CancellationToken overload is generated, CancellationToken is always appended last.
282- // Extract it by position to avoid name-collision issues and invalid Expr.Coerce
283- // on a struct type (which generates an invalid castclass IL instruction) .
289+ // When the CancellationToken overload is generated, CT is inserted at ctArgIndex
290+ // (after required params, before optional params). Extract it by that known index
291+ // to avoid name-collision issues and invalid Expr.Coerce on a struct type .
284292 let apiArgs , ct =
285293 let allArgs = List.tail args // skip `this`
286294
287295 if includeCancellationToken then
288- match List.rev allArgs with
289- | ctArg :: revApiArgs -> List.rev revApiArgs, Expr.Cast< Threading.CancellationToken>( ctArg)
290- | [] -> failwith " Expected CancellationToken argument but argument list was empty"
296+ let ctArg = List.item ctArgIndex allArgs
297+
298+ let apiArgs =
299+ allArgs
300+ |> List.indexed
301+ |> List.choose( fun ( i , a ) -> if i = ctArgIndex then None else Some a)
302+
303+ apiArgs, Expr.Cast< Threading.CancellationToken>( ctArg)
291304 else
292305 allArgs, <@ Threading.CancellationToken.None @>
293306
0 commit comments