Repository: javalin/javalin (kotlin)

Issue:
Info about Context#queryParams
Just wanted to know if javalin provides a way to extract queryParams and mapping it to a specific class.

Expected behavior:
- Availability of method Context#queryParamsAsClass(String, Class)

Current behavior:
- There is no such method

Pull request:
closes #2306

Patch:
diff --git a/javalin/src/main/java/io/javalin/http/Context.kt b/javalin/src/main/java/io/javalin/http/Context.kt
index f15765269..070fc9624 100644
--- a/javalin/src/main/java/io/javalin/http/Context.kt
+++ b/javalin/src/main/java/io/javalin/http/Context.kt
@@ -178,6 +178,15 @@ interface Context {
     /** Gets a list of form params for the specified key, or empty list. */
     fun formParams(key: String): List<String> = formParamMap()[key] ?: emptyList()
 
+    /** Gets a list of form params for the specified key, or empty list. */
+    fun <T> formParamsAsClass(key: String, clazz: Class<T>): Validator<List<T>> {
+        val params = (formParamMap()[key] ?: emptyList()).map {
+            appData(ValidationKey).validator(key, clazz, it).get()
+        }
+
+        return appData(ValidationKey).validator(key, params)
+    }
+
     /** Gets a map with all the form param keys and values. */
     fun formParamMap(): Map<String, List<String>> = when {
         isMultipartFormData() -> MultipartUtil.getFieldMap(req())
@@ -211,6 +220,15 @@ interface Context {
     /** Gets a list of query params for the specified key, or empty list. */
     fun queryParams(key: String): List<String> = queryParamMap()[key] ?: emptyList()
 
+    /** Gets a list of query params for the specified key, or empty list. */
+    fun <T> queryParamsAsClass(key: String, clazz: Class<T>): Validator<List<T>> {
+        val params = (queryParamMap()[key] ?: emptyList()).map {
+            appData(ValidationKey).validator(key, clazz, it).get()
+        }
+
+        return appData(ValidationKey).validator(key, params)
+    }
+
     /** Gets a map with all the query param keys and values. */
     fun queryParamMap(): Map<String, List<String>> = splitKeyValueStringAndGroupByKey(queryString() ?: "", characterEncoding() ?: "UTF-8")
 
@@ -527,5 +545,11 @@ inline fun <reified T : Any> Context.headerAsClass(header: String): Validator<T>
 /** Reified version of [Context.queryParamAsClass] (Kotlin only) */
 inline fun <reified T : Any> Context.queryParamAsClass(key: String): Validator<T> = queryParamAsClass(key, T::class.java)
 
+/** Reified version of [Context.queryParamsAsClass] (Kotlin only) */
+inline fun <reified T : Any> Context.queryParamsAsClass(key: String): Validator<List<T>> = queryParamsAsClass(key, T::class.java)
+
 /** Reified version of [Context.formParamAsClass] (Kotlin only) */
 inline fun <reified T : Any> Context.formParamAsClass(key: String): Validator<T> = formParamAsClass(key, T::class.java)
+
+/** Reified version of [Context.formParamsAsClass] (Kotlin only) */
+inline fun <reified T : Any> Context.formParamsAsClass(key: String): Validator<List<T>> = formParamsAsClass(key, T::class.java)


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestRequest.kt b/javalin/src/test/java/io/javalin/TestRequest.kt
index d6eabf452..43dd54b96 100644
--- a/javalin/src/test/java/io/javalin/TestRequest.kt
+++ b/javalin/src/test/java/io/javalin/TestRequest.kt
@@ -7,10 +7,13 @@
 package io.javalin
 
 import io.javalin.http.Header
+import io.javalin.http.HttpStatus
 import io.javalin.http.HttpStatus.INTERNAL_SERVER_ERROR
 import io.javalin.http.formParamAsClass
+import io.javalin.http.formParamsAsClass
 import io.javalin.http.headerAsClass
 import io.javalin.http.queryParamAsClass
+import io.javalin.http.queryParamsAsClass
 import io.javalin.http.servlet.SESSION_CACHE_KEY_PREFIX
 import io.javalin.http.staticfiles.Location
 import io.javalin.plugin.bundled.BasicAuthPlugin
@@ -221,6 +224,15 @@ class TestRequest {
         assertThat(http.getBody("/?qp1=1&qp1=2&qp1=3")).isEqualTo("[1, 2, 3]")
     }
 
+    @Test
+    fun `queryParams returns list of converted and validated supplied params`() = TestUtil.test { app, http ->
+        app.get("/1") { it.result(it.queryParamsAsClass<Double>("qp1").check({ it.all { it > .0 } }, "all values must be greater then 0").get().toString()) }
+        assertThat(http.getBody("/1?qp1=1.3&qp1=2.1&qp1=3.2")).isEqualTo("[1.3, 2.1, 3.2]")
+
+        app.get("/2") { it.result(it.queryParamsAsClass<Double>("qp1").check({ it.all { it > 0 } }, "all values must be greater then 0").get().toString()) }
+        assertThat(http.getStatus("/2?qp1=1.3&qp1=-2.1&qp1=3.2")).isEqualTo(HttpStatus.BAD_REQUEST)
+    }
+
     @Test
     fun `query params that are invalidly encoded are nulled`() = TestUtil.test { app, http ->
         app.get("/1") { it.result("${it.queryParam("qp")}") }
@@ -257,6 +269,15 @@ class TestRequest {
         assertThat(http.post("/").body("fp1=1&fp2=2").asString().body).isEqualTo("4")
     }
 
+    @Test
+    fun `formParams returns list of converted and validated supplied params`() = TestUtil.test { app, http ->
+        app.post("/1") { it.result(it.formParamsAsClass<Double>("fp1").check({ it.all { it > .0 } }, "all values must be greater then 0").get().toString()) }
+        assertThat(http.post("/1").body("fp1=1.3&fp1=2.1&fp1=3.2").asString().body).isEqualTo("[1.3, 2.1, 3.2]")
+
+        app.post("/2") { it.result(it.formParamsAsClass<Double>("fp1").check({ it.all { it > 0 } }, "all values must be greater then 0").get().toString()) }
+        assertThat(http.post("/2").body("fp1=1.3&fp1=-2.1&fp1=3.2").asString().status).isEqualTo(HttpStatus.BAD_REQUEST.code)
+    }
+
     @Test
     fun `basicAuthCredentials extracts username and password when header properly configured`() = TestUtil.test { app, http ->
         app.get("/") { ctx ->