TemplatePathPattern.kt
- package com.hexagonkt.http.patterns
- /**
- * A path definition. It parses path patterns and extract values for parameters.
- *
- * No splat support (you can use named parameters though).
- *
- * Delimiter is {var} to conform with [RFC 6570](https://tools.ietf.org/html/rfc6570).
- *
- * It supports the {var:regex} format to match only parameters with a specific pattern.
- */
- data class TemplatePathPattern(
- override val pattern: String,
- override val prefix: Boolean = false
- ) : PathPattern by RegexPathPattern(Regex(patternToRegex(pattern, prefix))) {
- internal companion object {
- private const val PARAMETER_PREFIX = "{"
- private const val PARAMETER_SUFFIX = "}"
- internal const val WILDCARD = "*"
- private const val PARAMETER = "\\$PARAMETER_PREFIX(\\w+)(:.+?)?$PARAMETER_SUFFIX"
- private val REGEX_CHARACTERS = listOf('(', ')', '|', '?', '+', '[', ']')
- const val VARIABLE_PATTERN = "[^/]+"
- val WILDCARD_REGEX = Regex("\\$WILDCARD")
- val PARAMETER_REGEX = Regex(PARAMETER)
- val PLACEHOLDER_REGEX = Regex("\\$WILDCARD|$PARAMETER")
- fun isTemplate(pattern: String): Boolean =
- REGEX_CHARACTERS.any { pattern.contains(it) } || PLACEHOLDER_REGEX in pattern
- fun patternToRegex(pattern: String, prefix: Boolean): String {
- return pattern
- .replace(WILDCARD, "(.*?)")
- .replaceParameters(parameters(pattern))
- .let { if (prefix) it else "$it$" }
- }
- private fun parameters(pattern: String): Map<String, String> =
- PARAMETER_REGEX.findAll(pattern)
- .map {
- val (_, k, re) = it.groupValues
- k to re.ifEmpty { VARIABLE_PATTERN }
- }
- .toMap()
- private fun String.replaceParameters(parameters: Map<String, String>): String =
- parameters
- .entries
- .fold(this) { accumulator, (k, v) ->
- val re = if (v == VARIABLE_PATTERN) "" else v
- val search = "$PARAMETER_PREFIX$k$re$PARAMETER_SUFFIX"
- val replacement = "(?<$k>${v.removePrefix(":")}?)"
- accumulator.replace(search, replacement)
- }
- }
- val parameters: List<String> = parameters(pattern).keys.toList()
- init {
- checkPathPatternPrefix(pattern, listOf("*"))
- }
- override fun insertParameters(parameters: Map<String, Any>): String {
- val keys = parameters.keys
- val patternParameters = this.parameters
- require(keys.toSet() == patternParameters.toSet()) {
- "Parameters must match pattern's parameters($patternParameters). Provided: $keys"
- }
- return parameters.entries.fold(pattern) { accumulator, (k, v) ->
- val re = Regex("\\$PARAMETER_PREFIX$k(:.+?)?$PARAMETER_SUFFIX")
- accumulator.replace(re, v.toString())
- }
- }
- }