-
Notifications
You must be signed in to change notification settings - Fork 41
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
Compose validation builders #63
Changes from 11 commits
4fe22f4
3965dcd
63d149d
ffc2a0b
a995c02
bbd2d61
a831af5
e665c6b
5701132
5a3dfe6
f6e4291
8e6bb8d
5d05d31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,31 @@ | ||
package io.konform.validation | ||
|
||
import io.konform.validation.internal.ValidationBuilderImpl | ||
import io.konform.validation.internal.ValidationNodeBuilder | ||
import kotlin.jvm.JvmName | ||
|
||
interface Validation<T> { | ||
interface Validation<C, T, E> { | ||
|
||
companion object { | ||
operator fun <T> invoke(init: ValidationBuilder<T>.() -> Unit): Validation<T> { | ||
val builder = ValidationBuilderImpl<T>() | ||
operator fun <C, T, E> invoke(init: ValidationBuilder<C, T, E>.() -> Unit): Validation<C, T, E> { | ||
val builder = ValidationNodeBuilder<C, T, E>() | ||
return builder.apply(init).build() | ||
} | ||
|
||
@JvmName("contextInvoke") | ||
operator fun <C, T> invoke(init: ValidationBuilder<C, T, String>.() -> Unit): Validation<C, T, String> { | ||
val builder = ValidationNodeBuilder<C, T, String>() | ||
return builder.apply(init).build() | ||
} | ||
|
||
@JvmName("simpleInvoke") | ||
operator fun <T> invoke(init: ValidationBuilder<Unit, T, String>.() -> Unit): Validation<Unit, T, String> { | ||
val builder = ValidationNodeBuilder<Unit, T, String>() | ||
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<E, T> | ||
operator fun invoke(context: C, value: T) = validate(context, value) | ||
} | ||
|
||
|
||
class Constraint<R> internal constructor(val hint: String, val templateValues: List<String>, val test: (R) -> Boolean) | ||
operator fun <T, E> Validation<Unit, T, E>.invoke(value: T) = validate(Unit, value) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,111 @@ | ||
package io.konform.validation | ||
|
||
import io.konform.validation.internal.ArrayValidation | ||
import io.konform.validation.internal.IterableValidation | ||
import io.konform.validation.internal.MapValidation | ||
import io.konform.validation.internal.OptionalValidation | ||
import io.konform.validation.internal.RequiredValidation | ||
import io.konform.validation.internal.ValidationBuilderImpl | ||
import io.konform.validation.internal.* | ||
import kotlin.jvm.JvmName | ||
import kotlin.reflect.KFunction1 | ||
import kotlin.reflect.KProperty1 | ||
|
||
@DslMarker | ||
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, E> : ComposableBuilder<C, T, E> { | ||
abstract override fun build(): Validation<C, T, E> | ||
|
||
abstract fun addConstraint(hint: HintBuilder<C, T, E>, vararg values: Any, test: C.(T) -> Boolean): ConstraintBuilder<C, T, E> | ||
|
||
abstract operator fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<C, R, E>.() -> Unit) | ||
abstract operator fun <R> KFunction1<T, R>.invoke(init: ValidationBuilder<C, R, E>.() -> Unit) | ||
|
||
internal abstract fun <R> onEachIterable(name: String, mapFn: (T) -> Iterable<R>, init: ValidationBuilder<C, R, E>.() -> 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, E>.() -> Unit) = onEachIterable(this.name, this, init) | ||
@JvmName("onEachIterable") | ||
infix fun <R> KFunction1<T, Iterable<R>>.onEach(init: ValidationBuilder<C, R, E>.() -> Unit) = onEachIterable(this.name, this, init) | ||
|
||
internal abstract fun <R> onEachArray(name: String, mapFn: (T) -> Array<R>, init: ValidationBuilder<C, R, E>.() -> Unit) | ||
@JvmName("onEachArray") | ||
infix fun <R> KProperty1<T, Array<R>>.onEach(init: ValidationBuilder<C, R, E>.() -> Unit) = onEachArray(this.name, this, init) | ||
@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> KFunction1<T, Array<R>>.onEach(init: ValidationBuilder<C, R, E>.() -> Unit) = onEachArray(this.name, this, init) | ||
|
||
internal abstract fun <K, V> onEachMap(name: String, mapFn: (T) -> Map<K, V>, init: ValidationBuilder<C, Map.Entry<K, V>, E>.() -> Unit) | ||
@JvmName("onEachMap") | ||
infix fun <K, V> KProperty1<T, Map<K, V>>.onEach(init: ValidationBuilder<C, Map.Entry<K, V>, E>.() -> Unit) = onEachMap(this.name, this, init) | ||
@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> KFunction1<T, Map<K, V>>.onEach(init: ValidationBuilder<C, Map.Entry<K, V>, E>.() -> Unit) = onEachMap(this.name, this, init) | ||
|
||
internal abstract fun <R : Any> ifPresent(name: String, mapFn: (T) -> R?, init: ValidationBuilder<C, R, E>.() -> Unit) | ||
infix fun <R : Any> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<C, R, E>.() -> Unit) = ifPresent(this.name, this, init) | ||
infix fun <R : Any> KFunction1<T, R?>.ifPresent(init: ValidationBuilder<C, R, E>.() -> Unit) = ifPresent(this.name, this, init) | ||
|
||
internal abstract fun <R : Any> required(name: String, hint: HintBuilder<C, R?, E>, mapFn: (T) -> R?, init: ValidationBuilder<C, R, E>.() -> Unit): ConstraintBuilder<C, R?, E> | ||
infix fun <R : Any> KProperty1<T, R?>.required(hintedBuilder: HintedRequiredBuilder<C, R, E>): ConstraintBuilder<C, R?, E> = | ||
required(this.name, hintedBuilder.hint, this, hintedBuilder.init) | ||
infix fun <R : Any> KFunction1<T, R?>.required(hintedBuilder: HintedRequiredBuilder<C, R, E>): ConstraintBuilder<C, R?, E> = | ||
required(this.name, hintedBuilder.hint, this, hintedBuilder.init) | ||
|
||
abstract fun <C, R, E> with(hint: HintBuilder<C, R?, E>, init: ValidationBuilder<C, R, E>.() -> Unit): HintedRequiredBuilder<C, R, E> | ||
abstract fun <C, R> with(init: ValidationBuilder<C, R, String>.() -> Unit): HintedRequiredBuilder<C, R, String> | ||
|
||
abstract fun <S> run(validation: Validation<S, T, E>, map: (C) -> S) | ||
fun run(validation: Validation<C, T, E>) = run(validation, ::identity) | ||
|
||
internal abstract fun add(builder: ComposableBuilder<C, T, E>) | ||
|
||
abstract val <R> KProperty1<T, R>.has: ValidationBuilder<C, R, E> | ||
} | ||
|
||
interface ConstraintBuilder<C, T, E> { | ||
infix fun hint(hint: HintBuilder<C, T, E>) : ConstraintBuilder<C, T, E> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be possible to define an extension function to allow the old
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice one! I added this extension function and one on I would keep |
||
} | ||
|
||
interface HintedRequiredBuilder<C, T, E> { | ||
val hint: HintBuilder<C, T?, E> | ||
val init: ValidationBuilder<C, T, E>.() -> Unit | ||
} | ||
|
||
fun <T : Any> ValidationBuilder<T?>.ifPresent(init: ValidationBuilder<T>.() -> Unit) { | ||
val builder = ValidationBuilderImpl<T>() | ||
init(builder) | ||
run(OptionalValidation(builder.build())) | ||
fun <C, T : Any, E> ValidationBuilder<C, T?, E>.ifPresent(init: ValidationBuilder<C, T, E>.() -> Unit) { | ||
val builder = ValidationNodeBuilder<C, T, E>().also(init) | ||
add(OptionalValidationBuilder(builder)) | ||
} | ||
|
||
fun <T : Any> ValidationBuilder<T?>.required(init: ValidationBuilder<T>.() -> Unit) { | ||
val builder = ValidationBuilderImpl<T>() | ||
init(builder) | ||
run(RequiredValidation(builder.build())) | ||
fun <C, T : Any, E> ValidationBuilder<C, T?, E>.required(hint: HintBuilder<C, T?, E>, init: ValidationBuilder<C, T, E>.() -> Unit): ConstraintBuilder<C, T?, E> { | ||
val builder = ValidationNodeBuilder<C, T, E>().also(init) | ||
val requiredValidationBuilder = RequiredValidationBuilder(hint, builder) | ||
add(requiredValidationBuilder) | ||
return requiredValidationBuilder.requiredConstraintBuilder | ||
} | ||
|
||
@JvmName("onEachIterable") | ||
fun <S, T : Iterable<S>> ValidationBuilder<T>.onEach(init: ValidationBuilder<S>.() -> Unit) { | ||
val builder = ValidationBuilderImpl<S>() | ||
init(builder) | ||
fun <C, S, T : Iterable<S>, E> ValidationBuilder<C, T, E>.onEach(init: ValidationBuilder<C, S, E>.() -> Unit) { | ||
val builder = ValidationNodeBuilder<C, S, E>().also(init) | ||
@Suppress("UNCHECKED_CAST") | ||
run(IterableValidation(builder.build()) as Validation<T>) | ||
add(IterableValidationBuilder(builder) as ComposableBuilder<C, T, E>) | ||
} | ||
|
||
@JvmName("onEachArray") | ||
fun <T> ValidationBuilder<Array<T>>.onEach(init: ValidationBuilder<T>.() -> Unit) { | ||
val builder = ValidationBuilderImpl<T>() | ||
init(builder) | ||
@Suppress("UNCHECKED_CAST") | ||
run(ArrayValidation(builder.build()) as Validation<Array<T>>) | ||
fun <C, T, E> ValidationBuilder<C, Array<T>, E>.onEach(init: ValidationBuilder<C, T, E>.() -> Unit) { | ||
val builder = ValidationNodeBuilder<C, T, E>().also(init) | ||
add(ArrayValidationBuilder(builder)) | ||
} | ||
|
||
@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>>() | ||
init(builder) | ||
fun <C, K, V, T : Map<K, V>, E> ValidationBuilder<C, T, E>.onEach(init: ValidationBuilder<C, Map.Entry<K, V>, E>.() -> Unit) { | ||
val builder = ValidationNodeBuilder<C, Map.Entry<K, V>, E>().also(init) | ||
@Suppress("UNCHECKED_CAST") | ||
run(MapValidation(builder.build()) as Validation<T>) | ||
add(MapValidationBuilder(builder) as ComposableBuilder<C, T, E>) | ||
} | ||
|
||
typealias HintArguments = List<Any> | ||
typealias HintBuilder<C, T, E> = C.(T, HintArguments) -> E | ||
|
||
fun <C, T> stringHint(template: String): HintBuilder<C, T, String> = { value, args -> | ||
args | ||
.map(Any::toString) | ||
.foldIndexed(template.replace("{value}", value.toString())) { index, acc, arg -> | ||
acc.replace("{$index}", arg) | ||
} | ||
} | ||
|
||
fun <C, T, E> staticHint(e: E): HintBuilder<C, T, E> = { _, _ -> e } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the other extension functions on
KFunction1
are not tested as far as I can see.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. I added some tests in a jvmTest module.