Skip to content

Commit

Permalink
Add recursive validation example to README (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhoepelman authored Nov 18, 2024
1 parent 9b80d75 commit 29c0ec7
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 14 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,25 @@ val requireCat = Validation<Animal> {
}
```

#### Recursive validation

If you have a recursive type that you can validate, this requires

1) an extra getter to get a self-reference to the validation, and
2) dynamic to create an extra instance of the validation as-needed to avoid an infinite loop

```
data class Node(val children: List<Node>)
val validation = Validation<Node> {
// Use dynamic and a function to get the current validation again
Node::children onEach {
runDynamic { validationRef() }
}
}
// Type must be explicitly specified on either this or the val
private val validationRef get(): Validation<Node> = validation
```

### Other validation libraries for Kotlin

- Akkurate: https://akkurate.dev/docs/overview.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,22 @@ import io.konform.validation.ValidationBuilder
import kotlin.jvm.JvmName

public fun <T : Iterable<*>> ValidationBuilder<T>.minItems(minSize: Int): Constraint<T> =
addConstraint("must have at least {0} items", minSize.toString()) { it.count() >= minSize }
constrain("must have at least $minSize items") { it.count() >= minSize }

@JvmName("arrayMinItems")
public fun <T> ValidationBuilder<Array<T>>.minItems(minSize: Int): Constraint<Array<T>> =
addConstraint("must have at least {0} items", minSize.toString()) { it.count() >= minSize }
constrain("must have at least $minSize items") { it.count() >= minSize }

public fun <T : Iterable<*>> ValidationBuilder<T>.maxItems(maxSize: Int): Constraint<T> =
addConstraint("must have at most {0} items", maxSize.toString()) {
it.count() <= maxSize
}
constrain("must have at most $maxSize items") { it.count() <= maxSize }

@JvmName("arrayMaxItems")
public fun <T> ValidationBuilder<Array<T>>.maxItems(maxSize: Int): Constraint<Array<T>> =
addConstraint("must have at most {0} items", maxSize.toString()) {
it.count() <= maxSize
}
constrain("must have at most $maxSize items") { it.count() <= maxSize }

public fun <T : Iterable<*>> ValidationBuilder<T>.uniqueItems(unique: Boolean = true): Constraint<T> =
addConstraint("all items must be unique") {
!unique || it.distinct().count() == it.count()
}
constrain("all items must be unique") { !unique || it.distinct().count() == it.count() }

@JvmName("arrayUniqueItems")
public fun <T> ValidationBuilder<Array<T>>.uniqueItems(unique: Boolean = true): Constraint<Array<T>> =
addConstraint("all items must be unique") {
!unique || it.distinct().count() == it.count()
}
constrain("all items must be unique") { !unique || it.distinct().count() == it.count() }
41 changes: 41 additions & 0 deletions src/commonTest/kotlin/io/konform/validation/ReadmeExampleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import io.konform.validation.constraints.minimum
import io.konform.validation.constraints.pattern
import io.konform.validation.path.PathValue
import io.konform.validation.path.PropRef
import io.konform.validation.path.ValidationPath
import io.kotest.assertions.konform.shouldBeInvalid
import io.kotest.assertions.konform.shouldBeValid
import io.kotest.assertions.konform.shouldContainError
import io.kotest.assertions.konform.shouldContainOnlyError
import kotlin.collections.Map.Entry
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -227,4 +229,43 @@ class ReadmeExampleTest {
it.shouldContainError(ValidationError.of(PathValue("age"), "must be at least '21'"))
}
}

@Test
fun recursiveValidation() {
Recursive.validation shouldBeValid Recursive.Node()
val invalid =
Recursive.Node(
listOf(
Recursive.Node(
listOf(
Recursive.Node(),
Recursive.Node(),
),
),
),
)
(Recursive.validation shouldBeInvalid invalid) shouldContainOnlyError
ValidationError.of(
ValidationPath.of(Recursive.Node::children, 0, Recursive.Node::children),
"must have at most 1 items",
)
}

object Recursive {
data class Node(
val children: List<Node> = emptyList(),
)

val validation =
Validation<Node> {
Node::children {
maxItems(1)
}
// Use dynamic and a function to get the current validation again
Node::children onEach {
runDynamic { validationRef }
}
}
private val validationRef get(): Validation<Node> = validation
}
}

0 comments on commit 29c0ec7

Please sign in to comment.