diff --git a/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs index 6630838..0d586c8 100644 --- a/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs @@ -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) = + content.Keys |> Seq.tryPick (|TextReturn|_|) + let (|NoMediaType|_|)(content: IDictionary) = if content.Count = 0 then Some() else None @@ -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) | _ -> None) let retMime = retMimeAndTy |> Option.map fst |> Option.defaultValue null @@ -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 @@ -389,7 +407,10 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, match retTy with | None -> responseUnit.Raw | Some t when t = typeof -> <@ %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) @> @@ -397,7 +418,10 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, match retTy with | None -> (awaitTask responseUnit).Raw | Some t when t = typeof -> <@ %(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 diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index 885ed8c..3663698 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -18,7 +18,6 @@ module MediaTypes = [] let MultipartFormData = "multipart/form-data" - type AsyncExtensions() = static member cast<'t> asyncOp = async { diff --git a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj index 3ae9ff2..507352a 100644 --- a/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj +++ b/tests/SwaggerProvider.ProviderTests/SwaggerProvider.ProviderTests.fsproj @@ -26,6 +26,7 @@ + diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs new file mode 100644 index 0000000..7eea5fd --- /dev/null +++ b/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs @@ -0,0 +1,17 @@ +module Swashbuckle.v3.ReturnTextControllersTests + +open Xunit +open FsUnitTyped +open SwaggerProvider +open System +open System.Net.Http + +open Swashbuckle.v3.ReturnControllersTests + +[] +let ``Return text/plain GET Test``() = + api.GetApiReturnPlain() |> asyncEqual "Hello world" + +[] +let ``Return text/csv GET Test``() = + api.GetApiReturnCsv() |> asyncEqual "Hello,world" diff --git a/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs b/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs new file mode 100644 index 0000000..70c58dd --- /dev/null +++ b/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs @@ -0,0 +1,40 @@ +namespace Swashbuckle.WebApi.Server.Controllers + +open System.Text +open Microsoft.AspNetCore.Mvc +open Microsoft.AspNetCore.Mvc.Formatters +open Swagger.Internal + +[] +[] +type ReturnPlainController() = + [] + member this.Get() = + "Hello world" |> ActionResult + +[] +[] +type ReturnCsvController() = + [] + member this.Get() = + "Hello,world" |> ActionResult + +// 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 + + 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) diff --git a/tests/Swashbuckle.WebApi.Server/Startup.fs b/tests/Swashbuckle.WebApi.Server/Startup.fs index 1827251..87f55c4 100644 --- a/tests/Swashbuckle.WebApi.Server/Startup.fs +++ b/tests/Swashbuckle.WebApi.Server/Startup.fs @@ -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 -> diff --git a/tests/Swashbuckle.WebApi.Server/Swashbuckle.WebApi.Server.fsproj b/tests/Swashbuckle.WebApi.Server/Swashbuckle.WebApi.Server.fsproj index 60e9dd5..b70e960 100644 --- a/tests/Swashbuckle.WebApi.Server/Swashbuckle.WebApi.Server.fsproj +++ b/tests/Swashbuckle.WebApi.Server/Swashbuckle.WebApi.Server.fsproj @@ -9,6 +9,7 @@ +