Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
| true, mediaTyObj -> Some(mediaTyObj)
| _ -> None

let (|TextReturn|_|)(input: string) =
if input.StartsWith("text/") then Some(input) else None

let (|TextMediaType|_|)(content: IDictionary<string, OpenApiMediaType>) =
content.Keys |> Seq.tryPick (|TextReturn|_|)

let (|NoMediaType|_|)(content: IDictionary<string, OpenApiMediaType>) =
if content.Count = 0 then Some() else None

Expand Down Expand Up @@ -176,6 +182,7 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
defCompiler.CompileTy providedMethodName "Response" mediaTy.Schema true

Some(MediaTypes.ApplicationOctetStream, ty)
| TextMediaType mediaTy -> Some(mediaTy, typeof<string>)
| _ -> None)

let retMime = retMimeAndTy |> Option.map fst |> Option.defaultValue null
Expand Down Expand Up @@ -373,6 +380,17 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
}
@>

let responseString =
<@
let x = %action

task {
let! response = x
let! data = response.ReadAsStringAsync()
return data
}
@>

let responseUnit =
<@
let x = %action
Expand All @@ -389,15 +407,21 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
match retTy with
| None -> responseUnit.Raw
| Some t when t = typeof<IO.Stream> -> <@ %responseStream @>.Raw
| Some t -> Expr.Coerce(<@ RuntimeHelpers.taskCast t %responseObj @>, overallReturnType)
| Some t ->
match retMime with
| TextReturn _ -> <@ %responseString @>.Raw
| _ -> Expr.Coerce(<@ RuntimeHelpers.taskCast t %responseObj @>, overallReturnType)
else
let awaitTask t =
<@ Async.AwaitTask(%t) @>

match retTy with
| None -> (awaitTask responseUnit).Raw
| Some t when t = typeof<IO.Stream> -> <@ %(awaitTask responseStream) @>.Raw
| Some t -> Expr.Coerce(<@ RuntimeHelpers.asyncCast t %(awaitTask responseObj) @>, overallReturnType)
| Some t ->
match retMime with
| TextReturn _ -> <@ %(awaitTask responseString) @>.Raw
| _ -> Expr.Coerce(<@ RuntimeHelpers.asyncCast t %(awaitTask responseObj) @>, overallReturnType)
)

if not <| String.IsNullOrEmpty(operation.Summary) then
Expand Down
1 change: 0 additions & 1 deletion src/SwaggerProvider.Runtime/RuntimeHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ module MediaTypes =
[<Literal>]
let MultipartFormData = "multipart/form-data"


type AsyncExtensions() =
static member cast<'t> asyncOp =
async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<Compile Include="v3\Swagger.I0181.Tests.fs" />
<Compile Include="v3\Swagger.I0219.Tests.fs" />
<Compile Include="v3\Swashbuckle.ReturnControllers.Tests.fs" />
<Compile Include="v3\Swashbuckle.ReturnTextControllers.Tests.fs" />
<Compile Include="v3\Swashbuckle.UpdateControllers.Tests.fs" />
<Compile Include="v3\Swashbuckle.ResourceControllers.Tests.fs" />
<Compile Include="v3\Swashbuckle.FileController.Tests.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Swashbuckle.v3.ReturnTextControllersTests

open Xunit
open FsUnitTyped
open SwaggerProvider
open System
open System.Net.Http

open Swashbuckle.v3.ReturnControllersTests

[<Fact>]
let ``Return text/plain GET Test``() =
api.GetApiReturnPlain() |> asyncEqual "Hello world"

[<Fact>]
let ``Return text/csv GET Test``() =
api.GetApiReturnCsv() |> asyncEqual "Hello,world"
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Swashbuckle.WebApi.Server.Controllers

open System.Text
open Microsoft.AspNetCore.Mvc
open Microsoft.AspNetCore.Mvc.Formatters
open Swagger.Internal

[<Route("api/[controller]")>]
[<ApiController>]
type ReturnPlainController() =
[<HttpGet; Produces("text/plain")>]
member this.Get() =
"Hello world" |> ActionResult<string>

[<Route("api/[controller]")>]
[<ApiController>]
type ReturnCsvController() =
[<HttpGet; Produces("text/csv")>]
member this.Get() =
"Hello,world" |> ActionResult<string>

// Simple CSV output formatter
// This formatter assumes the controller returns a string (already CSV-formatted)
type CsvOutputFormatter() as this =
inherit TextOutputFormatter()

do
this.SupportedMediaTypes.Add("text/csv")
this.SupportedEncodings.Add(Encoding.UTF8)
this.SupportedEncodings.Add(Encoding.Unicode)

override _.CanWriteType(t) =
// Accept string type only (for simplicity)
t = typeof<string>

override _.WriteResponseBodyAsync(context, encoding) =
let response = context.HttpContext.Response
let value = context.Object :?> string
let bytes = encoding.GetBytes(value)
response.Body.WriteAsync(bytes, 0, bytes.Length)
1 change: 1 addition & 0 deletions tests/Swashbuckle.WebApi.Server/Startup.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Startup private () =
let converters = options.JsonSerializerOptions.Converters
converters.Add(JsonFSharpConverter())
converters.Add(JsonStringEnumConverter()))
.AddMvcOptions(_.OutputFormatters.Add(CsvOutputFormatter()))
|> ignore
// Register the Swagger & OpenApi services
services.AddSwaggerGen(fun c ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<Compile Include="Controllers\Types.fs" />
<Compile Include="Controllers\ReturnControllers.fs" />
<Compile Include="Controllers\ReturnTextControllers.fs" />
<Compile Include="Controllers\UpdateControllers.fs" />
<Compile Include="Controllers\ResourceControllers.fs" />
<Compile Include="Controllers\ValuesController.fs" />
Expand Down
Loading