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

commonMain.dev.programadorthi.routing.statuspages.StatusPages.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0-alpha02
Show newest version
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.programadorthi.routing.statuspages

import dev.programadorthi.routing.core.application.ApplicationCall
import dev.programadorthi.routing.core.application.ApplicationPlugin
import dev.programadorthi.routing.core.application.createApplicationPlugin
import dev.programadorthi.routing.core.application.hooks.CallFailed
import dev.programadorthi.routing.core.logging.mdcProvider
import io.ktor.util.AttributeKey
import io.ktor.util.logging.KtorSimpleLogger
import io.ktor.util.reflect.instanceOf
import io.ktor.utils.io.KtorDsl
import kotlin.reflect.KClass

private val LOGGER = KtorSimpleLogger("kotlin-routing-status-pages")

/**
 * Specifies how the exception should be handled.
 */
public typealias HandlerFunction = suspend (call: ApplicationCall, cause: Throwable) -> Unit

/**
 * A plugin that handles exceptions and status codes. Useful to configure default error pages.
 */
public val StatusPages: ApplicationPlugin =
    createApplicationPlugin(
        "StatusPages",
        ::StatusPagesConfig,
    ) {
        val statusPageMarker = AttributeKey("StatusPagesTriggered")

        val exceptions = HashMap(pluginConfig.exceptions)
        val unhandled = pluginConfig.unhandled

        fun findHandlerByValue(cause: Throwable): HandlerFunction? {
            val keys = exceptions.keys.filter { cause.instanceOf(it) }
            if (keys.isEmpty()) return null

            if (keys.size == 1) {
                return exceptions[keys.single()]
            }

            val key = selectNearestParentClass(cause, keys)
            return exceptions[key]
        }

        on(CallFailed) { call, cause ->
            if (call.attributes.contains(statusPageMarker)) return@on

            LOGGER.trace("Call ${call.uri} failed with cause $cause")

            val handler = findHandlerByValue(cause)
            if (handler == null) {
                LOGGER.trace("No handler found for exception: $cause for call ${call.uri}")
                throw cause
            }

            call.attributes.put(statusPageMarker, Unit)
            call.application.mdcProvider.withMDCBlock(call) {
                LOGGER.trace("Executing $handler for exception $cause for call ${call.uri}")
                handler(call, cause)
            }
        }

        on(BeforeFallback) { call ->
            unhandled(call)
        }
    }

/**
 * A [StatusPages] plugin configuration.
 */
@KtorDsl
public class StatusPagesConfig {
    /**
     * Provides access to exception handlers of the exception class.
     */
    public val exceptions: MutableMap, HandlerFunction> = mutableMapOf()

    internal var unhandled: suspend (ApplicationCall) -> Unit = {}

    /**
     * Register an exception [handler] for the exception type [T] and its children.
     */
    public inline fun  exception(noinline handler: suspend (call: ApplicationCall, cause: T) -> Unit): Unit =
        exception(T::class, handler)

    /**
     * Register an exception [handler] for the exception class [klass] and its children.
     */
    public fun  exception(
        klass: KClass,
        handler: suspend (call: ApplicationCall, T) -> Unit,
    ) {
        @Suppress("UNCHECKED_CAST")
        val cast = handler as suspend (ApplicationCall, Throwable) -> Unit

        exceptions[klass] = cast
    }

    /**
     * Register a [handler] for the unhandled calls.
     */
    public fun unhandled(handler: suspend (ApplicationCall) -> Unit) {
        unhandled = handler
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy