Skip to content

Commit 031d910

Browse files
Numpsysergey-tihon
andauthored
feat: handle text/plain payload types (#276)
* Add a test controller which consumes text/plain content * feat: call new endpoint * feat: add plain text support fot request payload * feat: add plain text input formatter * fix: input formatter order * fix: canread on formatter --------- Co-authored-by: Sergey Tihon <[email protected]>
1 parent ba87fcf commit 031d910

File tree

5 files changed

+53
-1
lines changed

5 files changed

+53
-1
lines changed

src/SwaggerProvider.DesignTime/v3/OperationCompiler.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type PayloadType =
2828
| AppOctetStream
2929
| AppFormUrlEncoded
3030
| MultipartFormData
31+
| TextPlain
3132

3233
override x.ToString() =
3334
match x with
@@ -36,6 +37,7 @@ type PayloadType =
3637
| AppOctetStream -> "octetStream"
3738
| AppFormUrlEncoded -> "formUrlEncoded"
3839
| MultipartFormData -> "formData"
40+
| TextPlain -> "textPlain"
3941

4042
member x.ToMediaType() =
4143
match x with
@@ -44,6 +46,7 @@ type PayloadType =
4446
| AppOctetStream -> MediaTypes.ApplicationOctetStream
4547
| AppFormUrlEncoded -> MediaTypes.ApplicationFormUrlEncoded
4648
| MultipartFormData -> MediaTypes.MultipartFormData
49+
| TextPlain -> MediaTypes.TextPlain
4750

4851
static member Parse =
4952
function
@@ -52,6 +55,7 @@ type PayloadType =
5255
| "octetStream" -> AppOctetStream
5356
| "formUrlEncoded" -> AppFormUrlEncoded
5457
| "formData" -> MultipartFormData
58+
| "textPlain" -> TextPlain
5559
| name -> failwithf $"Payload '%s{name}' is not supported"
5660

5761
/// Object for compiling operations.
@@ -114,6 +118,7 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
114118
| MediaType MediaTypes.ApplicationOctetStream mediaTyObj -> formatAndParam AppOctetStream mediaTyObj.Schema
115119
| MediaType MediaTypes.MultipartFormData mediaTyObj -> formatAndParam MultipartFormData mediaTyObj.Schema
116120
| MediaType MediaTypes.ApplicationFormUrlEncoded mediaTyObj -> formatAndParam AppFormUrlEncoded mediaTyObj.Schema
121+
| MediaType MediaTypes.TextPlain mediaTyObj -> formatAndParam TextPlain mediaTyObj.Schema
117122
| NoMediaType ->
118123
// Assume that server treat it as `applicationJson`
119124
let defSchema = OpenApiSchema() // todo: we need to test it
@@ -359,6 +364,13 @@ type OperationCompiler(schema: OpenApiDocument, defCompiler: DefinitionCompiler,
359364
msg.Content <- RuntimeHelpers.toFormUrlEncodedContent(data)
360365
msg
361366
@>
367+
| Some(TextPlain, textObj) ->
368+
<@
369+
let text = (%%textObj: obj).ToString()
370+
let msg = %httpRequestMessage
371+
msg.Content <- RuntimeHelpers.toTextContent(text)
372+
msg
373+
@>
362374

363375
let action =
364376
<@ (%this).CallAsync(%httpRequestMessageWithPayload, errorCodes, errorDescriptions) @>

src/SwaggerProvider.Runtime/RuntimeHelpers.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ module MediaTypes =
1818
[<Literal>]
1919
let MultipartFormData = "multipart/form-data"
2020

21+
[<Literal>]
22+
let TextPlain = "text/plain"
23+
2124
type AsyncExtensions() =
2225
static member cast<'t> asyncOp =
2326
async {
@@ -127,6 +130,9 @@ module RuntimeHelpers =
127130
let toStringContent(valueStr: string) =
128131
new StringContent(valueStr, Text.Encoding.UTF8, "application/json")
129132

133+
let toTextContent(valueStr: string) =
134+
new StringContent(valueStr, Text.Encoding.UTF8, "text/plain")
135+
130136
let toStreamContent(boxedStream: obj) =
131137
match boxedStream with
132138
| :? IO.Stream as stream -> new StreamContent(stream)

tests/SwaggerProvider.ProviderTests/v3/Swashbuckle.ReturnTextControllers.Tests.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ let ``Return text/plain GET Test``() =
1515
[<Fact>]
1616
let ``Return text/csv GET Test``() =
1717
api.GetApiReturnCsv() |> asyncEqual "Hello,world"
18+
19+
[<Fact>]
20+
let ``Send & return text/plain POST Test``() =
21+
api.PostApiConsumesText("hello") |> asyncEqual "hello"

tests/Swashbuckle.WebApi.Server/Controllers/ReturnTextControllers.fs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace Swashbuckle.WebApi.Server.Controllers
22

3+
open System.IO
34
open System.Text
5+
open System.Threading.Tasks
46
open Microsoft.AspNetCore.Mvc
57
open Microsoft.AspNetCore.Mvc.Formatters
68
open Swagger.Internal
@@ -19,6 +21,13 @@ type ReturnCsvController() =
1921
member this.Get() =
2022
"Hello,world" |> ActionResult<string>
2123

24+
[<Route("api/[controller]")>]
25+
[<ApiController>]
26+
type ConsumesTextController() =
27+
[<HttpPost; Consumes("text/plain"); Produces("text/plain")>]
28+
member this.Post([<FromBody>] request: string) =
29+
request |> ActionResult<string>
30+
2231
// Simple CSV output formatter
2332
// This formatter assumes the controller returns a string (already CSV-formatted)
2433
type CsvOutputFormatter() as this =
@@ -38,3 +47,22 @@ type CsvOutputFormatter() as this =
3847
let value = context.Object :?> string
3948
let bytes = encoding.GetBytes(value)
4049
response.Body.WriteAsync(bytes, 0, bytes.Length)
50+
51+
// Text/plain input formatter for reading plain text request bodies
52+
type TextPlainInputFormatter() as this =
53+
inherit TextInputFormatter()
54+
55+
do
56+
this.SupportedMediaTypes.Add("text/plain")
57+
this.SupportedEncodings.Add(Encoding.UTF8)
58+
this.SupportedEncodings.Add(Encoding.Unicode)
59+
60+
override this.CanRead(context) =
61+
base.CanRead(context) && context.ModelType = typeof<string>
62+
63+
override _.ReadRequestBodyAsync(context, encoding) =
64+
task {
65+
use reader = new StreamReader(context.HttpContext.Request.Body, encoding)
66+
let! content = reader.ReadToEndAsync()
67+
return InputFormatterResult.Success(content)
68+
}

tests/Swashbuckle.WebApi.Server/Startup.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ type Startup private () =
2424
let converters = options.JsonSerializerOptions.Converters
2525
converters.Add(JsonFSharpConverter())
2626
converters.Add(JsonStringEnumConverter()))
27-
.AddMvcOptions(_.OutputFormatters.Add(CsvOutputFormatter()))
27+
.AddMvcOptions(fun options ->
28+
options.OutputFormatters.Add(CsvOutputFormatter())
29+
options.InputFormatters.Add(TextPlainInputFormatter()))
2830
|> ignore
2931
// Register the Swagger & OpenApi services
3032
services.AddSwaggerGen(fun c ->

0 commit comments

Comments
 (0)