Repository: tipsy/javalin
Pull request #1130 (2020-12-11 18:48:13)

restore splats to context
After some discussion in #526, this will restore the `splat` and `splats` methods the the `Context`.


Patch:
diff --git a/javalin/src/main/java/io/javalin/core/PathParser.kt b/javalin/src/main/java/io/javalin/core/PathParser.kt
index ebc1e9d4..372e662a 100644
--- a/javalin/src/main/java/io/javalin/core/PathParser.kt
+++ b/javalin/src/main/java/io/javalin/core/PathParser.kt
@@ -27,6 +27,7 @@ class PathParser(path: String, ignoreTrailingSlashes: Boolean) {
     private val matchRegex = ("^/${segments.joinToString("/") { it.asRegexString() }}" + matchRegexSuffix + "$").toRegex()
 
     private val pathParamRegex = matchRegex.pattern.replace("[^/]+?", "([^/]+?)").toRegex()
+    private val splatRegex = matchRegex.pattern.replace(".*?", "(.*?)").toRegex(RegexOption.IGNORE_CASE)
 
     fun matches(url: String): Boolean = url matches matchRegex
 
@@ -34,6 +35,8 @@ class PathParser(path: String, ignoreTrailingSlashes: Boolean) {
         name to ContextUtil.urlDecode(value)
     }.toMap()
 
+    fun extractSplats(url: String) = values(splatRegex, url).map { ContextUtil.urlDecode(it) }
+
     // Match and group values, then drop first element (the input string)
     private fun values(regex: Regex, url: String) = regex.matchEntire(url)?.groupValues?.drop(1) ?: emptyList()
 }
diff --git a/javalin/src/main/java/io/javalin/http/Context.kt b/javalin/src/main/java/io/javalin/http/Context.kt
index 309a60ac..726d8471 100644
--- a/javalin/src/main/java/io/javalin/http/Context.kt
+++ b/javalin/src/main/java/io/javalin/http/Context.kt
@@ -39,6 +39,7 @@ open class Context(@JvmField val req: HttpServletRequest, @JvmField val res: Htt
     @get:JvmSynthetic @set:JvmSynthetic internal var endpointHandlerPath = ""
     @get:JvmSynthetic @set:JvmSynthetic internal var pathParamMap = mapOf<String, String>()
     @get:JvmSynthetic @set:JvmSynthetic internal var handlerType = HandlerType.BEFORE
+    @get:JvmSynthetic @set:JvmSynthetic internal var splatList = listOf<String>()
     // @formatter:on
 
     private val cookieStore by lazy { CookieStore(cookie(CookieStore.COOKIE_NAME)) }
@@ -484,4 +485,14 @@ open class Context(@JvmField val req: HttpServletRequest, @JvmField val res: Htt
         return html(JavalinRenderer.renderBasedOnExtension(filePath, model, this))
     }
 
+    //
+    // Gets a splat by its index.
+    // Ex: If the handler path is /users/*
+    // and a browser GETs /users/123,
+    // splat(0) will return "123"
+    //
+    fun splat(splatNr: Int): String? = splatList[splatNr]
+
+    /** Gets a list of all the [splat] values. */
+    fun splats(): List<String> = Collections.unmodifiableList(splatList)
 }
diff --git a/javalin/src/main/java/io/javalin/http/PathMatcher.kt b/javalin/src/main/java/io/javalin/http/PathMatcher.kt
index d10052fb..09280241 100644
--- a/javalin/src/main/java/io/javalin/http/PathMatcher.kt
+++ b/javalin/src/main/java/io/javalin/http/PathMatcher.kt
@@ -13,6 +13,7 @@ data class HandlerEntry(val type: HandlerType, val path: String, val ignoreTrail
     private val pathParser = PathParser(path, ignoreTrailingSlashes)
     fun matches(requestUri: String) = pathParser.matches(requestUri)
     fun extractPathParams(requestUri: String) = pathParser.extractPathParams(requestUri)
+    fun extractSplats(requestUri: String) = pathParser.extractSplats(requestUri)
 }
 
 class PathMatcher {
diff --git a/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt b/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
index e1691b20..479f1421 100644
--- a/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
+++ b/javalin/src/main/java/io/javalin/http/util/ContextUtil.kt
@@ -26,6 +26,7 @@ object ContextUtil {
         if (handlerType != HandlerType.AFTER) {
             endpointHandlerPath = handlerEntry.path
         }
+        splatList = handlerEntry.extractSplats(requestUri)
     }
 
     // this header is semi-colon separated, like: "text/html; charset=UTF-8"
@@ -68,11 +69,13 @@ object ContextUtil {
             matchedPath: String = "*",
             pathParamMap: Map<String, String> = mapOf(),
             handlerType: HandlerType = HandlerType.INVALID,
-            appAttributes: Map<Class<*>, Any> = mapOf()
+            appAttributes: Map<Class<*>, Any> = mapOf(),
+            splatList: List<String> = listOf()
     ) = Context(request, response, appAttributes).apply {
         this.matchedPath = matchedPath
         this.pathParamMap = pathParamMap
         this.handlerType = handlerType
+        this.splatList = splatList
     }
 
     fun Context.isLocalhost() = try {


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestRouting.kt b/javalin/src/test/java/io/javalin/TestRouting.kt
index d451fd3b..0bfa7455 100644
--- a/javalin/src/test/java/io/javalin/TestRouting.kt
+++ b/javalin/src/test/java/io/javalin/TestRouting.kt
@@ -107,4 +107,26 @@ class TestRouting {
         assertThat(http.getBody("/123/test")).isEqualTo("BEFORE123")
     }
 
+    @Test
+    fun `extracting path-param and splat works`() = TestUtil.test { app, http ->
+        app.get("/path/:path-param/*") { ctx -> ctx.result("/" + ctx.pathParam("path-param") + "/" + ctx.splat(0)) }
+        assertThat(http.getBody("/path/P/S")).isEqualTo("/P/S")
+    }
+
+    @Test
+    fun `utf-8 encoded splat works`() = TestUtil.test { app, http ->
+        app.get("/:path-param/path/*") { ctx -> ctx.result(ctx.pathParam("path-param") + ctx.splat(0)!!) }
+        val responseBody = okHttp.getBody(http.origin + "/"
+                + URLEncoder.encode("java/kotlin", "UTF-8")
+                + "/path/"
+                + URLEncoder.encode("/java/kotlin", "UTF-8")
+        )
+        assertThat(responseBody).isEqualTo("java/kotlin/java/kotlin")
+    }
+
+    @Test
+    fun `getting splat-list works`() = TestUtil.test { app, http ->
+        app.get("/*/*/*") { ctx -> ctx.result(ctx.splats().toString()) }
+        assertThat(http.getBody("/1/2/3")).isEqualTo("[1, 2, 3]")
+    }
 }