Skip to content
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

Make validations context aware #61

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/commonMain/kotlin/io/konform/validation/Validation.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package io.konform.validation

import io.konform.validation.internal.ValidationBuilderImpl
import kotlin.jvm.JvmName

interface Validation<T> {
interface Validation<C, T> {

companion object {
operator fun <T> invoke(init: ValidationBuilder<T>.() -> Unit): Validation<T> {
val builder = ValidationBuilderImpl<T>()
operator fun <C, T> invoke(init: ValidationBuilder<C, T>.() -> Unit): Validation<C, T> {
val builder = ValidationBuilderImpl<C, T>()
return builder.apply(init).build()
}

@JvmName("simpleInvoke")
operator fun <T> invoke(init: ValidationBuilder<Unit, T>.() -> Unit): Validation<Unit, T> {
val builder = ValidationBuilderImpl<Unit, T>()
return builder.apply(init).build()
}
}

fun validate(value: T): ValidationResult<T>
operator fun invoke(value: T) = validate(value)
fun validate(context: C, value: T): ValidationResult<T>
operator fun invoke(context: C, value: T) = validate(context, value)
}

operator fun <T> Validation<Unit, T>.invoke(value: T) = validate(Unit, value)

class Constraint<R> internal constructor(val hint: String, val templateValues: List<String>, val test: (R) -> Boolean)
class Constraint<C, R> internal constructor(val hint: String, val templateValues: List<String>, val test: (C, R) -> Boolean)
57 changes: 29 additions & 28 deletions src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,59 @@ import kotlin.reflect.KProperty1
private annotation class ValidationScope

@ValidationScope
abstract class ValidationBuilder<T> {
abstract fun build(): Validation<T>
abstract fun addConstraint(errorMessage: String, vararg templateValues: String, test: (T) -> Boolean): Constraint<T>
abstract infix fun Constraint<T>.hint(hint: String): Constraint<T>
abstract operator fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<R>.() -> Unit)
internal abstract fun <R> onEachIterable(prop: KProperty1<T, Iterable<R>>, init: ValidationBuilder<R>.() -> Unit)
abstract class ValidationBuilder<C, T> {
abstract fun build(): Validation<C, T>
abstract fun addConstraint(errorMessage: String, vararg templateValues: String, test: C.(T) -> Boolean): Constraint<C, T>
abstract infix fun Constraint<C, T>.hint(hint: String): Constraint<C, T>
abstract operator fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<C, R>.() -> Unit)
internal abstract fun <R> onEachIterable(prop: KProperty1<T, Iterable<R>>, init: ValidationBuilder<C, R>.() -> Unit)
@JvmName("onEachIterable")
infix fun <R> KProperty1<T, Iterable<R>>.onEach(init: ValidationBuilder<R>.() -> Unit) = onEachIterable(this, init)
internal abstract fun <R> onEachArray(prop: KProperty1<T, Array<R>>, init: ValidationBuilder<R>.() -> Unit)
infix fun <R> KProperty1<T, Iterable<R>>.onEach(init: ValidationBuilder<C, R>.() -> Unit) = onEachIterable(this, init)
internal abstract fun <R> onEachArray(prop: KProperty1<T, Array<R>>, init: ValidationBuilder<C, R>.() -> Unit)
@JvmName("onEachArray")
infix fun <R> KProperty1<T, Array<R>>.onEach(init: ValidationBuilder<R>.() -> Unit) = onEachArray(this, init)
internal abstract fun <K, V> onEachMap(prop: KProperty1<T, Map<K, V>>, init: ValidationBuilder<Map.Entry<K, V>>.() -> Unit)
infix fun <R> KProperty1<T, Array<R>>.onEach(init: ValidationBuilder<C, R>.() -> Unit) = onEachArray(this, init)
internal abstract fun <K, V> onEachMap(prop: KProperty1<T, Map<K, V>>, init: ValidationBuilder<C, Map.Entry<K, V>>.() -> Unit)
@JvmName("onEachMap")
infix fun <K, V> KProperty1<T, Map<K, V>>.onEach(init: ValidationBuilder<Map.Entry<K, V>>.() -> Unit) = onEachMap(this, init)
abstract infix fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<R>.() -> Unit)
abstract infix fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<R>.() -> Unit)
abstract fun run(validation: Validation<T>)
abstract val <R> KProperty1<T, R>.has: ValidationBuilder<R>
infix fun <K, V> KProperty1<T, Map<K, V>>.onEach(init: ValidationBuilder<C, Map.Entry<K, V>>.() -> Unit) = onEachMap(this, init)
abstract infix fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<C, R>.() -> Unit)
abstract infix fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<C, R>.() -> Unit)
abstract fun run(validation: Validation<C, T>)
abstract fun <S> run(validation: Validation<S, T>, map: (C) -> S)
abstract val <R> KProperty1<T, R>.has: ValidationBuilder<C, R>
}

