Skip to content

Commit

Permalink
Merge pull request #117 from kkalisz/form-url-encoded
Browse files Browse the repository at this point in the history
Support application/x-www-form-urlencoded content type
  • Loading branch information
Wicpar authored Nov 5, 2021
2 parents 3b3429f + 0932806 commit 4b8dd27
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import com.papsign.ktor.openapigen.annotations.encodings.APIRequestFormat
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@APIRequestFormat
annotation class FormDataRequest
annotation class FormDataRequest(val type: FormDataRequestType = FormDataRequestType.MULTIPART)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.papsign.ktor.openapigen.content.type.multipart

import io.ktor.http.ContentType

enum class FormDataRequestType(val contentType: ContentType){
MULTIPART(ContentType.MultiPart.FormData),
URL_ENCODED(ContentType.Application.FormUrlEncoded)
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension

override fun <T> getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: ContentTypeProvider.Usage): Map<ContentType, MediaTypeModel<T>>? {
if (type == unitKType) return null
type.jvmErasure.findAnnotation<FormDataRequest>() ?: return null
val formContentType = type.jvmErasure.findAnnotation<FormDataRequest>()?.type?.contentType ?: return null
val ctor = type.jvmErasure.primaryConstructor
when (usage) {
ContentTypeProvider.Usage.PARSE -> {
Expand Down Expand Up @@ -143,6 +143,6 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
}
val schemaBuilder = provider.ofType<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
@Suppress("UNCHECKED_CAST")
return mapOf(ContentType.MultiPart.FormData to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
return mapOf(formContentType to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
}
}
4 changes: 2 additions & 2 deletions src/test/kotlin/OneOf.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import TestServer.Setup
import TestServer.setupBaseTestServer
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeName
import com.papsign.ktor.openapigen.annotations.type.`object`.example.ExampleProvider
Expand Down Expand Up @@ -57,7 +57,7 @@ val ref = "\$ref"
internal class OneOfLegacyGenerationTests {
@Test
fun willDiscriminatorsBePresent() = withTestApplication({
Setup()
setupBaseTestServer()
apiRouting {
SealedRoute()
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/kotlin/TestServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object TestServer {
class ProperException(msg: String, val id: String = "proper.exception") : Exception(msg)

fun Application.testServer() {
Setup()
setupBaseTestServer()

apiRouting {

Expand Down Expand Up @@ -247,7 +247,7 @@ object TestServer {
}


fun Application.Setup() {
fun Application.setupBaseTestServer() {
//define basic OpenAPI info
val api = install(OpenAPIGen) {
info {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.papsign.ktor.openapigen

import TestServer.setupBaseTestServer
import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequest
import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequestType
import com.papsign.ktor.openapigen.content.type.multipart.NamedFileInputStream
import com.papsign.ktor.openapigen.route.apiRouting
import com.papsign.ktor.openapigen.route.path.normal.post
import com.papsign.ktor.openapigen.route.response.respond
import com.papsign.ktor.openapigen.route.route
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

internal class FormDocumentationGenerationTest {

@Test
fun formDataTestRequest() = withTestApplication({
setupBaseTestServer()
apiRouting {
route("form-data"){
post<Unit, TestServer.StringResponse, FormData>{ _, _ ->
respond(TestServer.StringResponse("result"))
}
}
}
}) {
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
this@withTestApplication.application.log.debug(response.content)
assertEquals(HttpStatusCode.OK, response.status())
assertTrue(
response.content!!.contains(
""" "paths" : {
"/form-data" : {
"post" : {
"requestBody" : {
"content" : {
"application/x-www-form-urlencoded" : {
"schema" : {
"${"$"}ref" : "#/components/schemas/FormData"
}
}
}
},"""
)
)

}
}

@Test
fun multipartFormDataTestRequest() = withTestApplication({
setupBaseTestServer()
apiRouting {
route("multipart-data"){
post<Unit, TestServer.StringResponse, MultiPartForm>{ _, _ ->
respond(TestServer.StringResponse("result"))
}
}
}
}) {
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
this@withTestApplication.application.log.debug(response.content)
assertEquals(HttpStatusCode.OK, response.status())
assertTrue(
response.content!!.contains(
""" "paths" : {
"/multipart-data" : {
"post" : {
"requestBody" : {
"content" : {
"multipart/form-data" : {
"schema" : {
"${"$"}ref" : "#/components/schemas/MultiPartForm"
}
}
}
},"""
)
)

}
}

@Test
fun defaultFormDataTestRequest() = withTestApplication({
setupBaseTestServer()
apiRouting {
route("default-form-data"){
post<Unit, TestServer.StringResponse, DefaultFormData>{ _, _ ->
respond(TestServer.StringResponse("result"))
}
}
}
}) {
with(handleRequest(HttpMethod.Get, "//openapi.json")) {
this@withTestApplication.application.log.debug(response.content)
assertEquals(HttpStatusCode.OK, response.status())
assertTrue(
response.content!!.contains(
""" "paths" : {
"/default-form-data" : {
"post" : {
"requestBody" : {
"content" : {
"multipart/form-data" : {
"schema" : {
"${"$"}ref" : "#/components/schemas/DefaultFormData"
}
}
}
},"""
)
)

}
}
}

@FormDataRequest(type = FormDataRequestType.MULTIPART)
data class MultiPartForm(val userId: String, val file: NamedFileInputStream)

@FormDataRequest(type = FormDataRequestType.URL_ENCODED)
data class FormData(val login: String, val password: String)

@FormDataRequest
data class DefaultFormData(val login: String, val password: String)

0 comments on commit 4b8dd27

Please sign in to comment.