I'm still having a hard time understanding how thi...
# ktor
r
I'm still having a hard time understanding how this plugin is supposed to work. It seems you can't install it multiple times on Routing, but you also can't install plugins on routes when using Resources, and certainly don't want to have a giant file validating all of your application's endpoints's payloads. How do you use RequestValidation?
For now this will be my solution: not installing the plugin at all.
Copy code
suspend inline fun <reified T : Any> T.validated(noinline block: suspend T.() -> ValidationResult): T {
    val result = block()
    if (result is ValidationResult.Invalid) {
        throw RequestValidationException(this, result.reasons)
    }
    return this
}
Copy code
post<AuthResource> { resource ->

        val payload = call.receive<AuthResource.Payload>().validated {
            val reasons = mutableListOf<String>()
            if (username.isBlank()) {
                reasons += "username should not be blank"
            }
            if (password.isBlank()) {
                reasons += "password should not be blank"
            }
            if (appId != null && !AppId.isValid(appId)) {
                reasons += "appId is invalid"
            }
            if (reasons.isEmpty()) {
                ValidationResult.Valid
            } else {
                ValidationResult.Invalid(reasons)
            }
        }

        …

    }
Actually…
Copy code
class ValidationScope {

    private val _invalidationReasons = mutableListOf<String>()

    val invalidationReasons: List<String> get() = _invalidationReasons

    fun invalidate(reason: String) {
        _invalidationReasons += reason
    }

}

fun <T : Any> T.validated(
    block: context(ValidationScope) T.() -> Unit,
): T {
    val scope = ValidationScope()
    block(scope, this)
    if (scope.invalidationReasons.isNotEmpty()) {
        throw RequestValidationException(this, scope.invalidationReasons)
    }
    return this
}
Copy code
post<AuthResource> { resource ->

        val payload = call.receive<AuthResource.Payload>().validated {
            if (username.isBlank()) {
                invalidate("username should not be blank")
            }
            if (password.isBlank()) {
                invalidate("password should not be blank")
            }
            if (appId != null && !AppId.isValid(appId)) {
                invalidate("appId is invalid")
            }
        }

        …

    }
n
You very much can install plugins on Routes with resources; I’m doing it using:
Copy code
resource<MessageResource> {
    install(ContentNegotiation) {
        jackson(block = jacksonOptions)
    }
}
I bundle up a bunch of common plugins and call them as a generic extension function which then uses that to install them over
resource<T>
r
Thanks, I didn't find
resource<T>(…)
, I was lookin for
route<T>(…)
. I'm still trying to figure out why I would need the plugin, and how to also handle serialization issues, which the plugin does not handle
Like in my example, if the passed
username
is blank I have an exception somewhere, but if the field is missing from the body it's a different error somewhere else, which is harder to catch. In the past I used to have a
Weak
data class mimicking my payload class but with all fields nullable with default value
null
, and then have a function converting it to the actual payload, but I don't like it
a
So the problem is that the deserialization exceptions aren't available somehow in the
RequestValidation
plugin or that each exception contains only a message without specific information about the missing fields?
r
My issue is higher level: I just want a way to handle request body/header/etc validation. The RequestValidation plugin should be that, but it isn't because of how serialization is handled. I would like to handle a missing field in a json body the same way I handle an invalid value in a field. Now, I'm not sure what the prefered solution to this would be, but it's a problem when trying to create a well-built API
I think that somehow, somewhere, RequestValidation plugin should know that ContentNegociation is a thing and talk to it.