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

io.javalin.validation.BaseValidator.kt Maven / Gradle / Ivy

There is a newer version: 6.3.0
Show newest version
/*
 * Javalin - https://javalin.io
 * Copyright 2017 David Åse
 * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
 */

package io.javalin.validation

import io.javalin.json.JsonMapper
import io.javalin.util.JavalinLogger
import io.javalin.util.javalinLazy
import kotlin.LazyThreadSafetyMode.NONE

typealias Check = (T) -> Boolean

data class Rule(val fieldName: String, val check: Check, val error: ValidationError)
data class ValidationError(val message: String, val args: Map = mapOf(), var value: Any? = null)
class ValidationException(val errors: Map>>) : RuntimeException()

data class StringSource(
    val stringValue: String?,
    val clazz: Class,
    val jsonMapper: JsonMapper? = null
)

open class BaseValidator(val fieldName: String, protected var typedValue: T?, protected val stringSource: StringSource?) {
    internal val rules = mutableListOf>()

    constructor(stringValue: String?, clazz: Class, fieldName: String, jsonMapper: JsonMapper? = null) :
        this(fieldName, null, StringSource(stringValue, clazz, jsonMapper))

    private val errors by javalinLazy {
        if (stringSource != null) {
            if (this is BodyValidator) {
                try {
                    typedValue = stringSource.jsonMapper!!.fromJsonString(stringSource.stringValue!!, stringSource.clazz)
                } catch (e: Exception) {
                    JavalinLogger.info("Couldn't deserialize body to ${stringSource.clazz.simpleName}", e)
                    return@javalinLazy mapOf(REQUEST_BODY to listOf(ValidationError("DESERIALIZATION_FAILED", value = stringSource.stringValue)))
                }
            } else if (this is NullableValidator || this is Validator) {
                try {
                    typedValue = JavalinValidation.convertValue(stringSource.clazz, stringSource.stringValue)
                } catch (e: Exception) {
                    JavalinLogger.info("Parameter '$fieldName' with value '${stringSource.stringValue}' is not a valid ${stringSource.clazz.simpleName}")
                    return@javalinLazy mapOf(fieldName to listOf(ValidationError("TYPE_CONVERSION_FAILED", value = stringSource.stringValue)))
                }
                if (this !is NullableValidator && typedValue == null) { // only check typedValue - null might map to 0, which could be valid?
                    return@javalinLazy mapOf(fieldName to listOf(ValidationError("NULLCHECK_FAILED", value = stringSource.stringValue)))
                }
            }
        }

        /** after this point [typedValue] replaces [stringValue] */
        val errors = mutableMapOf>>()
        rules.filter { !it.check(typedValue) }.forEach { failedRule ->
            // if it's a BodyValidator, the same validator can have rules with different field names
            errors.computeIfAbsent(failedRule.fieldName) { mutableListOf() }
            errors[failedRule.fieldName]!!.add(failedRule.error.also { it.value = typedValue })
        }
        errors.mapValues { it.value.toList() }.toMap() // make immutable
    }

    protected fun addRule(fieldName: String, check: Check, error: String): BaseValidator {
        rules.add(Rule(fieldName, check, ValidationError(error)))
        return this
    }

    protected fun addRule(fieldName: String, check: Check, error: ValidationError): BaseValidator {
        rules.add(Rule(fieldName, check, error))
        return this
    }

    open fun get(): T? = getOrThrow { ValidationException(it) }

    open fun getOrThrow(exceptionFunction: (Map>>) -> Exception): T? = when {
        errors.isEmpty() -> typedValue
        else -> throw exceptionFunction(errors as Map>>)
    }

    fun errors(): Map>> = errors
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy