RegexPathPattern.kt

package com.hexagonkt.http.patterns

import com.hexagonkt.core.text.filter
import com.hexagonkt.http.patterns.TemplatePathPattern.Companion.VARIABLE_PATTERN
import com.hexagonkt.http.patterns.TemplatePathPattern.Companion.patternToRegex

data class RegexPathPattern(val regex: Regex) : PathPattern {

    override val pattern: String = regex.pattern
    override val prefix: Boolean = !regex.pattern.endsWith("$")

    internal companion object {
        const val PARAMETER_PREFIX = "(?<"
        const val PARAMETER_SUFFIX = ">$VARIABLE_PATTERN?)"
        val PARAMETER_REGEX = Regex("""\(\?<\w+>""")
    }

    init {
        checkPathPatternPrefix(pattern, listOf("(.*?)", "$"))
    }

    val parameters: List<String> =
        PARAMETER_REGEX.findAll(pattern)
            .map { it.value.removePrefix("(?<").removeSuffix(">") }
            .toList()

    override fun addPrefix(prefix: String?): PathPattern =
        if (prefix == null) this
        else copy(regex = Regex(patternToRegex(prefix, true) + pattern))

    override fun matches(requestUrl: String): Boolean =
        if (prefix) regex.matchesAt(requestUrl, 0)
        else regex.matches(requestUrl)

    override fun extractParameters(requestUrl: String): Map<String, String> {
        val result = regex.matchEntire(requestUrl)
        require(result != null) { "URL '$requestUrl' does not match path" }

        val allValues = result.groupValues
            .drop(1)
            .mapIndexed { ii, v -> ii.toString() to v }
            .toMap()

        val resultGroups = result.groups as MatchNamedGroupCollection
        return if (parameters.isEmpty()) allValues
        else parameters.associateWith { resultGroups[it]?.value ?: "" } + allValues
    }

    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 pattern.filter(PARAMETER_PREFIX, PARAMETER_SUFFIX, parameters)
    }
}