All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.javalin.router.matcher.ParserState.kt Maven / Gradle / Ivy

There is a newer version: 6.3.0
Show newest version
package io.javalin.router.matcher

import io.javalin.router.matcher.ParserState.INSIDE_SLASH_ACCEPTING_BRACKETS
import io.javalin.router.matcher.ParserState.INSIDE_SLASH_IGNORING_BRACKETS
import io.javalin.router.matcher.ParserState.NORMAL
import io.javalin.router.matcher.PathSegment.MultipleSegments
import io.javalin.router.matcher.PathSegment.Normal
import io.javalin.router.matcher.PathSegment.Wildcard

import io.javalin.util.javalinLazy
import kotlin.LazyThreadSafetyMode.NONE

private enum class ParserState {
    NORMAL,
    INSIDE_SLASH_IGNORING_BRACKETS,
    INSIDE_SLASH_ACCEPTING_BRACKETS
}

private val allDelimiters = setOf('{', '}', '<', '>')
private val adjacentViolations = listOf("*{", "*<", "}*", ">*")

internal fun convertSegment(segment: String, rawPath: String): PathSegment {
    val bracketsCount by javalinLazy { segment.count { it in allDelimiters } }
    val wildcardCount by javalinLazy { segment.count { it == '*' } }
    return when {
        bracketsCount % 2 != 0 -> throw MissingBracketsException(segment, rawPath)
        adjacentViolations.any { it in segment } -> throw WildcardBracketAdjacentException(segment, rawPath)
        segment == "*" -> Wildcard // a wildcard segment
        bracketsCount == 0 && wildcardCount == 0 -> createNormal(segment) // a normal segment, no params or wildcards
        bracketsCount == 2 && segment.isEnclosedBy('{', '}') -> createSlashIgnoringParam(segment.stripEnclosing('{', '}')) // simple path param (no slashes)
        bracketsCount == 2 && segment.isEnclosedBy('<', '>') -> createSlashAcceptingParam(segment.stripEnclosing('<', '>')) // simple path param (slashes)
        else -> parseAsPathSegment(segment, rawPath) // complicated path segment, need to parse
    }
}

private fun parseAsPathSegment(segment: String, rawPath: String): PathSegment {
    var state: ParserState = NORMAL
    val pathNameAccumulator = mutableListOf()
    fun mapSingleChar(char: Char): PathSegment? = when (state) {
        NORMAL -> when (char) {
            '*' -> Wildcard
            '{' -> {
                state = INSIDE_SLASH_IGNORING_BRACKETS
                null
            }

            '<' -> {
                state = INSIDE_SLASH_ACCEPTING_BRACKETS
                null
            }

            '}', '>' -> throw MissingBracketsException(
                segment,
                rawPath
            ) // cannot start with a closing delimiter
            else -> createNormal(char.toString()) // the single characters will be merged later
        }

        INSIDE_SLASH_IGNORING_BRACKETS -> when (char) {
            '}' -> {
                state = NORMAL
                val name = pathNameAccumulator.joinToString(separator = "")
                pathNameAccumulator.clear()
                createSlashIgnoringParam(name)
            }

            '{', '<', '>' -> throw MissingBracketsException(
                segment,
                rawPath
            ) // cannot start another variable inside a variable
            // wildcard is also okay inside a variable name
            else -> {
                pathNameAccumulator += char
                null
            }
        }

        INSIDE_SLASH_ACCEPTING_BRACKETS -> when (char) {
            '>' -> {
                state = NORMAL
                val name = pathNameAccumulator.joinToString(separator = "")
                pathNameAccumulator.clear()
                createSlashAcceptingParam(name)
            }

            '{', '}', '<' -> throw MissingBracketsException(
                segment,
                rawPath
            ) // cannot start another variable inside a variable
            // wildcard is also okay inside a variable name
            else -> {
                pathNameAccumulator += char
                null
            }
        }
    }

    return segment.map(::mapSingleChar)
        .filterNotNull()
        .fold(MultipleSegments(emptyList())) { acc, pathSegment ->
            val lastAddition = acc.innerSegments.lastOrNull()
            when {
                lastAddition == null -> MultipleSegments(listOf(pathSegment))
                lastAddition is Wildcard && pathSegment is Wildcard -> acc
                lastAddition is Normal && pathSegment is Normal -> MultipleSegments(
                    acc.innerSegments.dropLast(1) + createNormal(lastAddition.content + pathSegment.content)
                )

                else -> MultipleSegments(acc.innerSegments + pathSegment)
            }
        }
}

private fun String.isEnclosedBy(prefix: Char, suffix: Char) = this.startsWith(prefix) && this.endsWith(suffix)
private fun String.stripEnclosing(prefix: Char, suffix: Char) = this.removePrefix(prefix.toString()).removeSuffix(suffix.toString())




© 2015 - 2025 Weber Informatics LLC | Privacy Policy