com.lightningkite.lightningserver.http.HttpEndpointMatcher.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of server-core Show documentation
Show all versions of server-core Show documentation
A set of tools to fill in/replace what Ktor is lacking in.
The newest version!
package com.lightningkite.lightningserver.http
import com.lightningkite.lightningserver.core.ServerPath
import io.ktor.http.*
class HttpEndpointMatcher(paths: Sequence) {
data class Node(
val path: Map,
val trailingSlash: Map,
val chainedWildcard: Map,
val thenConstant: Map,
val thenWildcard: Node?,
) {
override fun toString(): String {
return """Node(
path = $path,
trailingSlash = $trailingSlash,
chainedWildcard = $chainedWildcard,
thenConstant = ${thenConstant.keys.joinToString()},
thenWildcard = ${thenWildcard != null},
)""".trimIndent().replace("\n", "")
}
}
val root = run {
fun ServerPath.Segment.s(): String? = when (this) {
is ServerPath.Segment.Constant -> value
is ServerPath.Segment.Wildcard -> null
}
fun toNode(soFar: List): Node {
val future = paths.map { it.path }.filter {
it.segments.asSequence().zip(soFar.asSequence())
.all { it.first.s() == it.second } && it.segments.size > soFar.size
}
return Node(
path = paths.filter { it.path.segments.map { it.s() } == soFar && it.path.after == ServerPath.Afterwards.None }
.associateBy { it.method },
trailingSlash = paths.filter { it.path.segments.map { it.s() } == soFar && it.path.after == ServerPath.Afterwards.TrailingSlash }
.associateBy { it.method },
chainedWildcard = paths.filter { it.path.segments.map { it.s() } == soFar && it.path.after == ServerPath.Afterwards.ChainedWildcard }
.associateBy { it.method },
thenConstant = future
.mapNotNull { (it.segments[soFar.size] as? ServerPath.Segment.Constant)?.value }
.distinct()
.associateWith { toNode(soFar + it) },
thenWildcard = if (future.any { it.segments[soFar.size] is ServerPath.Segment.Wildcard })
toNode(soFar + null)
else null
)
}
toNode(listOf())
}
data class Match(
val endpoint: HttpEndpoint,
val parts: Map,
val wildcard: String?
)
fun match(string: String, method: HttpMethod): Match? =
match(string.split('/').filter { it.isNotEmpty() }, string.endsWith('/'), method)
fun match(pathParts: List, endingSlash: Boolean, method: HttpMethod): Match? {
if (pathParts.isEmpty())
return (root.path[method] ?: root.trailingSlash[method] ?: root.chainedWildcard[method])?.let {
Match(
it,
mapOf(),
if (it.path.after == ServerPath.Afterwards.ChainedWildcard) "" else null
)
}
// println("Navigating $pathParts with ending slash $endingSlash")
val wildcards = ArrayList()
var current = root
val soFar = arrayListOf()
var beyond = false
for (part in pathParts) {
soFar.add(current)
// println("Current is $current, looking for $part")
val c = current.thenConstant[part]
if (c != null) {
current = c
continue
}
val w = current.thenWildcard
if (w != null) {
current = w
wildcards.add(part)
continue
}
beyond = true
break
}
// println("Stopped at $current")
return if (beyond) {
// println("Searching for wildcard ending")
soFar.asReversed().asSequence().mapNotNull {
it.chainedWildcard[method]?.let {
Match(
endpoint = it,
parts = it.path.segments.filterIsInstance().zip(wildcards)
.associate { it.first.name to it.second.decodeURLPart() },
wildcard = pathParts.drop(it.path.segments.size)
.joinToString("/") + (if (endingSlash) "/" else "")
)
}
}.firstOrNull()
} else if (endingSlash) {
// println("Pulling trailingSlash")
current.trailingSlash[method]?.let {
Match(
endpoint = it,
parts = it.path.segments.filterIsInstance().zip(wildcards)
.associate { it.first.name to it.second.decodeURLPart() },
wildcard = null
)
}
} else {
// println("Pulling path")
current.path[method]?.let {
Match(
endpoint = it,
parts = it.path.segments.filterIsInstance().zip(wildcards)
.associate { it.first.name to it.second.decodeURLPart() },
wildcard = null
)
}
} ?: run {
// println("Searching for wildcard ending")
soFar.asReversed().asSequence().mapNotNull {
it.chainedWildcard[method]?.let {
Match(
endpoint = it,
parts = it.path.segments.filterIsInstance().zip(wildcards)
.associate { it.first.name to it.second.decodeURLPart() },
wildcard = pathParts.drop(it.path.segments.size)
.joinToString("/") + (if (endingSlash) "/" else "")
)
}
}.firstOrNull()
}
}
}