Skip to content

Commit

Permalink
Enable explicit API mode (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhoepelman authored Apr 1, 2024
1 parent da9f26c commit ce7b5a4
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 51 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ group = projectGroup
version = projectVersion

kotlin {
// Since we are a library, prevent accidentally making things part of the public API
explicitApi()

sourceSets.all {
languageSettings {
languageVersion = kotlinApiTarget
Expand Down
16 changes: 10 additions & 6 deletions src/commonMain/kotlin/io/konform/validation/Validation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package io.konform.validation

import io.konform.validation.internal.ValidationBuilderImpl

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

fun validate(value: T): ValidationResult<T>
public fun validate(value: T): ValidationResult<T>

operator fun invoke(value: T) = validate(value)
public operator fun invoke(value: T): ValidationResult<T> = validate(value)
}

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

@ValidationScope
abstract class ValidationBuilder<T> {
abstract fun build(): Validation<T>
public abstract class ValidationBuilder<T> {
public abstract fun build(): Validation<T>

abstract fun addConstraint(
public abstract fun addConstraint(
errorMessage: String,
vararg templateValues: String,
test: (T) -> Boolean,
): Constraint<T>

abstract infix fun Constraint<T>.hint(hint: String): Constraint<T>
public abstract infix fun Constraint<T>.hint(hint: String): Constraint<T>

abstract operator fun <R> KProperty1<T, R>.invoke(init: ValidationBuilder<R>.() -> Unit)
public 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,
)

@JvmName("onEachIterable")
infix fun <R> KProperty1<T, Iterable<R>>.onEach(init: ValidationBuilder<R>.() -> Unit) = onEachIterable(this, init)
public infix fun <R> KProperty1<T, Iterable<R>>.onEach(init: ValidationBuilder<R>.() -> Unit): Unit = onEachIterable(this, init)

internal abstract fun <R> onEachArray(
prop: KProperty1<T, Array<R>>,
init: ValidationBuilder<R>.() -> Unit,
)

@JvmName("onEachArray")
infix fun <R> KProperty1<T, Array<R>>.onEach(init: ValidationBuilder<R>.() -> Unit) = onEachArray(this, init)
public infix fun <R> KProperty1<T, Array<R>>.onEach(init: ValidationBuilder<R>.() -> Unit): Unit = onEachArray(this, init)

internal abstract fun <K, V> onEachMap(
prop: KProperty1<T, Map<K, V>>,
init: ValidationBuilder<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)
public infix fun <K, V> KProperty1<T, Map<K, V>>.onEach(init: ValidationBuilder<Map.Entry<K, V>>.() -> Unit): Unit =
onEachMap(this, init)

abstract infix fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<R>.() -> Unit)
public abstract infix fun <R> KProperty1<T, R?>.ifPresent(init: ValidationBuilder<R>.() -> Unit)

abstract infix fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<R>.() -> Unit)
public abstract infix fun <R> KProperty1<T, R?>.required(init: ValidationBuilder<R>.() -> Unit)

abstract fun run(validation: Validation<T>)
public abstract fun run(validation: Validation<T>)

abstract val <R> KProperty1<T, R>.has: ValidationBuilder<R>
public abstract val <R> KProperty1<T, R>.has: ValidationBuilder<R>
}

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

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

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

@JvmName("onEachArray")
fun <T> ValidationBuilder<Array<T>>.onEach(init: ValidationBuilder<T>.() -> Unit) {
public 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>>)
}

@JvmName("onEachMap")
fun <K, V, T : Map<K, V>> ValidationBuilder<T>.onEach(init: ValidationBuilder<Map.Entry<K, V>>.() -> Unit) {
public 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)
@Suppress("UNCHECKED_CAST")
Expand Down
20 changes: 10 additions & 10 deletions src/commonMain/kotlin/io/konform/validation/ValidationResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package io.konform.validation

import kotlin.reflect.KProperty1

interface ValidationError {
val dataPath: String
val message: String
public interface ValidationError {
public val dataPath: String
public val message: String
}

internal data class PropertyValidationError(
Expand All @@ -16,7 +16,7 @@ internal data class PropertyValidationError(
}
}

interface ValidationErrors : List<ValidationError>
public interface ValidationErrors : List<ValidationError>

internal object NoValidationErrors : ValidationErrors, List<ValidationError> by emptyList()

Expand All @@ -26,15 +26,15 @@ internal class DefaultValidationErrors(private val errors: List<ValidationError>
}
}

sealed class ValidationResult<out T> {
abstract operator fun get(vararg propertyPath: Any): List<String>?
public sealed class ValidationResult<out T> {
public abstract operator fun get(vararg propertyPath: Any): List<String>?

abstract fun <R> map(transform: (T) -> R): ValidationResult<R>
public abstract fun <R> map(transform: (T) -> R): ValidationResult<R>

abstract val errors: ValidationErrors
public abstract val errors: ValidationErrors
}

data class Invalid<T>(
public data class Invalid<T>(
internal val internalErrors: Map<String, List<String>>,
) : ValidationResult<T>() {
override fun get(vararg propertyPath: Any): List<String>? = internalErrors[propertyPath.joinToString("", transform = ::toPathSegment)]
Expand Down Expand Up @@ -62,7 +62,7 @@ data class Invalid<T>(
}
}

data class Valid<T>(val value: T) : ValidationResult<T>() {
public data class Valid<T>(val value: T) : ValidationResult<T>() {
override fun get(vararg propertyPath: Any): List<String>? = null

override fun <R> map(transform: (T) -> R): ValidationResult<R> = Valid(transform(this.value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,32 @@ import io.konform.validation.Constraint
import io.konform.validation.ValidationBuilder
import kotlin.math.roundToInt

inline fun <reified T> ValidationBuilder<*>.type() =
public inline fun <reified T> ValidationBuilder<*>.type(): Constraint<*> =
addConstraint(
"must be of the correct type",
) { it is T }

fun <T> ValidationBuilder<T>.enum(vararg allowed: T) =
public fun <T> ValidationBuilder<T>.enum(vararg allowed: T): Constraint<T> =
addConstraint(
"must be one of: {0}",
allowed.joinToString("', '", "'", "'"),
) { it in allowed }

inline fun <reified T : Enum<T>> ValidationBuilder<String>.enum(): Constraint<String> {
public inline fun <reified T : Enum<T>> ValidationBuilder<String>.enum(): Constraint<String> {
val enumNames = enumValues<T>().map { it.name }
return addConstraint(
"must be one of: {0}",
enumNames.joinToString("', '", "'", "'"),
) { it in enumNames }
}

fun <T> ValidationBuilder<T>.const(expected: T) =
public fun <T> ValidationBuilder<T>.const(expected: T): Constraint<T> =
addConstraint(
"must be {0}",
expected?.let { "'$it'" } ?: "null",
) { expected == it }

fun <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constraint<T> {
public fun <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constraint<T> {
val factorAsDouble = factor.toDouble()
require(factorAsDouble > 0) { "multipleOf requires the factor to be strictly larger than 0" }
return addConstraint("must be a multiple of '{0}'", factor.toString()) {
Expand All @@ -38,55 +38,55 @@ fun <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constraint<T>
}
}

fun <T : Number> ValidationBuilder<T>.maximum(maximumInclusive: Number) =
public fun <T : Number> ValidationBuilder<T>.maximum(maximumInclusive: Number): Constraint<T> =
addConstraint(
"must be at most '{0}'",
maximumInclusive.toString(),
) { it.toDouble() <= maximumInclusive.toDouble() }

fun <T : Number> ValidationBuilder<T>.exclusiveMaximum(maximumExclusive: Number) =
public fun <T : Number> ValidationBuilder<T>.exclusiveMaximum(maximumExclusive: Number): Constraint<T> =
addConstraint(
"must be less than '{0}'",
maximumExclusive.toString(),
) { it.toDouble() < maximumExclusive.toDouble() }

fun <T : Number> ValidationBuilder<T>.minimum(minimumInclusive: Number) =
public fun <T : Number> ValidationBuilder<T>.minimum(minimumInclusive: Number): Constraint<T> =
addConstraint(
"must be at least '{0}'",
minimumInclusive.toString(),
) { it.toDouble() >= minimumInclusive.toDouble() }

fun <T : Number> ValidationBuilder<T>.exclusiveMinimum(minimumExclusive: Number) =
public fun <T : Number> ValidationBuilder<T>.exclusiveMinimum(minimumExclusive: Number): Constraint<T> =
addConstraint(
"must be greater than '{0}'",
minimumExclusive.toString(),
) { it.toDouble() > minimumExclusive.toDouble() }

fun ValidationBuilder<String>.minLength(length: Int): Constraint<String> {
public fun ValidationBuilder<String>.minLength(length: Int): Constraint<String> {
require(length >= 0) { IllegalArgumentException("minLength requires the length to be >= 0") }
return addConstraint(
"must have at least {0} characters",
length.toString(),
) { it.length >= length }
}

fun ValidationBuilder<String>.maxLength(length: Int): Constraint<String> {
public fun ValidationBuilder<String>.maxLength(length: Int): Constraint<String> {
require(length >= 0) { IllegalArgumentException("maxLength requires the length to be >= 0") }
return addConstraint(
"must have at most {0} characters",
length.toString(),
) { it.length <= length }
}

fun ValidationBuilder<String>.pattern(pattern: String) = pattern(pattern.toRegex())
public fun ValidationBuilder<String>.pattern(pattern: String): Constraint<String> = pattern(pattern.toRegex())

fun ValidationBuilder<String>.pattern(pattern: Regex) =
public fun ValidationBuilder<String>.pattern(pattern: Regex): Constraint<String> =
addConstraint(
"must match the expected pattern",
pattern.toString(),
) { it.matches(pattern) }

inline fun <reified T> ValidationBuilder<T>.minItems(minSize: Int): Constraint<T> =
public inline fun <reified T> ValidationBuilder<T>.minItems(minSize: Int): Constraint<T> =
addConstraint(
"must have at least {0} items",
minSize.toString(),
Expand All @@ -99,7 +99,7 @@ inline fun <reified T> ValidationBuilder<T>.minItems(minSize: Int): Constraint<T
}
}

inline fun <reified T> ValidationBuilder<T>.maxItems(maxSize: Int): Constraint<T> =
public inline fun <reified T> ValidationBuilder<T>.maxItems(maxSize: Int): Constraint<T> =
addConstraint(
"must have at most {0} items",
maxSize.toString(),
Expand All @@ -112,13 +112,13 @@ inline fun <reified T> ValidationBuilder<T>.maxItems(maxSize: Int): Constraint<T
}
}

inline fun <reified T : Map<*, *>> ValidationBuilder<T>.minProperties(minSize: Int): Constraint<T> =
public inline fun <reified T : Map<*, *>> ValidationBuilder<T>.minProperties(minSize: Int): Constraint<T> =
minItems(minSize) hint "must have at least {0} properties"

inline fun <reified T : Map<*, *>> ValidationBuilder<T>.maxProperties(maxSize: Int): Constraint<T> =
public inline fun <reified T : Map<*, *>> ValidationBuilder<T>.maxProperties(maxSize: Int): Constraint<T> =
maxItems(maxSize) hint "must have at most {0} properties"

inline fun <reified T> ValidationBuilder<T>.uniqueItems(unique: Boolean): Constraint<T> =
public inline fun <reified T> ValidationBuilder<T>.uniqueItems(unique: Boolean): Constraint<T> =
addConstraint(
"all items must be unique",
) {
Expand Down

0 comments on commit ce7b5a4

Please sign in to comment.