From 4031bd7baf34d15f9904591112b7cf07c3929dd7 Mon Sep 17 00:00:00 2001 From: Gennadi Kudrjavtsev Date: Thu, 16 Dec 2021 11:03:06 +0200 Subject: [PATCH] Support receiver bound properties to validate: https://github.com/valiktor/valiktor/issues/71 --- .../src/main/kotlin/org/valiktor/Validator.kt | 76 +++++++++++++++++-- .../org/valiktor/functions/AnyFunctions.kt | 2 +- .../org/valiktor/functions/ArrayFunctions.kt | 4 +- .../valiktor/functions/IterableFunctions.kt | 4 +- .../valiktor/functions/AnyFunctionsTest.kt | 12 +++ 5 files changed, 85 insertions(+), 13 deletions(-) diff --git a/valiktor-core/src/main/kotlin/org/valiktor/Validator.kt b/valiktor-core/src/main/kotlin/org/valiktor/Validator.kt index 628eb8ee..5cf36da5 100644 --- a/valiktor-core/src/main/kotlin/org/valiktor/Validator.kt +++ b/valiktor-core/src/main/kotlin/org/valiktor/Validator.kt @@ -16,6 +16,8 @@ package org.valiktor +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 import kotlin.reflect.KProperty1 /** @@ -62,11 +64,33 @@ open class Validator(private val obj: E) { /** * Returns a [Property] for this property * - * @receiver the property to be validated + * @receiver the class property to be validated * @return the property validator + * + * @see KProperty1 */ @JvmName("validate") - fun validate(property: KProperty1): Property = Property(obj, property) + fun validate(property: KProperty1): Property = Property1(obj, property) + + /** + * Returns a [Property] for this property + * + * @receiver the object property to be validated + * @return the property validator + * + * @see KProperty0 + */ + @JvmName("validate") + fun validate(property: KProperty0): Property = Property0(property) + + /** + * Returns a [Property] for this iterable property + * + * @receiver the property to be validated + * @return the property validator + */ + @JvmName("validateIterable") + fun validate(property: KProperty1?>): Property?> = Property1(obj, property) /** * Returns a [Property] for this iterable property @@ -75,7 +99,7 @@ open class Validator(private val obj: E) { * @return the property validator */ @JvmName("validateIterable") - fun validate(property: KProperty1?>): Property?> = Property(obj, property) + fun validate(property: KProperty0?>): Property?> = Property0(property) /** * Returns a [Property] for this array property @@ -84,20 +108,56 @@ open class Validator(private val obj: E) { * @return the property validator */ @JvmName("validateArray") - fun validate(property: KProperty1?>): Property?> = Property(obj, property) + fun validate(property: KProperty1?>): Property?> = Property1(obj, property) + + /** + * Returns a [Property] for this array property + * + * @receiver the property to be validated + * @return the property validator + */ + @JvmName("validateArray") + fun validate(property: KProperty0?>): Property?> = Property0(property) + + /** + * KProperty1 based implementation of a Property + * + * @param property KProperty1 + * + * @author Gennadi Kudrjavtsev + * @since 0.13 + * @see Property + * @see KProperty1 + */ + private inner class Property1(obj: E, property: KProperty1) : + Property(property, { property.get(obj) }) + + /** + * KProperty0 based implementation of a Property + * + * @param property KProperty0 + * + * @author Gennadi Kudrjavtsev + * @since 0.13 + * @see Property + * @see KProperty0 + */ + private inner class Property0(property: KProperty0) : + Property(property, property::get) /** * Represents a property validator that contains extended functions * - * @param obj specifies the object to be validated * @param property specifies the property to be validated + * @param getValue specifies the function to get value of a property * * @author Rodolpho S. Couto + * @author Gennadi Kudrjavtsev * @see Validator * @see KProperty1 * @since 0.1.0 */ - open inner class Property(val obj: E, val property: KProperty1) { + abstract inner class Property(val property: KProperty, val getValue: () -> T?) { /** * Validates the property by passing the constraint and the validation function @@ -109,7 +169,7 @@ open class Validator(private val obj: E) { * @return the property validator */ fun validate(constraint: (T?) -> Constraint, isValid: (T?) -> Boolean): Property { - val value = this.property.get(this.obj) + val value = getValue() if (!isValid(value)) { this@Validator.constraintViolations += DefaultConstraintViolation( property = this.property.name, @@ -142,7 +202,7 @@ open class Validator(private val obj: E) { * @return the property validator */ suspend fun coValidate(constraint: (T?) -> Constraint, isValid: suspend (T?) -> Boolean): Property { - val value = this.property.get(this.obj) + val value = getValue() if (!isValid(value)) { this@Validator.constraintViolations += DefaultConstraintViolation( property = this.property.name, diff --git a/valiktor-core/src/main/kotlin/org/valiktor/functions/AnyFunctions.kt b/valiktor-core/src/main/kotlin/org/valiktor/functions/AnyFunctions.kt index 1656f8c1..702a53d0 100644 --- a/valiktor-core/src/main/kotlin/org/valiktor/functions/AnyFunctions.kt +++ b/valiktor-core/src/main/kotlin/org/valiktor/functions/AnyFunctions.kt @@ -34,7 +34,7 @@ import org.valiktor.constraints.Valid * @return the same receiver property */ inline fun Validator.Property.validate(block: Validator.(T) -> Unit): Validator.Property { - val value = this.property.get(this.obj) + val value = getValue() if (value != null) { this.addConstraintViolations( Validator(value).apply { block(value) }.constraintViolations.map { diff --git a/valiktor-core/src/main/kotlin/org/valiktor/functions/ArrayFunctions.kt b/valiktor-core/src/main/kotlin/org/valiktor/functions/ArrayFunctions.kt index 9dc25177..b20983b3 100644 --- a/valiktor-core/src/main/kotlin/org/valiktor/functions/ArrayFunctions.kt +++ b/valiktor-core/src/main/kotlin/org/valiktor/functions/ArrayFunctions.kt @@ -43,7 +43,7 @@ import org.valiktor.constraints.Size inline fun Validator.Property?>.validateForEach( block: Validator.(T) -> Unit ): Validator.Property?> { - this.property.get(this.obj)?.forEachIndexed { index, value -> + getValue()?.forEachIndexed { index, value -> this.addConstraintViolations( Validator(value).apply { block(value) }.constraintViolations.map { DefaultConstraintViolation( @@ -68,7 +68,7 @@ inline fun Validator.Property?>.validateForEach( inline fun Validator.Property?>.validateForEachIndexed( block: Validator.(Int, T) -> Unit ): Validator.Property?> { - this.property.get(this.obj)?.forEachIndexed { index, value -> + getValue()?.forEachIndexed { index, value -> this.addConstraintViolations( Validator(value).apply { block(index, value) }.constraintViolations.map { DefaultConstraintViolation( diff --git a/valiktor-core/src/main/kotlin/org/valiktor/functions/IterableFunctions.kt b/valiktor-core/src/main/kotlin/org/valiktor/functions/IterableFunctions.kt index 21b908ec..1230493a 100644 --- a/valiktor-core/src/main/kotlin/org/valiktor/functions/IterableFunctions.kt +++ b/valiktor-core/src/main/kotlin/org/valiktor/functions/IterableFunctions.kt @@ -41,7 +41,7 @@ import org.valiktor.constraints.Size inline fun Validator.Property?>.validateForEach( block: Validator.(T) -> Unit ): Validator.Property?> { - this.property.get(this.obj)?.forEachIndexed { index, value -> + getValue()?.forEachIndexed { index, value -> this.addConstraintViolations( Validator(value).apply { block(value) }.constraintViolations.map { DefaultConstraintViolation( @@ -66,7 +66,7 @@ inline fun Validator.Property?>.validateForEach( inline fun Validator.Property?>.validateForEachIndexed( block: Validator.(Int, T) -> Unit ): Validator.Property?> { - this.property.get(this.obj)?.forEachIndexed { index, value -> + getValue()?.forEachIndexed { index, value -> this.addConstraintViolations( Validator(value).apply { block(index, value) }.constraintViolations.map { DefaultConstraintViolation( diff --git a/valiktor-core/src/test/kotlin/org/valiktor/functions/AnyFunctionsTest.kt b/valiktor-core/src/test/kotlin/org/valiktor/functions/AnyFunctionsTest.kt index e6067a95..4e7d3204 100644 --- a/valiktor-core/src/test/kotlin/org/valiktor/functions/AnyFunctionsTest.kt +++ b/valiktor-core/src/test/kotlin/org/valiktor/functions/AnyFunctionsTest.kt @@ -60,6 +60,18 @@ private object AnyFunctionsFixture { @ExperimentalCoroutinesApi class AnyFunctionsTest { + @Test + fun `should validate by KProperty0`() { + validate(Employee(company = Company(1))) { + validate(it::company).isNotNull().validate { company -> + validate(company::id).isNotNull() + } + validate(it::id).isNull() + validate(it::address).isNull() + validate(it::name).isNull() + } + } + @Test fun `isNull with null property should be valid`() { validate(Employee()) {