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

org.http4k.contract.ContractRoutingHttpHandler.kt Maven / Gradle / Ivy

There is a newer version: 5.31.0.0
Show newest version
package org.http4k.contract

import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.Method.GET
import org.http4k.core.NoOp
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.UriTemplate
import org.http4k.core.then
import org.http4k.filter.ServerFilters.CatchLensFailure
import org.http4k.routing.RoutedRequest
import org.http4k.routing.RoutedResponse
import org.http4k.routing.Router
import org.http4k.routing.RoutingHttpHandler

data class ContractRoutingHttpHandler(private val renderer: ContractRenderer,
                                      private val security: Security,
                                      private val descriptionPath: String,
                                      private val routes: List = emptyList(),
                                      private val rootAsString: String = "",
                                      private val preSecurityFilter: Filter = Filter.NoOp,
                                      private val postSecurityFilter: Filter = Filter.NoOp
) : RoutingHttpHandler {
    private val contractRoot = PathSegments(rootAsString)

    fun withPostSecurityFilter(new: Filter) = copy(postSecurityFilter = postSecurityFilter.then(new))

    /**
     * NOTE: By default, filters for Contracts are applied *before* the Security filter. Use withPostSecurityFilter()
     * to achieve population of filters after security.
     */
    override fun withFilter(new: Filter) = copy(preSecurityFilter = new.then(preSecurityFilter))

    override fun withBasePath(new: String) = copy(rootAsString = new + rootAsString)

    private val standardFilters = preSecurityFilter.then(security.filter).then(postSecurityFilter)

    private val handler: HttpHandler = {
        match(it)?.invoke(it) ?: standardFilters.then { Response(NOT_FOUND.description("Route not found")) }(it)
    }

    override fun invoke(request: Request): Response = handler(request)

    private val descriptionRoute = ContractRouteSpec0({ PathSegments("$it$descriptionPath") }, RouteMeta()) bindContract GET to { renderer.description(contractRoot, security, routes) }

    private val routers: List> = routes
        .map { CatchLensFailure.then(identify(it)).then(standardFilters) to it.toRouter(contractRoot) }
        .plus(identify(descriptionRoute).then(preSecurityFilter).then(postSecurityFilter) to descriptionRoute.toRouter(contractRoot))

    private val noMatch: HttpHandler? = null

    override fun toString(): String = contractRoot.toString() + "\n" + routes.joinToString("\n") { it.toString() }

    override fun match(request: Request): HttpHandler? =
        if (request.isIn(contractRoot)) {
            routers.fold(noMatch) { memo, (routeFilter, router) ->
                memo ?: router.match(request)?.let { routeFilter.then(it) }
            }
        } else null

    private fun identify(route: ContractRoute): Filter =
        route.describeFor(contractRoot).let { routeIdentity ->
            Filter { next ->
                {
                    val xUriTemplate = UriTemplate.from(if (routeIdentity.isEmpty()) "/" else routeIdentity)
                    RoutedResponse(next(RoutedRequest(it, xUriTemplate)), xUriTemplate)
                }
            }
        }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy