Skip to content

Commit 4b8dd27

Browse files
authored
Merge pull request #117 from kkalisz/form-url-encoded
Support application/x-www-form-urlencoded content type
2 parents 3b3429f + 0932806 commit 4b8dd27

File tree

6 files changed

+145
-7
lines changed

6 files changed

+145
-7
lines changed

src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/FormDataRequest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import com.papsign.ktor.openapigen.annotations.encodings.APIRequestFormat
55
@Target(AnnotationTarget.CLASS)
66
@Retention(AnnotationRetention.RUNTIME)
77
@APIRequestFormat
8-
annotation class FormDataRequest
8+
annotation class FormDataRequest(val type: FormDataRequestType = FormDataRequestType.MULTIPART)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.papsign.ktor.openapigen.content.type.multipart
2+
3+
import io.ktor.http.ContentType
4+
5+
enum class FormDataRequestType(val contentType: ContentType){
6+
MULTIPART(ContentType.MultiPart.FormData),
7+
URL_ENCODED(ContentType.Application.FormUrlEncoded)
8+
}

src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
114114

115115
override fun <T> getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: ContentTypeProvider.Usage): Map<ContentType, MediaTypeModel<T>>? {
116116
if (type == unitKType) return null
117-
type.jvmErasure.findAnnotation<FormDataRequest>() ?: return null
117+
val formContentType = type.jvmErasure.findAnnotation<FormDataRequest>()?.type?.contentType ?: return null
118118
val ctor = type.jvmErasure.primaryConstructor
119119
when (usage) {
120120
ContentTypeProvider.Usage.PARSE -> {
@@ -143,6 +143,6 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
143143
}
144144
val schemaBuilder = provider.ofType<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
145145
@Suppress("UNCHECKED_CAST")
146-
return mapOf(ContentType.MultiPart.FormData to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
146+
return mapOf(formContentType to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
147147
}
148148
}

src/test/kotlin/OneOf.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import TestServer.Setup
1+
import TestServer.setupBaseTestServer
22
import com.fasterxml.jackson.annotation.JsonTypeInfo
33
import com.fasterxml.jackson.annotation.JsonTypeName
44
import com.papsign.ktor.openapigen.annotations.type.`object`.example.ExampleProvider
@@ -57,7 +57,7 @@ val ref = "\$ref"
5757
internal class OneOfLegacyGenerationTests {
5858
@Test
5959
fun willDiscriminatorsBePresent() = withTestApplication({
60-
Setup()
60+
setupBaseTestServer()
6161
apiRouting {
6262
SealedRoute()
6363
}

src/test/kotlin/TestServer.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ object TestServer {
5555
class ProperException(msg: String, val id: String = "proper.exception") : Exception(msg)
5656

5757
fun Application.testServer() {
58-
Setup()
58+
setupBaseTestServer()
5959

6060
apiRouting {
6161

@@ -247,7 +247,7 @@ object TestServer {
247247
}
248248

249249

250-
fun Application.Setup() {
250+
fun Application.setupBaseTestServer() {
251251
//define basic OpenAPI info
252252
val api = install(OpenAPIGen) {
253253
info {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.papsign.ktor.openapigen
2+
3+
import TestServer.setupBaseTestServer
4+
import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequest
5+
import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequestType
6+
import com.papsign.ktor.openapigen.content.type.multipart.NamedFileInputStream
7+
import com.papsign.ktor.openapigen.route.apiRouting
8+
import com.papsign.ktor.openapigen.route.path.normal.post
9+
import com.papsign.ktor.openapigen.route.response.respond
10+
import com.papsign.ktor.openapigen.route.route
11+
import io.ktor.application.*
12+
import io.ktor.http.*
13+
import io.ktor.server.testing.*
14+
import org.junit.Assert.assertEquals
15+
import org.junit.Assert.assertTrue
16+
import org.junit.Test
17+
18+
internal class FormDocumentationGenerationTest {
19+
20+
@Test
21+
fun formDataTestRequest() = withTestApplication({
22+
setupBaseTestServer()
23+
apiRouting {
24+
route("form-data"){
25+
post<Unit, TestServer.StringResponse, FormData>{ _, _ ->
26+
respond(TestServer.StringResponse("result"))
27+
}
28+
}
29+
}
30+
}) {
31+
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
32+
this@withTestApplication.application.log.debug(response.content)
33+
assertEquals(HttpStatusCode.OK, response.status())
34+
assertTrue(
35+
response.content!!.contains(
36+
""" "paths" : {
37+
"/form-data" : {
38+
"post" : {
39+
"requestBody" : {
40+
"content" : {
41+
"application/x-www-form-urlencoded" : {
42+
"schema" : {
43+
"${"$"}ref" : "#/components/schemas/FormData"
44+
}
45+
}
46+
}
47+
},"""
48+
)
49+
)
50+
51+
}
52+
}
53+
54+
@Test
55+
fun multipartFormDataTestRequest() = withTestApplication({
56+
setupBaseTestServer()
57+
apiRouting {
58+
route("multipart-data"){
59+
post<Unit, TestServer.StringResponse, MultiPartForm>{ _, _ ->
60+
respond(TestServer.StringResponse("result"))
61+
}
62+
}
63+
}
64+
}) {
65+
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
66+
this@withTestApplication.application.log.debug(response.content)
67+
assertEquals(HttpStatusCode.OK, response.status())
68+
assertTrue(
69+
response.content!!.contains(
70+
""" "paths" : {
71+
"/multipart-data" : {
72+
"post" : {
73+
"requestBody" : {
74+
"content" : {
75+
"multipart/form-data" : {
76+
"schema" : {
77+
"${"$"}ref" : "#/components/schemas/MultiPartForm"
78+
}
79+
}
80+
}
81+
},"""
82+
)
83+
)
84+
85+
}
86+
}
87+
88+
@Test
89+
fun defaultFormDataTestRequest() = withTestApplication({
90+
setupBaseTestServer()
91+
apiRouting {
92+
route("default-form-data"){
93+
post<Unit, TestServer.StringResponse, DefaultFormData>{ _, _ ->
94+
respond(TestServer.StringResponse("result"))
95+
}
96+
}
97+
}
98+
}) {
99+
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
100+
this@withTestApplication.application.log.debug(response.content)
101+
assertEquals(HttpStatusCode.OK, response.status())
102+
assertTrue(
103+
response.content!!.contains(
104+
""" "paths" : {
105+
"/default-form-data" : {
106+
"post" : {
107+
"requestBody" : {
108+
"content" : {
109+
"multipart/form-data" : {
110+
"schema" : {
111+
"${"$"}ref" : "#/components/schemas/DefaultFormData"
112+
}
113+
}
114+
}
115+
},"""
116+
)
117+
)
118+
119+
}
120+
}
121+
}
122+
123+
@FormDataRequest(type = FormDataRequestType.MULTIPART)
124+
data class MultiPartForm(val userId: String, val file: NamedFileInputStream)
125+
126+
@FormDataRequest(type = FormDataRequestType.URL_ENCODED)
127+
data class FormData(val login: String, val password: String)
128+
129+
@FormDataRequest
130+
data class DefaultFormData(val login: String, val password: String)

0 commit comments

Comments
 (0)