Skip to content

Commit fcbbb73

Browse files
committed
Added string length validators, string schemamodel
1 parent 10f49e3 commit fcbbb73

8 files changed

Lines changed: 163 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation
4+
import com.papsign.ktor.openapigen.validation.ValidatorAnnotation
5+
6+
@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
7+
@SchemaProcessorAnnotation(LengthProcessor::class)
8+
@ValidatorAnnotation(LengthProcessor::class)
9+
annotation class Length(val min: Int, val max: Int, val message: String = "")
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.classLogger
4+
import com.papsign.ktor.openapigen.getKType
5+
import com.papsign.ktor.openapigen.model.schema.SchemaModel
6+
import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor
7+
import com.papsign.ktor.openapigen.validation.Validator
8+
import com.papsign.ktor.openapigen.validation.ValidatorBuilder
9+
import java.lang.Exception
10+
import kotlin.reflect.KType
11+
import kotlin.reflect.full.withNullability
12+
13+
abstract class LengthConstraintProcessor<A: Annotation>(): SchemaProcessor<A>, ValidatorBuilder<A> {
14+
15+
private val log = classLogger()
16+
17+
val types = listOf(getKType<String>().withNullability(true), getKType<String>().withNullability(false))
18+
19+
abstract fun process(model: SchemaModel.SchemaModelString, annotation: A): SchemaModel.SchemaModelString
20+
21+
abstract fun getConstraint(annotation: A): LengthConstraint
22+
23+
private class LengthConstraintValidator(private val constraint: LengthConstraint): Validator {
24+
override fun <T> validate(subject: T?): T? {
25+
if (subject is String?) {
26+
val value = subject?.length ?: 0
27+
if (constraint.min != null) {
28+
if (value < constraint.min) throw LengthConstraintViolation(value, constraint)
29+
}
30+
if (constraint.max != null) {
31+
if (value > constraint.max) throw LengthConstraintViolation(value, constraint)
32+
}
33+
} else {
34+
throw NotAStringViolation(subject)
35+
}
36+
return subject
37+
}
38+
}
39+
40+
override fun build(type: KType, annotation: A): Validator {
41+
return if (types.contains(type)) {
42+
LengthConstraintValidator(getConstraint(annotation))
43+
} else {
44+
error("${annotation::class} can only be used on types: $types")
45+
}
46+
}
47+
48+
override fun process(model: SchemaModel<*>, type: KType, annotation: A): SchemaModel<*> {
49+
return if (model is SchemaModel.SchemaModelString && types.contains(type)) {
50+
process(model, annotation)
51+
} else {
52+
log.warn("${annotation::class} can only be used on types: $types")
53+
model
54+
}
55+
}
56+
}
57+
58+
data class LengthConstraint(val min: Int? = null, val max: Int? = null, val errorMessage: String? = null)
59+
60+
open class ConstraintViolation(message: String, cause: Throwable? = null): Exception(message, cause)
61+
62+
class LengthConstraintViolation(val actual: Number?, val constraint: LengthConstraint): ConstraintViolation(constraint.errorMessage ?: "Constraint violation: the length of the string should be ${
63+
{
64+
val min = "${constraint.min}"
65+
val max = "${constraint.max}"
66+
when {
67+
constraint.min != null && constraint.max != null -> "between $min and $max"
68+
constraint.min != null -> "at least $min"
69+
constraint.max != null -> "at most $max"
70+
else -> "anything"
71+
}
72+
}()
73+
}, but it is $actual")
74+
75+
class NotAStringViolation(val value: Any?): ConstraintViolation("Constraint violation: $value is not a string")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.model.schema.SchemaModel
4+
5+
object LengthProcessor : LengthConstraintProcessor<Length>() {
6+
override fun process(model: SchemaModel.SchemaModelString, annotation: Length): SchemaModel.SchemaModelString {
7+
return model.apply {
8+
maxLength = annotation.max
9+
minLength = annotation.min
10+
}
11+
}
12+
13+
override fun getConstraint(annotation: Length): LengthConstraint {
14+
val errorMessage = if (annotation.message.isNotEmpty()) annotation.message else null
15+
return LengthConstraint(min = annotation.min, max = annotation.max, errorMessage = errorMessage)
16+
}
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation
4+
import com.papsign.ktor.openapigen.validation.ValidatorAnnotation
5+
6+
@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
7+
@SchemaProcessorAnnotation(LengthProcessor::class)
8+
@ValidatorAnnotation(LengthProcessor::class)
9+
annotation class MaxLength(val value: Int, val message: String = "")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.model.schema.SchemaModel
4+
5+
object MaxLengthProcessor : LengthConstraintProcessor<MaxLength>() {
6+
override fun process(model: SchemaModel.SchemaModelString, annotation: MaxLength): SchemaModel.SchemaModelString {
7+
return model.apply {
8+
maxLength = annotation.value
9+
}
10+
}
11+
12+
override fun getConstraint(annotation: MaxLength): LengthConstraint {
13+
val errorMessage = if (annotation.message.isNotEmpty()) annotation.message else null
14+
return LengthConstraint(max = annotation.value, errorMessage = errorMessage)
15+
}
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation
4+
import com.papsign.ktor.openapigen.validation.ValidatorAnnotation
5+
6+
@Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY)
7+
@SchemaProcessorAnnotation(LengthProcessor::class)
8+
@ValidatorAnnotation(LengthProcessor::class)
9+
annotation class MinLength(val value: Int, val message: String = "")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.papsign.ktor.openapigen.annotations.type.string.length
2+
3+
import com.papsign.ktor.openapigen.model.schema.SchemaModel
4+
5+
object MinLengthProcessor : LengthConstraintProcessor<MinLength>() {
6+
override fun process(model: SchemaModel.SchemaModelString, annotation: MinLength): SchemaModel.SchemaModelString {
7+
return model.apply {
8+
minLength = annotation.value
9+
}
10+
}
11+
12+
override fun getConstraint(annotation: MinLength): LengthConstraint {
13+
val errorMessage = if (annotation.message.isNotEmpty()) annotation.message else null
14+
return LengthConstraint(min = annotation.value, errorMessage = errorMessage)
15+
}
16+
}

src/main/kotlin/com/papsign/ktor/openapigen/model/schema/SchemaModel.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ sealed class SchemaModel<T>: DataModel {
6060
override var description: String? = null
6161
) : SchemaModel<T>()
6262

63+
data class SchemaModelString(
64+
var type: DataType? = null,
65+
var format: DataFormat? = null,
66+
var nullable: Boolean = false,
67+
var minLength: Int? = null,
68+
var maxLength: Int? = null,
69+
var pattern: String? = null,
70+
override var example: String? = null,
71+
override var examples: List<String>? = null,
72+
override var description: String? = null
73+
) : SchemaModel<String>()
74+
6375
data class SchemaModelRef<T>(override val `$ref`: String) : SchemaModel<T>(), RefModel<SchemaModel<T>> {
6476
override var example: T? = null
6577
override var examples: List<T>? = null

0 commit comments

Comments
 (0)