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

io.github.cdimascio.openapi.Validate.kt Maven / Gradle / Ivy

The newest version!
package io.github.cdimascio.openapi

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.http.HttpStatus
import org.springframework.web.reactive.function.BodyExtractors
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.awaitBodyOrNull
import reactor.core.publisher.Mono

operator fun Regex.contains(text: CharSequence): Boolean = this.matches(text)

/**
 * Represents an error when validating a request against the
 * Swagger 2 or OpenApi 3 specification
 */
data class ValidationError(val request: ServerRequest, val code: Int, val message: String)

/**
 * Handler for validation errors
 */
typealias ErrorHandler = (request: ServerRequest, status: HttpStatus, List) -> T

/**
 * Factory for ObjectMapper.
 */
typealias ObjectMapperFactory = () -> ObjectMapper

/**
 * Validates requests against a Swagger 2 or OpenAPI 3 specification.
 */
class Validate internal constructor(
    swaggerJsonPath: String,
    errorHandler: ErrorHandler,
    private val objectMapperFactory: ObjectMapperFactory) {

    /**
     * The validate instance
     */
    companion object Instance {
        private val defaultErrorHandler: ErrorHandler =
            { request, status, messages -> ValidationError(request, status.value(), messages[0]) }

        private val defaultObjectMapperFactory: ObjectMapperFactory = { jacksonObjectMapper() }

        /**
         * Configures the Validator with the Swagger 2 or OpenApi specification located at [openApiSwaggerPath]
         * The Swagger 2 or OpenApi 3 specification file may be represented as YAML or JSON.
         */
        fun configure(openApiSwaggerPath: String) = configure(openApiSwaggerPath, defaultErrorHandler)

        /**
         * Configures the Validator with the Swagger 2 or OpenApi specification located at [openApiSwaggerPath]
         * and a custom [errorHandler]. The specification file may be represented as YAML or JSON.
         */
        fun  configure(openApiSwaggerPath: String,
                          errorHandler: ErrorHandler) = configure(openApiSwaggerPath, defaultObjectMapperFactory, errorHandler)


        /**
         * Configures the Validator with the Swagger 2 or OpenApi specification located at [openApiSwaggerPath], using
         * custom [objectMapperFactory] and a custom [errorHandler]. The specification file may be represented as YAML or JSON.
         */
        fun  configure(openApiSwaggerPath: String,
                          objectMapperFactory: ObjectMapperFactory,
                          errorHandler: ErrorHandler) = Validate(openApiSwaggerPath, errorHandler, objectMapperFactory)
    }

    private val validator = Validator(swaggerJsonPath, errorHandler)

    /**
     * The [request] to validate
     */
    fun request(request: ServerRequest) = Request(request, objectMapperFactory)

    /**
     * Validates the [request]. If validation succeeds, the [handler] function is called to return a response
     */
    fun request(request: ServerRequest, handler: () -> Mono) = validator.validate(request) ?: handler()

    /**
     * Validates the [request]. If validation succeeds, the [handler] function is called to return a response.
     * It's a suspended alternative to a [request] method.
     */
    suspend fun requestAndAwait(request: ServerRequest, handler: suspend () -> ServerResponse): ServerResponse =
        validator.validateAndAwait(request) ?: handler()

    inner class Request(val request: ServerRequest, val objectMapperFactory: ObjectMapperFactory) {

        /**
         * Validates a request with body of type [bodyType]. If validation succeeds, the [handler]
         * is called to return a response
         */
        fun  withBody(bodyType: Class, handler: (T) -> Mono): Mono {
            return withBody(bodyType, readJsonValue(bodyType), handler)
        }

        /**
         * Validates a request with body of type [bodyType]. If validation succeeds, the [readValue] function
         * is used to transform the string body to [bodyType], and the [handler]
         * is called to return a response
         */
        fun  withBody(bodyType: Class,
                         readValue: (String) -> T,
                         handler: (T) -> Mono): Mono {
            return BodyValidator(request, bodyType, objectMapperFactory).validate(handler, readValue)
        }

        /**
         * Reified inline version of the [Request.withBody] function.
         * @param T type of the body.
         * @param handler handler function.
         * @return ServerResponse as a result of the call.
         */
        inline fun  withBody(noinline handler: (T) -> Mono): Mono =
            this.withBody(T::class.java, handler = handler)

        /**
         * Validates a request with body of type [bodyType] . If validation succeeds, the [handler]
         * is called to return a response.
         * It's a suspended alternative to a [withBody] method.
         */
        suspend fun  awaitBody(bodyType: Class,
                                  readValue: (String) -> T = readJsonValue(bodyType),
                                  handler: suspend (T) -> ServerResponse): ServerResponse {
            return BodyValidator(request, bodyType, objectMapperFactory).validateAndAwait(handler, readValue)
        }

        private fun  readJsonValue(bodyType: Class): (String) -> T = { json ->
            objectMapperFactory().readValue(json, bodyType)
        }

    }
    /**
     * Creates a new BodyValidator to validate a [request] of type [bodyType] using [objectMapperFactory].
     */
    inner class BodyValidator(val request: ServerRequest, val bodyType: Class, val objectMapperFactory: ObjectMapperFactory) {
        /**
         * Validates the body and calls [handler] if the validation succeeds
         */
        fun validate(handler: (T) -> Mono, readValue: (String) -> T): Mono {
            val json = request.body(BodyExtractors.toMono(String::class.java)).switchIfEmpty(Mono.just(""))
            return json.flatMap { validator.validate(request, it) ?: handler(readValue(it)) }
        }

        /**
         * Validates the body and calls [handler] if the validation succeeds.
         * It's a suspended alternative to a [validate] method.
         */
        suspend fun validateAndAwait(handler: suspend (T) -> ServerResponse, readValue: (String) -> T): ServerResponse {
            val json = request.awaitBodyOrNull() ?: ""
            return json.let { validator.validateAndAwait(request, it) ?: handler(readValue(it)) }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy