HttpPredicate.kt

  1. package com.hexagonkt.http.handlers

  2. import com.hexagonkt.handlers.Context
  3. import com.hexagonkt.core.logging.Logger
  4. import com.hexagonkt.http.patterns.LiteralPathPattern
  5. import com.hexagonkt.http.model.HttpMethod
  6. import com.hexagonkt.http.model.HttpStatus
  7. import com.hexagonkt.http.patterns.PathPattern
  8. import com.hexagonkt.http.patterns.createPathPattern
  9. import com.hexagonkt.http.model.HttpCall
  10. import kotlin.reflect.KClass

  11. data class HttpPredicate(
  12.     val methods: Set<HttpMethod> = emptySet(),
  13.     val pathPattern: PathPattern = LiteralPathPattern(),
  14.     val exception: KClass<out Exception>? = null,
  15.     val status: HttpStatus? = null,
  16. ) : (Context<HttpCall>) -> Boolean {

  17.     private companion object {
  18.         val logger: Logger = Logger(HttpPredicate::class)
  19.     }

  20.     private fun PathPattern.isEmpty(): Boolean =
  21.         pattern.isEmpty()

  22.     val predicate: (Context<HttpCall>) -> Boolean =
  23.         if (methods.isEmpty()) log(::filterWithoutMethod)
  24.         else log(::filterWithMethod)

  25.     constructor(
  26.         methods: Set<HttpMethod> = emptySet(),
  27.         pattern: String = "",
  28.         exception: KClass<out Exception>? = null,
  29.         status: HttpStatus? = null,
  30.         prefix: Boolean = false,
  31.     ) :
  32.         this(methods, createPathPattern(pattern, prefix), exception, status)

  33.     override fun invoke(context: Context<HttpCall>): Boolean =
  34.         predicate(context)

  35.     fun clearMethods(): HttpPredicate =
  36.         copy(methods = emptySet())

  37.     private fun log(
  38.         predicate: (Context<HttpCall>) -> Boolean
  39.     ): (Context<HttpCall>) -> Boolean = {
  40.         val allowed = predicate(it)
  41.         logger.debug { "${describe()} -> ${if (allowed) "ALLOWED" else "DENIED"}" }
  42.         allowed
  43.     }

  44.     private fun filterMethod(context: Context<HttpCall>): Boolean =
  45.         context.event.request.method in methods

  46.     private fun filterPattern(context: Context<HttpCall>): Boolean =
  47.         if (pathPattern.isEmpty() && context.event.request.path == "/") true
  48.         else pathPattern.matches(context.event.request.path)

  49.     private fun filterException(context: Context<HttpCall>): Boolean {
  50.         val exceptionClass = context.exception?.javaClass ?: return false
  51.         return exception?.java?.isAssignableFrom(exceptionClass) ?: false
  52.     }

  53.     private fun filterStatus(context: Context<HttpCall>): Boolean =
  54.         status == context.event.response.status

  55.     private fun filterWithoutMethod(context: Context<HttpCall>): Boolean =
  56.         filterPattern(context)
  57.             && (exception == null || filterException(context))
  58.             && (status == null || filterStatus(context))

  59.     private fun filterWithMethod(context: Context<HttpCall>): Boolean =
  60.         filterMethod(context) && filterWithoutMethod(context)

  61.     fun addPrefix(prefix: String): HttpPredicate =
  62.         copy(pathPattern = pathPattern.addPrefix(prefix))

  63.     fun describe(): String =
  64.         methods
  65.             .map { it.name }
  66.             .ifEmpty { listOf("ANY") }
  67.             .joinToString(
  68.                 separator = ", ",
  69.                 postfix = pathPattern.describe().prependIndent(" "),
  70.                 transform = { it }
  71.             )
  72. }