-
Notifications
You must be signed in to change notification settings - Fork 59
Add type provider integration tests for CancellationToken-overloaded methods #338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -272,46 +272,45 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, | |
|
|
||
| // Locates parameters matching the arguments | ||
| let mutable payloadExp = None | ||
| let mutable ctExpr: Expr<Threading.CancellationToken> option = None | ||
|
|
||
| // When the CancellationToken overload is generated, CancellationToken is always appended last. | ||
| // Extract it by position to avoid name-collision issues and invalid Expr.Coerce | ||
| // on a struct type (which generates an invalid castclass IL instruction). | ||
| let apiArgs, ct = | ||
| let allArgs = List.tail args // skip `this` | ||
|
|
||
| if includeCancellationToken then | ||
| match List.rev allArgs with | ||
| | ctArg :: revApiArgs -> List.rev revApiArgs, Expr.Cast<Threading.CancellationToken>(ctArg) | ||
| | [] -> failwith "Expected CancellationToken argument but argument list was empty" | ||
| else | ||
| allArgs, <@ Threading.CancellationToken.None @> | ||
|
Comment on lines
+276
to
+287
|
||
|
|
||
| let parameters = | ||
| List.tail args // skip `this` param | ||
| apiArgs | ||
| |> List.choose (function | ||
| | ShapeVar sVar as expr -> | ||
| // cancellationToken is added by the compiler, not from OpenAPI spec | ||
| if sVar.Name = "cancellationToken" then | ||
| ctExpr <- | ||
| Some( | ||
| Expr.Coerce(expr, typeof<Threading.CancellationToken>) | ||
| |> Expr.Cast<Threading.CancellationToken> | ||
| ) | ||
|
|
||
| None | ||
| else | ||
|
|
||
| let param = | ||
| openApiParameters | ||
| |> Seq.tryFind(fun x -> | ||
| // pain point: we have to make sure that the set of names we search for here are the same as the set of names generated when we make `parameters` above | ||
| let baseName = niceCamelName x.Name | ||
| baseName = sVar.Name || (unambiguousName x) = sVar.Name) | ||
|
|
||
| match param with | ||
| | Some(par) -> Some(par, expr) | ||
| | _ -> | ||
| let payloadType = PayloadType.Parse sVar.Name | ||
|
|
||
| match payloadExp with | ||
| | None -> | ||
| payloadExp <- Some(payloadType, Expr.Coerce(expr, typeof<obj>)) | ||
| None | ||
| | Some _ -> | ||
| failwithf | ||
| $"More than one payload parameter is specified: '%A{payloadType}' & '%A{payloadExp.Value |> fst}'" | ||
| let param = | ||
| openApiParameters | ||
| |> Seq.tryFind(fun x -> | ||
| // pain point: we have to make sure that the set of names we search for here are the same as the set of names generated when we make `parameters` above | ||
| let baseName = niceCamelName x.Name | ||
| baseName = sVar.Name || (unambiguousName x) = sVar.Name) | ||
|
|
||
| match param with | ||
| | Some(par) -> Some(par, expr) | ||
| | _ -> | ||
| let payloadType = PayloadType.Parse sVar.Name | ||
|
|
||
| match payloadExp with | ||
| | None -> | ||
| payloadExp <- Some(payloadType, Expr.Coerce(expr, typeof<obj>)) | ||
| None | ||
| | Some _ -> | ||
| failwithf | ||
| $"More than one payload parameter is specified: '%A{payloadType}' & '%A{payloadExp.Value |> fst}'" | ||
| | _ -> failwithf $"Function '%s{providedMethodName}' does not support functions as arguments.") | ||
|
|
||
| let ct = ctExpr |> Option.defaultValue <@ Threading.CancellationToken.None @> | ||
|
|
||
| // Makes argument a string // TODO: Make body an exception | ||
| let coerceString exp = | ||
| let obj = Expr.Coerce(exp, typeof<obj>) |> Expr.Cast<obj> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| module Swashbuckle.v3.CancellationTokenTests | ||
|
|
||
| open Xunit | ||
| open FsUnitTyped | ||
| open System | ||
| open System.Threading | ||
| open Swashbuckle.v3.ReturnControllersTests | ||
|
|
||
| [<Fact>] | ||
| let ``Call generated method with explicit CancellationToken None``() = | ||
| task { | ||
| let! result = api.GetApiReturnBoolean(CancellationToken.None) | ||
| result |> shouldEqual true | ||
| } | ||
|
|
||
| [<Fact>] | ||
| let ``Call generated method with valid CancellationTokenSource token``() = | ||
| task { | ||
| use cts = new CancellationTokenSource() | ||
| let! result = api.GetApiReturnInt32(cts.Token) | ||
| result |> shouldEqual 42 | ||
| } | ||
|
|
||
| [<Fact>] | ||
| let ``Call generated method with already-cancelled token raises OperationCanceledException``() = | ||
| task { | ||
| use cts = new CancellationTokenSource() | ||
| cts.Cancel() | ||
|
|
||
| try | ||
| let! _ = api.GetApiReturnString(cts.Token) | ||
| failwith "Expected OperationCanceledException" | ||
| with | ||
| | :? OperationCanceledException -> () | ||
| | :? System.AggregateException as aex when (aex.InnerException :? OperationCanceledException) -> () | ||
| } | ||
|
|
||
| [<Fact>] | ||
| let ``Call POST generated method with explicit CancellationToken None``() = | ||
| task { | ||
| let! result = api.PostApiReturnString(CancellationToken.None) | ||
| result |> shouldEqual "Hello world" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This failwith message is likely only hit when TP invocation arguments are unexpected, but it would be much easier to diagnose if it included the operation/method name (e.g., providedMethodName) and the received argument count. Consider switching to failwithf with that context.