Skip to content

Commit

Permalink
Update kotlin to 2.0 (#385)
Browse files Browse the repository at this point in the history
* Bump jvm from 1.9.23 to 2.0.0

Bumps [jvm](https://github.com/JetBrains/kotlin) from 1.9.23 to 2.0.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](JetBrains/kotlin@v1.9.23...v2.0.0)

---
updated-dependencies:
- dependency-name: jvm
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Breaking Change: support kotlin 2.0

* Change: make changes only slightly breaking

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yannick Block <yannick.block@moia.io>
Co-authored-by: Julius Wigankow <julius.wigankow@moia.io>
  • Loading branch information
4 people committed Jun 17, 2024
1 parent 5b539f7 commit d01321a
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 46 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {

plugins {
java
kotlin("jvm") version "1.9.23"
kotlin("jvm") version "2.0.0"
`maven-publish`
jacoco
id("com.github.kt3k.coveralls") version "2.12.2"
Expand Down
26 changes: 8 additions & 18 deletions router/src/main/kotlin/io/moia/router/RequestHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ import com.google.common.net.MediaType
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.reflect.KClass
import kotlin.reflect.jvm.ExperimentalReflectionOnLambdas
import kotlin.reflect.jvm.reflect

@Suppress("UnstableApiUsage")
abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
open val objectMapper = jacksonObjectMapper()

Expand All @@ -49,7 +46,6 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
.apply { headers = headers.mapKeys { it.key.lowercase() } }
.let { router.filter.then(this::handleRequest)(it) }

@ExperimentalReflectionOnLambdas
@Suppress("UNCHECKED_CAST")
private fun handleRequest(input: APIGatewayProxyRequestEvent): APIGatewayProxyResponseEvent {
log.debug(
Expand All @@ -66,16 +62,16 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
routerFunction.requestPredicate.matchedAcceptType(input.acceptedMediaTypes())
?: MediaType.parse(router.defaultContentType)

val handler: HandlerFunction<Any, Any> = routerFunction.handler
val handler: HandlerFunctionWrapper<Any, Any> = routerFunction.handler

val response =
try {
if (missingPermissions(input, routerFunction)) {
throw ApiException("missing permissions", "MISSING_PERMISSIONS", 403)
} else {
val requestBody = deserializeRequest(handler, input)
val request = Request(input, requestBody, routerFunction.requestPredicate.pathPattern)
(handler as HandlerFunction<*, *>)(request)
val request = Request(input, requestBody, routerFunction.requestPredicate.pathPattern) as Request<Any>
handler.handlerFunction(request)
}
} catch (e: Exception) {
exceptionToResponseEntity(e, input)
Expand Down Expand Up @@ -144,22 +140,16 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG

open fun predicatePermissionHandlerSupplier(): ((r: APIGatewayProxyRequestEvent) -> PredicatePermissionHandler)? = null

@ExperimentalReflectionOnLambdas
private fun deserializeRequest(
handler: HandlerFunction<Any, Any>,
handler: HandlerFunctionWrapper<Any, Any>,
input: APIGatewayProxyRequestEvent,
): Any? {
val requestType =
handler.reflect()?.parameters?.first()?.type?.arguments?.first()?.type
?: throw IllegalArgumentException(
"reflection failed, try using a real lambda instead of function references (Kotlin 1.6 bug?)",
)
return when {
requestType.classifier as KClass<*> == Unit::class -> Unit
input.body == null && requestType.isMarkedNullable -> null
handler.requestType.classifier as KClass<*> == Unit::class -> Unit
input.body == null && handler.requestType.isMarkedNullable -> null
input.body == null -> throw ApiException("no request body present", "REQUEST_BODY_MISSING", 400)
input.body is String && requestType.classifier as KClass<*> == String::class -> input.body
else -> deserializationHandlerChain.deserialize(input, requestType)
input.body is String && handler.requestType.classifier as KClass<*> == String::class -> input.body
else -> deserializationHandlerChain.deserialize(input, handler.requestType)
}
}

Expand Down
57 changes: 39 additions & 18 deletions router/src/main/kotlin/io/moia/router/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package io.moia.router
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent.ProxyRequestContext
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import kotlin.reflect.KType
import kotlin.reflect.typeOf

typealias PredicateFactory = (String, String, Set<String>, Set<String>) -> RequestPredicate

Expand All @@ -33,35 +35,35 @@ class Router(private val predicateFactory: PredicateFactory) {

var filter: Filter = Filter.NoOp

fun <I, T> GET(
inline fun <reified I, reified T> GET(
pattern: String,
handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "GET", handlerFunction, emptySet())
crossinline handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "GET", HandlerFunctionWrapper.invoke(handlerFunction), emptySet())

fun <I, T> POST(
inline fun <reified I, reified T> POST(
pattern: String,
handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "POST", handlerFunction)
crossinline handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "POST", HandlerFunctionWrapper.invoke(handlerFunction))

fun <I, T> PUT(
inline fun <reified I, reified T> PUT(
pattern: String,
handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "PUT", handlerFunction)
crossinline handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "PUT", HandlerFunctionWrapper.invoke(handlerFunction))

fun <I, T> DELETE(
inline fun <reified I, reified T> DELETE(
pattern: String,
handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "DELETE", handlerFunction, emptySet())
crossinline handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "DELETE", HandlerFunctionWrapper.invoke(handlerFunction), emptySet())

fun <I, T> PATCH(
inline fun <reified I, reified T> PATCH(
pattern: String,
handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "PATCH", handlerFunction)
crossinline handlerFunction: HandlerFunction<I, T>,
) = defaultRequestPredicate(pattern, "PATCH", HandlerFunctionWrapper.invoke(handlerFunction))

private fun <I, T> defaultRequestPredicate(
fun <I, T> defaultRequestPredicate(
pattern: String,
method: String,
handlerFunction: HandlerFunction<I, T>,
handlerFunction: HandlerFunctionWrapper<I, T>,
consuming: Set<String> = defaultConsuming,
) = predicateFactory(method, pattern, consuming, defaultProducing)
.also { routes += RouterFunction(it, handlerFunction) }
Expand Down Expand Up @@ -108,9 +110,28 @@ fun Filter.then(next: APIGatewayRequestHandlerFunction): APIGatewayRequestHandle
typealias APIGatewayRequestHandlerFunction = (APIGatewayProxyRequestEvent) -> APIGatewayProxyResponseEvent
typealias HandlerFunction<I, T> = (request: Request<I>) -> ResponseEntity<T>

abstract class HandlerFunctionWrapper<I, T> {
abstract val requestType: KType
abstract val responseType: KType

abstract val handlerFunction: HandlerFunction<I, T>

companion object {
inline operator fun <reified I, reified T> invoke(crossinline handler: HandlerFunction<I, T>): HandlerFunctionWrapper<I, T> {
val requestType = typeOf<I>()
val responseType = typeOf<T>()
return object : HandlerFunctionWrapper<I, T>() {
override val requestType: KType = requestType
override val responseType: KType = responseType
override val handlerFunction: HandlerFunction<I, T> = { request -> handler.invoke(request) }
}
}
}
}

class RouterFunction<I, T>(
val requestPredicate: RequestPredicate,
val handler: HandlerFunction<I, T>,
val handler: HandlerFunctionWrapper<I, T>,
) {
override fun toString(): String {
return "RouterFunction(requestPredicate=$requestPredicate)"
Expand Down
17 changes: 9 additions & 8 deletions router/src/test/kotlin/io/moia/router/RequestHandlerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import io.mockk.mockk
import io.moia.router.Router.Companion.router
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.time.LocalDate

@Suppress("ktlint:standard:max-line-length")
Expand Down Expand Up @@ -710,7 +709,7 @@ class RequestHandlerTest {
}

@Test
fun `should fail for function references when using Kotlin 1_6_10`() {
fun `should be able to use function references as handler`() {
class DummyHandler : RequestHandler() {
val dummy =
object {
Expand All @@ -725,15 +724,17 @@ class RequestHandlerTest {
GET("/some", dummy::handler).producing("application/json")
}
}
assertThrows<IllegalArgumentException> {

val response =
DummyHandler().handleRequest(
APIGatewayProxyRequestEvent()
.withHttpMethod("GET")
.withPath("/some")
.withAcceptHeader("application/json"),
mockk(),
)
}

assertThat(response.statusCode).isEqualTo(200)
}

class TestRequestHandlerAuthorization : RequestHandler() {
Expand Down Expand Up @@ -864,16 +865,16 @@ class RequestHandlerTest {
POST("/no-content") { _: Request<TestRequest> ->
ResponseEntity.noContent()
}
POST("/create-without-location") { _: Request<TestRequest> ->
POST<TestRequest, Unit>("/create-without-location") { _: Request<TestRequest> ->
ResponseEntity.created(null, null, emptyMap())
}
POST("/create-with-location") { r: Request<TestRequest> ->
POST<TestRequest, Unit>("/create-with-location") { r: Request<TestRequest> ->
ResponseEntity.created(null, r.apiRequest.location("test"), emptyMap())
}
DELETE("/delete-me") { _: Request<Unit> ->
ResponseEntity.noContent()
}
GET("/non-existing-path-parameter") { request: Request<Unit> ->
GET<Unit, Unit>("/non-existing-path-parameter") { request: Request<Unit> ->
request.getPathParameter("foo")
ResponseEntity.ok(null)
}
Expand All @@ -883,7 +884,7 @@ class RequestHandlerTest {
class TestQueryParamParsingHandler : RequestHandler() {
override val router =
router {
GET("/search") { r: Request<TestRequestHandler.TestRequest> ->
GET<TestRequestHandler.TestRequest, Unit>("/search") { r: Request<TestRequestHandler.TestRequest> ->
assertThat(r.getQueryParameter("testQueryParam")).isNotNull()
assertThat(r.getQueryParameter("testQueryParam")).isEqualTo("foo")
assertThat(r.queryParameters!!["testQueryParam"]).isNotNull()
Expand Down
2 changes: 1 addition & 1 deletion router/src/test/kotlin/io/moia/router/RouterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class RouterTest {
fun `should not consume for a deletion route`() {
val router =
router {
DELETE("/delete-me") { _: Request<Unit> ->
DELETE<Unit, Unit>("/delete-me") { _: Request<Unit> ->
ResponseEntity.ok(null)
}
}
Expand Down

0 comments on commit d01321a

Please sign in to comment.