diff --git a/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs b/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs index 524e7a1..3498e20 100644 --- a/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs +++ b/src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs @@ -28,6 +28,7 @@ type PayloadType = | AppOctetStream | AppFormUrlEncoded | MultipartFormData + | TextPlain override x.ToString() = match x with @@ -36,6 +37,7 @@ type PayloadType = | AppOctetStream -> "octetStream" | AppFormUrlEncoded -> "formUrlEncoded" | MultipartFormData -> "formData" + | TextPlain -> "textPlain" member x.ToMediaType() = match x with @@ -44,6 +46,7 @@ type PayloadType = | AppOctetStream -> MediaTypes.ApplicationOctetStream | AppFormUrlEncoded -> MediaTypes.ApplicationFormUrlEncoded | MultipartFormData -> MediaTypes.MultipartFormData + | TextPlain -> MediaTypes.TextPlain static member Parse = function @@ -52,6 +55,7 @@ type PayloadType = | "octetStream" -> AppOctetStream | "formUrlEncoded" -> AppFormUrlEncoded | "formData" -> MultipartFormData + | "textPlain" -> TextPlain | name -> failwithf $"Payload '%s{name}' is not supported" /// Object for compiling operations. @@ -114,6 +118,7 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, | MediaType MediaTypes.ApplicationOctetStream mediaTyObj -> formatAndParam AppOctetStream mediaTyObj.Schema | MediaType MediaTypes.MultipartFormData mediaTyObj -> formatAndParam MultipartFormData mediaTyObj.Schema | MediaType MediaTypes.ApplicationFormUrlEncoded mediaTyObj -> formatAndParam AppFormUrlEncoded mediaTyObj.Schema + | MediaType MediaTypes.TextPlain mediaTyObj -> formatAndParam TextPlain mediaTyObj.Schema | NoMediaType -> // Assume that server treat it as `applicationJson` let defSchema = OpenApiSchema() // todo: we need to test it @@ -359,6 +364,13 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler, msg.Content <- RuntimeHelpers.toFormUrlEncodedContent(data) msg @> + | Some(TextPlain, textObj) -> + <@ + let text = (%%textObj: obj).ToString() + let msg = %httpRequestMessage + msg.Content <- RuntimeHelpers.toTextContent(text) + msg + @> let action = <@ (%this).CallAsync(%httpRequestMessageWithPayload, errorCodes, errorDescriptions) @> diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index 3663698..10bb2ee 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -18,6 +18,9 @@ module MediaTypes = [] let MultipartFormData = "multipart/form-data" + [] + let TextPlain = "text/plain" + type AsyncExtensions() = static member cast<'t> asyncOp = async { @@ -127,6 +130,9 @@ module RuntimeHelpers = let toStringContent(valueStr: string) = new StringContent(valueStr, Text.Encoding.UTF8, "application/json") + let toTextContent(valueStr: string) = + new StringContent(valueStr, Text.Encoding.UTF8, "text/plain") + let toStreamContent(boxedStream: obj) = match boxedStream with | :? IO.Stream as stream -> new StreamContent(stream) diff --git a/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs b/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs index 7eea5fd..19c6498 100644 --- a/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs +++ b/tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs @@ -15,3 +15,7 @@ let ``Return text/plain GET Test``() = [] let ``Return text/csv GET Test``() = api.GetApiReturnCsv() |> asyncEqual "Hello,world" + +[] +let ``Send & return text/plain POST Test``() = + api.PostApiConsumesText("hello") |> asyncEqual "hello" diff --git a/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs b/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs index 70c58dd..494a069 100644 --- a/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs +++ b/tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs @@ -1,6 +1,8 @@ namespace Swashbuckle.WebApi.Server.Controllers +open System.IO open System.Text +open System.Threading.Tasks open Microsoft.AspNetCore.Mvc open Microsoft.AspNetCore.Mvc.Formatters open Swagger.Internal @@ -19,6 +21,13 @@ type ReturnCsvController() = member this.Get() = "Hello,world" |> ActionResult +[] +[] +type ConsumesTextController() = + [] + member this.Post([] request: string) = + request |> ActionResult + // Simple CSV output formatter // This formatter assumes the controller returns a string (already CSV-formatted) type CsvOutputFormatter() as this = @@ -38,3 +47,22 @@ type CsvOutputFormatter() as this = let value = context.Object :?> string let bytes = encoding.GetBytes(value) response.Body.WriteAsync(bytes, 0, bytes.Length) + +// Text/plain input formatter for reading plain text request bodies +type TextPlainInputFormatter() as this = + inherit TextInputFormatter() + + do + this.SupportedMediaTypes.Add("text/plain") + this.SupportedEncodings.Add(Encoding.UTF8) + this.SupportedEncodings.Add(Encoding.Unicode) + + override this.CanRead(context) = + base.CanRead(context) && context.ModelType = typeof + + override _.ReadRequestBodyAsync(context, encoding) = + task { + use reader = new StreamReader(context.HttpContext.Request.Body, encoding) + let! content = reader.ReadToEndAsync() + return InputFormatterResult.Success(content) + } diff --git a/tests/Swashbuckle.WebApi.Server/Startup.fs b/tests/Swashbuckle.WebApi.Server/Startup.fs index 87f55c4..f36a2d3 100644 --- a/tests/Swashbuckle.WebApi.Server/Startup.fs +++ b/tests/Swashbuckle.WebApi.Server/Startup.fs @@ -24,7 +24,9 @@ type Startup private () = let converters = options.JsonSerializerOptions.Converters converters.Add(JsonFSharpConverter()) converters.Add(JsonStringEnumConverter())) - .AddMvcOptions(_.OutputFormatters.Add(CsvOutputFormatter())) + .AddMvcOptions(fun options -> + options.OutputFormatters.Add(CsvOutputFormatter()) + options.InputFormatters.Add(TextPlainInputFormatter())) |> ignore // Register the Swagger & OpenApi services services.AddSwaggerGen(fun c ->