fun <T : Any> ValidationBuilder<T?>.ifPresent(init: ValidationBuilder<T>.() -> Unit) {
val builder = ValidationBuilderImpl<T>()
fun <C, T : Any> ValidationBuilder<C, T?>.ifPresent(init: ValidationBuilder<C, T>.() -> Unit) {
val builder = ValidationBuilderImpl<C, T>()
init(builder)
run(OptionalValidation(builder.build()))
}

fun <T : Any> ValidationBuilder<T?>.required(init: ValidationBuilder<T>.() -> Unit) {
val builder = ValidationBuilderImpl<T>()
fun <C, T : Any> ValidationBuilder<C, T?>.required(init: ValidationBuilder<C, T>.() -> Unit) {
val builder = ValidationBuilderImpl<C, T>()
init(builder)
run(RequiredValidation(builder.build()))
}

@JvmName("onEachIterable")
fun <S, T : Iterable<S>> ValidationBuilder<T>.onEach(init: ValidationBuilder<S>.() -> Unit) {
val builder = ValidationBuilderImpl<S>()
fun <C, S, T : Iterable<S>> ValidationBuilder<C, T>.onEach(init: ValidationBuilder<C, S>.() -> Unit) {
val builder = ValidationBuilderImpl<C, S>()
init(builder)
@Suppress("UNCHECKED_CAST")
run(IterableValidation(builder.build()) as Validation<T>)
run(IterableValidation(builder.build()) as Validation<C, T>)
}

@JvmName("onEachArray")
fun <T> ValidationBuilder<Array<T>>.onEach(init: ValidationBuilder<T>.() -> Unit) {
val builder = ValidationBuilderImpl<T>()
fun <C, T> ValidationBuilder<C, Array<T>>.onEach(init: ValidationBuilder<C, T>.() -> Unit) {
val builder = ValidationBuilderImpl<C, T>()
init(builder)
@Suppress("UNCHECKED_CAST")
run(ArrayValidation(builder.build()) as Validation<Array<T>>)
run(ArrayValidation(builder.build()) as Validation<C, Array<T>>)
}

@JvmName("onEachMap")
fun <K, V, T : Map<K, V>> ValidationBuilder<T>.onEach(init: ValidationBuilder<Map.Entry<K, V>>.() -> Unit) {
val builder = ValidationBuilderImpl<Map.Entry<K, V>>()
fun <C, K, V, T : Map<K, V>> ValidationBuilder<C, T>.onEach(init: ValidationBuilder<C, Map.Entry<K, V>>.() -> Unit) {
val builder = ValidationBuilderImpl<C, Map.Entry<K, V>>()
init(builder)
@Suppress("UNCHECKED_CAST")
run(MapValidation(builder.build()) as Validation<T>)
run(MapValidation(builder.build()) as Validation<C, T>)
}
92 changes: 47 additions & 45 deletions src/commonMain/kotlin/io/konform/validation/internal/Builder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ import io.konform.validation.internal.ValidationBuilderImpl.Companion.PropModifi
import kotlin.collections.Map.Entry
import kotlin.reflect.KProperty1

internal class ValidationBuilderImpl<T> : ValidationBuilder<T>() {
internal class ValidationBuilderImpl<C, T> : ValidationBuilder<C, T>() {
companion object {
private enum class PropModifier {
NonNull, Optional, OptionalRequired
}

private abstract class PropKey<T> {
abstract fun build(builder: ValidationBuilderImpl<*>): Validation<T>
private abstract class PropKey<C, T> {
abstract fun build(builder: ValidationBuilderImpl<C, *>): Validation<C, T>
}

private data class SingleValuePropKey<T, R>(
private data class SingleValuePropKey<C, T, R>(
val property: KProperty1<T, R>,
val modifier: PropModifier
) : PropKey<T>() {
override fun build(builder: ValidationBuilderImpl<*>): Validation<T> {
) : PropKey<C, T>() {
override fun build(builder: ValidationBuilderImpl<C, *>): Validation<C, T> {
@Suppress("UNCHECKED_CAST")
val validations = (builder as ValidationBuilderImpl<R>).build()
val validations = (builder as ValidationBuilderImpl<C, R>).build()
return when (modifier) {
NonNull -> NonNullPropertyValidation(property, validations)
Optional -> OptionalPropertyValidation(property, validations)
Expand All @@ -34,13 +34,13 @@ internal class ValidationBuilderImpl<T> : ValidationBuilder<T>() {
}
}

private data class IterablePropKey<T, R>(
private data class IterablePropKey<C, T, R>(
val property: KProperty1<T, Iterable<R>>,
val modifier: PropModifier
) : PropKey<T>() {
override fun build(builder: ValidationBuilderImpl<*>): Validation<T> {
) : PropKey<C, T>() {
override fun build(builder: ValidationBuilderImpl<C, *>): Validation<C, T> {
@Suppress("UNCHECKED_CAST")
val validations = (builder as ValidationBuilderImpl<R>).build()
val validations = (builder as ValidationBuilderImpl<C, R>).build()
@Suppress("UNCHECKED_CAST")
return when (modifier) {
NonNull -> NonNullPropertyValidation(property, IterableValidation(validations))
Expand All @@ -50,13 +50,13 @@ internal class ValidationBuilderImpl<T> : ValidationBuilder<T>() {
}
}

private data class ArrayPropKey<T, R>(
private data class ArrayPropKey<C, T, R>(
val property: KProperty1<T, Array<R>>,
val modifier: PropModifier
) : PropKey<T>() {
override fun build(builder: ValidationBuilderImpl<*>): Validation<T> {
) : PropKey<C, T>() {
override fun build(builder: ValidationBuilderImpl<C, *>): Validation<C, T> {
@Suppress("UNCHECKED_CAST")
val validations = (builder as ValidationBuilderImpl<R>).build()
val validations = (builder as ValidationBuilderImpl<C, R>).build()
@Suppress("UNCHECKED_CAST")
return when (modifier) {
NonNull -> NonNullPropertyValidation(property, ArrayValidation(validations))
Expand All @@ -66,84 +66,86 @@ internal class ValidationBuilderImpl<T> : ValidationBuilder<T>() {
}
}

private data class MapPropKey<T, K, V>(
private data class MapPropKey<C, T, K, V>(
val property: KProperty1<T, Map<K, V>>,
val modifier: PropModifier
) : PropKey<T>() {
override fun build(builder: ValidationBuilderImpl<*>): Validation<T> {
) : PropKey<C, T>() {
override fun build(builder: ValidationBuilderImpl<C, *>): Validation<C, T> {
@Suppress("UNCHECKED_CAST")
val validations = (builder as ValidationBuilderImpl<Map.Entry<K, V>>).build()
val validations = (builder as ValidationBuilderImpl<C, Entry<K, V>>).build()
return when (modifier) {
NonNull -> NonNullPropertyValidation(property, MapValidation(validations))
Optional -> OptionalPropertyValidation(property, MapValidation(validations))
OptionalRequired -> RequiredPropertyValidation(property, MapValidation(validations))
}
}
}


}

private val constraints = mutableListOf<Constraint<T>>()
private val subValidations = mutableMapOf<PropKey<T>, ValidationBuilderImpl<*>>()
private val prebuiltValidations = mutableListOf<Validation<T>>()
private val constraints = mutableListOf<Constraint<C, T>>()
private val subValidations = mutableMapOf<PropKey<C, T>, ValidationBuilderImpl<C, *>>()
private val prebuiltValidations = mutableListOf<Validation<C, T>>()

override fun Constraint<T>.hint(hint: String): Constraint<T> =
override fun Constraint<C, T>.hint(hint: String): Constraint<C, T> =
Constraint(hint, this.templateValues, this.test).also { constraints.remove(this); constraints.add(it) }

override fun addConstraint(errorMessage: String, vararg templateValues: String, test: (T) -> Boolean): Constraint<T> {
override fun addConstraint(errorMessage: String, vararg templateValues: String, test: (C, T) -> Boolean): Constraint<C, T> {
return Constraint(errorMessage, templateValues.toList(), test).also { constraints.add(it) }
}

private fun <R> KProperty1<T, R?>.getOrCreateBuilder(modifier: PropModifier): ValidationBuilder<R> {
val key = SingleValuePropKey(this, modifier)
private fun <R> KProperty1<T, R?>.getOrCreateBuilder(modifier: PropModifier): ValidationBuilder<C, R> {
val key = SingleValuePropKey<C, T, R?>(this, modifier)
@Suppress("UNCHECKED_CAST")
return (subValidations.getOrPut(key, { ValidationBuilderImpl<R>() }) as ValidationBuilder<R>)
return (subValidations.getOrPut(key, { ValidationBuilderImpl<C, R>() }) as ValidationBuilder<C, R>)
}

private fun <R> KProperty1<T, Iterable<R>>.getOrCreateIterablePropertyBuilder(modifier: PropModifier): ValidationBuilder<R> {
val key = IterablePropKey(this, modifier)
private fun <R> KProperty1<T, Iterable<R>>.getOrCreateIterablePropertyBuilder(modifier: PropModifier): ValidationBuilder<C, R> {
val key = IterablePropKey<C, T, R>(this, modifier)
@Suppress("UNCHECKED_CAST")
return (subValidations.getOrPut(key, { ValidationBuilderImpl<R>() }) as ValidationBuilder<R>)
return (subValidations.getOrPut(key, { ValidationBuilderImpl<C, R>() }) as ValidationBuilder<C, R>)
}

private fun <R> PropKey<T>.getOrCreateBuilder(): ValidationBuilder<R> {
private fun <R> PropKey<C, T>.getOrCreateBuilder(): ValidationBuilder<C, R> {
@Suppress("UNCHECKED_CAST")
return (subValidations.getOrPut(this, { ValidationBuilderImpl<R>() }) as ValidationBuilder<R>)
return (subValidations.getOrPut(this, { ValidationBuilderImpl<C, R>() }) as ValidationBuilder<C, R>)
}

override fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<R>.() -> Unit) {
override fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<C, R>.() -> Unit) {
getOrCreateBuilder(NonNull).also(init)
}

override fun <R> onEachIterable(prop: KProperty1<T, Iterable<R>>, init: ValidationBuilder<R>.() -> Unit) {
override fun <R> onEachIterable(prop: KProperty1<T, Iterable<R>>, init: ValidationBuilder<C, R>.() -> Unit) {
prop.getOrCreateIterablePropertyBuilder(NonNull).also(init)
}

override fun <R> onEachArray(prop: KProperty1<T, Array<R>>, init: ValidationBuilder<R>.() -> Unit) {
ArrayPropKey(prop, NonNull).getOrCreateBuilder<R>().also(init)
override fun <R> onEachArray(prop: KProperty1<T, Array<R>>, init: ValidationBuilder<C, R>.() -> Unit) {
ArrayPropKey<C, T, R>(prop, NonNull).getOrCreateBuilder<R>().also(init)
}

override fun <K, V> onEachMap(prop: KProperty1<T, Map<K, V>>, init: ValidationBuilder<Entry<K, V>>.() -> Unit) {
MapPropKey(prop, NonNull).getOrCreateBuilder<Map.Entry<K, V>>().also(init)
override fun <K, V> onEachMap(prop: KProperty1<T, Map<K, V>>, init: ValidationBuilder<C, Entry<K, V>>.() -> Unit) {
MapPropKey<C, T, K, V>(prop, NonNull).getOrCreateBuilder<Entry<K, V>>().also(init)
}

override fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<R>.() -> Unit) {
override fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<C, R>.() -> Unit) {
getOrCreateBuilder(Optional).also(init)
}

override fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<R>.() -> Unit) {
override fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<C, R>.() -> Unit) {
getOrCreateBuilder(OptionalRequired).also(init)
}

override val <R> KProperty1<T, R>.has: ValidationBuilder<R>
override val <R> KProperty1<T, R>.has: ValidationBuilder<C, R>
get() = getOrCreateBuilder(NonNull)

override fun run(validation: Validation<T>) {
override fun run(validation: Validation<C, T>) {
prebuiltValidations.add(validation)
}

override fun build(): Validation<T> {
override fun <S> run(validation: Validation<S, T>, map: (C) -> S) {
prebuiltValidations.add(MappedValidation(validation, map))
}

override fun build(): Validation<C, T> {
val nestedValidations = subValidations.map { (key, builder) ->
key.build(builder)
}
Expand Down
Loading