From 357a1dcad35f91b3b3fd331c940d1fd209d5f446 Mon Sep 17 00:00:00 2001 From: Leonhardt Koepsell Date: Thu, 18 Apr 2024 13:34:01 -0400 Subject: [PATCH 1/2] Add context to the ValidationBuilder API and a failing test --- .../io/konform/validation/ValidationBuilder.kt | 4 ++++ .../io/konform/validation/ValidationBuilderTest.kt | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt b/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt index aece51e..cf785da 100644 --- a/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt +++ b/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt @@ -58,6 +58,10 @@ public abstract class ValidationBuilder { public abstract fun run(validation: Validation) public abstract val KProperty1.has: ValidationBuilder + + public val context: Context get() = TODO("not implemented") + + public class Context(public val value: T) } public fun ValidationBuilder.ifPresent(init: ValidationBuilder.() -> Unit) { diff --git a/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt b/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt index 27324bb..7ba328d 100644 --- a/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt +++ b/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt @@ -115,6 +115,20 @@ class ValidationBuilderTest { Register(referredBy = "poweruser@").let { assertEquals(1, countErrors(nullableFieldValidation(it), Register::referredBy)) } } + @Test + fun validatingDependentFields() { + val nullableFieldValidation = + Validation { + val register = context.value + Register::password { + addConstraint("cannot equal email") { it != register.email } + } + } + + Register(email = "sillyuser@test.com", password = "sillyuser@test.com") + .let { assertEquals(1, countErrors(nullableFieldValidation(it), Register::password)) } + } + @Test fun validatingNestedTypesDirectly() { val nestedTypeValidation = From 42b7cab6fe7c25f50ac21823fa6a7e9ef23c78b2 Mon Sep 17 00:00:00 2001 From: Leonhardt Koepsell Date: Sun, 12 May 2024 08:54:21 -0700 Subject: [PATCH 2/2] Refactor context and allow destructring - Moving Context from ValidationBuilder to Validation - This commit includes a failing test, destructuring doesn't work yet. --- .../io/konform/validation/Validation.kt | 35 +++++++++++++++++-- .../konform/validation/ValidationBuilder.kt | 4 --- .../validation/ValidationBuilderTest.kt | 33 ++++++++++++++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/commonMain/kotlin/io/konform/validation/Validation.kt b/src/commonMain/kotlin/io/konform/validation/Validation.kt index c992e79..68104bd 100644 --- a/src/commonMain/kotlin/io/konform/validation/Validation.kt +++ b/src/commonMain/kotlin/io/konform/validation/Validation.kt @@ -4,15 +4,46 @@ import io.konform.validation.internal.ValidationBuilderImpl public interface Validation { public companion object { - public operator fun invoke(init: ValidationBuilder.() -> Unit): Validation { + public operator fun invoke(init: ValidationBuilder.(Context) -> Unit): Validation { + val context = Context() val builder = ValidationBuilderImpl() - return builder.apply(init).build() + init(builder, context) + val validation = builder.build() + return object : Validation by validation { + override fun validate(value: T): ValidationResult { + context.subject = value + return validation.validate(value) + } + + override fun invoke(value: T): ValidationResult { + return validate(value) + } + } } } public fun validate(value: T): ValidationResult public operator fun invoke(value: T): ValidationResult = validate(value) + + public class Context { + private var _subjectHolder: SubjectHolder? = null + + public var subject: T + get() { + return when (val subjectHolder = _subjectHolder) { + null -> throw IllegalStateException("Subject not initialized") + else -> subjectHolder.value + } + } + set(value) { + _subjectHolder = SubjectHolder(value) + } + + public operator fun component1(): T = subject + + private data class SubjectHolder(val value: T) + } } public class Constraint internal constructor( diff --git a/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt b/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt index cf785da..aece51e 100644 --- a/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt +++ b/src/commonMain/kotlin/io/konform/validation/ValidationBuilder.kt @@ -58,10 +58,6 @@ public abstract class ValidationBuilder { public abstract fun run(validation: Validation) public abstract val KProperty1.has: ValidationBuilder - - public val context: Context get() = TODO("not implemented") - - public class Context(public val value: T) } public fun ValidationBuilder.ifPresent(init: ValidationBuilder.() -> Unit) { diff --git a/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt b/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt index 7ba328d..fe1e48c 100644 --- a/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt +++ b/src/commonTest/kotlin/io/konform/validation/ValidationBuilderTest.kt @@ -116,17 +116,29 @@ class ValidationBuilderTest { } @Test - fun validatingDependentFields() { - val nullableFieldValidation = - Validation { - val register = context.value + fun validatingFieldsWithContext() { + val fieldValidation = + Validation { context -> + Register::password { + addConstraint("cannot equal email") { it != context.subject.email } + } + } + + Register(email = "sillyuser@test.com", password = "sillyuser@test.com") + .let { assertEquals(1, countErrors(fieldValidation(it), Register::password)) } + } + + @Test + fun validatingFieldsWithDestructuredContext() { + val fieldValidation = + Validation { (register) -> Register::password { addConstraint("cannot equal email") { it != register.email } } } Register(email = "sillyuser@test.com", password = "sillyuser@test.com") - .let { assertEquals(1, countErrors(nullableFieldValidation(it), Register::password)) } + .let { assertEquals(1, countErrors(fieldValidation(it), Register::password)) } } @Test @@ -144,6 +156,17 @@ class ValidationBuilderTest { Register(home = Address("")).let { assertEquals(1, countErrors(nestedTypeValidation(it), Register::home, Address::address)) } } + @Test + fun validatingNullableValues() { + val nullableValueValidation = + Validation { + addConstraint("cannot be null") { it != null} + } + + "poweruser@test.com".let { assertEquals(Valid(it), nullableValueValidation(it)) } + null.let { assertEquals(1, countErrors(nullableValueValidation(it))) } + } + @Test fun validatingOptionalNullableValues() { val nullableTypeValidation =