Skip to content

Commit

Permalink
Merge pull request #58
Browse files Browse the repository at this point in the history
Spring: support all media-types for consumes
  • Loading branch information
cc-jhr authored Aug 19, 2021
2 parents 0797dc5 + fbcff23 commit 1ae5d0a
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,39 @@ import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.mvc.method.RequestMappingInfo
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.jvmErasure
import kotlin.reflect.jvm.kotlinFunction

internal fun Map.Entry<RequestMappingInfo, HandlerMethod>.consumes(): Set<String> {
val consumes = this.key
.consumesCondition
.expressions
.map { it.mediaType.toString() }
.toSet()

if (consumes.isNotEmpty()) {
return consumes
}

val providesRequestBodyAnnotation = this.value
.method
.kotlinFunction
?.parameters
?.any {
it.annotations
.filterIsInstance<RequestBody>()
.any()
it.findAnnotation<RequestBody>() !== null
} ?: false

if (!providesRequestBodyAnnotation) {
return emptySet()
}

val consumes = this.key
.consumesCondition
.expressions
.map { it.mediaType.toString() }
.toSet()

if (consumes.isNotEmpty()) {
return consumes
}

val isParameterString = this.value
.method
.kotlinFunction
?.parameters
?.firstOrNull {
it.annotations
.filterIsInstance<RequestBody>()
.any()
it.findAnnotation<RequestBody>() !== null
}
?.type
?.jvmErasure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.http.MediaType.*
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile

@SpringBootApplication
open class DummyApp
Expand Down Expand Up @@ -43,6 +44,21 @@ open class RequestMappingOneMediaTypeIsExtractedCorrectlyController {
fun todos(@RequestBody todo: Todo) { }
}

@Controller
@Suppress("UNUSED_PARAMETER")
open class RequestMappingMultipartFormIsExtractedCorrectlyController {

@RequestMapping(
path = ["/form"],
method = [RequestMethod.POST],
consumes = [MULTIPART_FORM_DATA_VALUE]
)
fun form(
@RequestPart("title") title: String,
@RequestPart("file") form: MultipartFile
) { }
}

@Controller
@Suppress("UNUSED_PARAMETER")
open class RequestMappingMultipleMediaTypesAreExtractedCorrectlyController {
Expand Down Expand Up @@ -120,7 +136,7 @@ open class RequestMappingOnClassWithoutRequestBodyAnnotationController {

@Controller
@Suppress("UNUSED_PARAMETER")
open class RequestMappingOnFunctionWithoutRequestBodyAnnotationController {
open class RequestMappingOnFunctionWithoutConsumesAnnotationController {

@RequestMapping("/todos")
fun todos(todo: String) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ class SpringConverterConsumesTest {

@Nested
inner class ClassLevelTests {

@Nested
@WebMvcTest(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class OneMediaTypeIsInheritedByAllFunctionsTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `media type declared at class level using RequestMapping annotation is inherited by all functions`() {
//given
Expand Down Expand Up @@ -94,22 +94,22 @@ class SpringConverterConsumesTest {
),
Endpoint("/tags", OPTIONS)
)

//when
val implementation = SpringConverter(context)

//then
assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification)
}
}

@Nested
@WebMvcTest(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class MultipleMediaTypesAreInheritedByAllFunctionsTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `multiple media types declared at class level using RequestMapping annotation are inherited by all functions`() {
//given
Expand Down Expand Up @@ -177,10 +177,10 @@ class SpringConverterConsumesTest {
),
Endpoint("/tags", OPTIONS)
)

//when
val implementation = SpringConverter(context)

//then
assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification)
}
Expand Down Expand Up @@ -318,17 +318,17 @@ class SpringConverterConsumesTest {
}
}
}

@Nested
inner class FunctionLevelTests {

@Nested
@WebMvcTest(RequestMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class OneMediaTypeIsExtractedCorrectlyTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `media type declared at function level using RequestMapping annotation is extracted correctly`() {
//given
Expand Down Expand Up @@ -365,22 +365,57 @@ class SpringConverterConsumesTest {
),
Endpoint("/todos", OPTIONS)
)

//when
val implementation = SpringConverter(context)

//then
assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification)
}
}


@Nested
@WebMvcTest(RequestMappingMultipartFormIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class MultipartFormIsExtractedCorrectlyTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `multipart form media type is extracted correctly`() {
//given
val specification: Set<Endpoint> = setOf(
Endpoint(
path = "/form",
httpMethod = POST,
consumes = setOf(MULTIPART_FORM_DATA_VALUE)
),
Endpoint(
path = "/form",
httpMethod = HEAD,
consumes = setOf(MULTIPART_FORM_DATA_VALUE)
),
Endpoint(
path = "/form",
httpMethod = OPTIONS,
),
)

//when
val implementation = SpringConverter(context)

//then
assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification)
}
}

@Nested
@WebMvcTest(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class MultipleMediaTypesAreExtractedCorrectlyTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `multiple media types declared at function level using RequestMapping annotation are extracted correctly`() {
//given
Expand Down Expand Up @@ -417,10 +452,10 @@ class SpringConverterConsumesTest {
),
Endpoint("/todos", OPTIONS)
)

//when
val implementation = SpringConverter(context)

//then
assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification)
}
Expand Down Expand Up @@ -531,14 +566,14 @@ class SpringConverterConsumesTest {
}

@Nested
@WebMvcTest(RequestMappingOnFunctionWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class NoRequestBodyAnnotationTest {
@WebMvcTest(RequestMappingOnFunctionWithoutConsumesAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class])
inner class EmptyAnnotationTest {

@Autowired
lateinit var context: ConfigurableApplicationContext

@Test
fun `no RequestBody annotation results in an empty produces list`() {
fun `no RequestBody nor consumes annotation results in an empty produces list`() {
//given
val specification: Set<Endpoint> = setOf(
Endpoint("/todos", GET),
Expand Down

0 comments on commit 1ae5d0a

Please sign in to comment.