-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to create custom validators with the latest version? #56
Comments
Ok, now I've found some example code from the built-in validators/transformers, but I still don't know how could I write a custom validator that validates if the length of a string equals to a number. Annotation package com.papsign.ktor.openapigen.validation.string.lowercase
import com.papsign.ktor.openapigen.validation.ValidatorAnnotation
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
@ValidatorAnnotation(LowerCaseValidator::class)
annotation class LowerCase Validator package com.papsign.ktor.openapigen.validation.string.lowercase
import com.papsign.ktor.openapigen.getKType
import com.papsign.ktor.openapigen.validation.Validator
import com.papsign.ktor.openapigen.validation.util.SingleTypeValidator
object LowerCaseValidator : SingleTypeValidator<LowerCase>(getKType<String>(), { LowerCaseValidator }), Validator {
override fun <T> validate(subject: T?): T? {
@Suppress("UNCHECKED_CAST")
return (subject as String?)?.toLowerCase() as T?
}
} |
Yes it changed quite a bit to get rid of the registration process, now you can just provide the annotation and the annotation itself provides the handler. Just ignore the abstract part, implement the SchemaProcessor and Validator builder. Then you create an annotation like this: @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
@SchemaProcessorAnnotation(MinProcessor::class)
@ValidatorAnnotation(MinProcessor::class)
annotation class Min(val value: Long) I will have to write a wiki page for it indeed. |
Thanks for your help! I've managed to implement the custom validator I wanted. I'm sharing my code, maybe it will be helpful for others. I've wrote a LengthConstraintProcessor class based on the NumberConstraintProcessor class abstract class LengthConstraintProcessor<A: Annotation>(): SchemaProcessor<A>, ValidatorBuilder<A> {
val types = listOf(getKType<String>().withNullability(true), getKType<String>().withNullability(false))
abstract fun process(model: SchemaModel<*>, annotation: A): SchemaModel<*>
abstract fun getConstraint(annotation: A): LengthConstraint
private class LengthConstraintValidator(private val constraint: LengthConstraint): Validator {
override fun <T> validate(subject: T?): T? {
if (subject is String?) {
val value = subject?.length ?: 0
if (constraint.min != null) {
if (value < constraint.min) throw LengthConstraintViolation(value, constraint)
}
if (constraint.max != null) {
if (value > constraint.max) throw LengthConstraintViolation(value, constraint)
}
} else {
throw NotAStringViolation(subject)
}
return subject
}
}
override fun build(type: KType, annotation: A): Validator {
return if (types.contains(type)) {
LengthConstraintValidator(getConstraint(annotation))
} else {
error("${annotation::class} can only be used on types: $types")
}
}
override fun process(model: SchemaModel<*>, type: KType, annotation: A): SchemaModel<*> {
return if (types.contains(type)) {
process(model, annotation)
} else {
model
}
}
}
data class LengthConstraint(val min: Int? = null, val max: Int? = null, val errorMessage: String? = null)
open class ConstraintViolation(message: String, cause: Throwable? = null): Exception(message, cause)
class LengthConstraintViolation(val actual: Number?, val constraint: LengthConstraint): ConstraintViolation(constraint.errorMessage ?: "Constraint violation: the length of the string should be ${
{
val min = "${constraint.min}"
val max = "${constraint.max}"
when {
constraint.min != null && constraint.max != null -> "between $min and $max"
constraint.min != null -> "at least $min"
constraint.max != null -> "at most $max"
else -> "anything"
}
}()
}, but it is $actual")
class NotAStringViolation(val value: Any?): ConstraintViolation("Constraint violation: $value is not a string") Annotation @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
@SchemaProcessorAnnotation(ExactLengthProcessor::class)
@ValidatorAnnotation(ExactLengthProcessor::class)
annotation class ExactLength(val value: Int, val message: String = "") Processor class object ExactLengthProcessor : LengthConstraintProcessor<ExactLength>() {
override fun process(model: SchemaModel<*>, annotation: ExactLength): SchemaModel<*> {
// There is no string schema and couldn't create one, because SchemaModel is a sealed class
return model
}
override fun getConstraint(annotation: ExactLength): LengthConstraint {
val errorMessage = if (annotation.message.isNotEmpty()) annotation.message else null
return LengthConstraint(min = annotation.value, max = annotation.value, errorMessage = errorMessage)
}
} |
Nice :) |
Ok, I've created a pull request. I couldn't test it, yet, because I have problems with adding the modified gradle project as dependency to a project correctly. Edit: I have managed to import the modified project somehow. The validation works, but the new constraints are missing from the schema. I think the Schema builders can't handle the new SchemaModelString class correctly. |
I've created custom validators with the 0.1-beta.2 version and I wanted to update to the latest version (0.2-beta.5). The validation system changed a lot and I can't figure it out how to write a custom validator. I've only found example on custom validators for the old version.
The text was updated successfully, but these errors were encountered: