diff --git a/akkurate-arrow/src/commonMain/kotlin/dev/nesk/akkurate/arrow/Arrow.kt b/akkurate-arrow/src/commonMain/kotlin/dev/nesk/akkurate/arrow/Arrow.kt index ece15864..0f4a72df 100644 --- a/akkurate-arrow/src/commonMain/kotlin/dev/nesk/akkurate/arrow/Arrow.kt +++ b/akkurate-arrow/src/commonMain/kotlin/dev/nesk/akkurate/arrow/Arrow.kt @@ -23,8 +23,10 @@ import arrow.core.raise.either import dev.nesk.akkurate.ValidationResult import dev.nesk.akkurate.constraints.ConstraintViolation import dev.nesk.akkurate.constraints.ConstraintViolationSet +import dev.nesk.akkurate.constraints.GenericConstraintViolation +import dev.nesk.akkurate.constraints.GenericConstraintViolationSet -private fun ConstraintViolationSet.toNonEmptySet(): NonEmptySet = +private fun GenericConstraintViolationSet.toNonEmptySet(): NonEmptySet> = nonEmptySetOf(this.first(), *this.drop(1).toTypedArray()) /** @@ -52,7 +54,7 @@ private fun ConstraintViolationSet.toNonEmptySet(): NonEmptySet" * ``` */ -public fun ValidationResult.toEither(): Either, T> = when (this) { +public fun ValidationResult.toEither(): Either>, T> = when (this) { is ValidationResult.Failure -> violations.toNonEmptySet().left() is ValidationResult.Success -> value.right() } @@ -85,7 +87,7 @@ public fun ValidationResult.toEither(): Either Raise>.bind(validationResult: ValidationResult): T = when (validationResult) { +public fun Raise>>.bind(validationResult: ValidationResult): T = when (validationResult) { is ValidationResult.Failure -> raise(validationResult.violations.toNonEmptySet()) is ValidationResult.Success -> validationResult.value } diff --git a/akkurate-arrow/src/commonTest/kotlin/dev/nesk/akkurate/arrow/ArrowKtTest.kt b/akkurate-arrow/src/commonTest/kotlin/dev/nesk/akkurate/arrow/ArrowKtTest.kt index 6ec84d3f..27fbc7df 100644 --- a/akkurate-arrow/src/commonTest/kotlin/dev/nesk/akkurate/arrow/ArrowKtTest.kt +++ b/akkurate-arrow/src/commonTest/kotlin/dev/nesk/akkurate/arrow/ArrowKtTest.kt @@ -24,6 +24,7 @@ import dev.nesk.akkurate.ValidationResult import dev.nesk.akkurate.Validator import dev.nesk.akkurate.constraints.ConstraintViolation import dev.nesk.akkurate.constraints.builders.isIdenticalTo +import dev.nesk.akkurate.validatables.DefaultMetadataType import kotlin.test.Test import kotlin.test.assertIs import kotlin.test.assertSame @@ -59,7 +60,7 @@ class ArrowKtTest { val validationResult = validate(SomeSealedClass.Invalid) val validationEither = validationResult.toEither() - assertIs(validationResult) + assertIs>(validationResult) assertIs>>(validationEither, "The `Either` instance is a `Left` type") assertSame( validationResult.violations.single(), @@ -87,7 +88,7 @@ class ArrowKtTest { val validationResult = validate(SomeSealedClass.Invalid) val validationEither = either { bind(validationResult) } - assertIs(validationResult) + assertIs>(validationResult) assertIs>>(validationEither, "The `Either` instance is a `Left` type") assertSame( validationResult.violations.single(), diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/AkkurateScratch.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/AkkurateScratch.kt index 77644c54..b3f03f3c 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/AkkurateScratch.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/AkkurateScratch.kt @@ -17,6 +17,7 @@ package dev.nesk.akkurate +import dev.nesk.akkurate.validatables.DefaultMetadataType import dev.nesk.akkurate.validatables.Validatable /** @@ -62,13 +63,13 @@ internal class AkkurateScratch { public fun Validator( configuration: Configuration = Configuration(), block: Validatable.() -> Unit, - ): Validator.Runner = ValidatorWrapper(dev.nesk.akkurate.Validator(configuration, block)) + ): Validator.Runner = ValidatorWrapper(dev.nesk.akkurate.Validator(configuration, block)) } public class ValidatorWrapper( - private val delegate: Validator.Runner, - ) : Validator.Runner by delegate { - override fun invoke(value: ValueType): ValidationResult { + private val delegate: Validator.Runner, + ) : Validator.Runner by delegate { + override fun invoke(value: ValueType): ValidationResult { return delegate(value).also { result -> when (result) { is ValidationResult.Success -> println("Success") diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Path.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Path.kt index 7b349653..82514bbc 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Path.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Path.kt @@ -17,11 +17,12 @@ package dev.nesk.akkurate +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable public typealias Path = List -public class PathBuilder(private val validatable: Validatable<*>) { +public class PathBuilder(private val validatable: GenericValidatable<*, MetadataType>) { public fun absolute(vararg pathSegments: String): Path = pathSegments.toList() public fun relative(vararg pathSegments: String): Path { @@ -32,4 +33,4 @@ public class PathBuilder(private val validatable: Validatable<*>) { public fun appended(vararg pathSegments: String): Path = validatable.path() + pathSegments.toList() } -public fun Validatable<*>.path(block: PathBuilder.() -> Path): Path = PathBuilder(this).block() +public fun GenericValidatable<*, MetadataType>.path(block: PathBuilder.() -> Path): Path = PathBuilder(this).block() diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/ValidationResult.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/ValidationResult.kt index 7a60898c..cf82fab3 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/ValidationResult.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/ValidationResult.kt @@ -20,16 +20,20 @@ package dev.nesk.akkurate import dev.nesk.akkurate.ValidationResult.Failure import dev.nesk.akkurate.ValidationResult.Success import dev.nesk.akkurate.constraints.ConstraintViolationSet +import dev.nesk.akkurate.constraints.GenericConstraintViolationSet +import dev.nesk.akkurate.validatables.DefaultMetadataType /** * The result of a validation. Can be [a successful outcome][Success] with the validated value, or [a failure][Failure] with a violation list. */ -public sealed interface ValidationResult { +public sealed interface ValidationResult { /** * Throws an [Exception] if the result is a failure. */ public fun orThrow() + public fun orThrow(toDefaultMetadata: (E) -> DefaultMetadataType) + /** * A successful outcome to the validation, with [the validated value][value]. */ @@ -38,13 +42,14 @@ public sealed interface ValidationResult { * The subject of the validation result. */ public val value: T, - ) : ValidationResult { + ) : ValidationResult { /** * Returns the [value]. */ public operator fun component1(): T = value override fun orThrow(): Unit = Unit + override fun orThrow(toDefaultMetadata: (Nothing) -> DefaultMetadataType): Unit = Unit override fun equals(other: Any?): Boolean { if (this === other) return true @@ -64,30 +69,36 @@ public sealed interface ValidationResult { /** * A failed outcome to the validation, with a violation list describing what went wrong. */ - public class Failure internal constructor( + public class Failure internal constructor( /** * A list of failed constraint violations. */ - public val violations: ConstraintViolationSet, - ) : ValidationResult { + public val violations: GenericConstraintViolationSet, + ) : ValidationResult { /** * Returns the [violations]. */ - public operator fun component1(): ConstraintViolationSet = violations + public operator fun component1(): GenericConstraintViolationSet = violations + + override fun orThrow(): Nothing = orThrow { emptyMap() } + override fun orThrow(toDefaultMetadata: (E) -> DefaultMetadataType): Nothing = + throw Exception( + ConstraintViolationSet(violations.map { it.copy(metadata = toDefaultMetadata(it.metadata)) }.toSet()) + ) - override fun orThrow(): Nothing = throw Exception(violations) override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false - other as Failure + other as Failure return violations == other.violations } override fun hashCode(): Int = violations.hashCode() override fun toString(): String = "ValidationResult.Failure(violations=$violations)" + } /** diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Validator.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Validator.kt index 8c633ea0..6e894b70 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Validator.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/Validator.kt @@ -18,6 +18,8 @@ package dev.nesk.akkurate import dev.nesk.akkurate.constraints.* +import dev.nesk.akkurate.validatables.DefaultMetadataType +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable public sealed interface Validator { @@ -25,53 +27,77 @@ public sealed interface Validator { public operator fun invoke( configuration: Configuration = Configuration(), block: Validatable.(context: ContextType) -> Unit, - ): Runner.WithContext = ValidatorRunner(configuration, block) + ): Runner.WithContext = invoke(emptyMap(), configuration, block) + + public operator fun invoke( + defaultMetadata: MetadataType, + configuration: Configuration = Configuration(), + block: GenericValidatable.(context: ContextType) -> Unit, + ): Runner.WithContext = ValidatorRunner(defaultMetadata, configuration, block) public operator fun invoke( configuration: Configuration = Configuration(), block: Validatable.() -> Unit, - ): Runner = ValidatorRunner.WithoutContext(configuration, block) + ): Runner = invoke(emptyMap(), configuration, block) + + public operator fun invoke( + defaultMetadata: MetadataType, + configuration: Configuration = Configuration(), + block: GenericValidatable.() -> Unit, + ): Runner = ValidatorRunner.WithoutContext(defaultMetadata, configuration, block) public fun suspendable( configuration: Configuration = Configuration(), block: suspend Validatable.(context: ContextType) -> Unit, - ): SuspendableRunner.WithContext = SuspendableValidatorRunner(configuration, block) + ): SuspendableRunner.WithContext = suspendable(emptyMap(), configuration, block) + + public fun suspendable( + defaultMetadata: MetadataType, + configuration: Configuration = Configuration(), + block: suspend GenericValidatable.(context: ContextType) -> Unit, + ): SuspendableRunner.WithContext = SuspendableValidatorRunner(defaultMetadata, configuration, block) public fun suspendable( configuration: Configuration = Configuration(), block: suspend Validatable.() -> Unit, - ): SuspendableRunner = SuspendableValidatorRunner.WithoutContext(configuration, block) + ): SuspendableRunner = suspendable(emptyMap(), configuration, block) + + public fun suspendable( + defaultMetadata: MetadataType, + configuration: Configuration = Configuration(), + block: suspend GenericValidatable.() -> Unit, + ): SuspendableRunner = SuspendableValidatorRunner.WithoutContext(defaultMetadata, configuration, block) } - public sealed interface Runner { - public operator fun invoke(value: ValueType): ValidationResult + public sealed interface Runner { + public operator fun invoke(value: ValueType): ValidationResult - public sealed interface WithContext { - public operator fun invoke(context: ContextType): Runner - public operator fun invoke(context: ContextType, value: ValueType): ValidationResult + public sealed interface WithContext { + public operator fun invoke(context: ContextType): Runner + public operator fun invoke(context: ContextType, value: ValueType): ValidationResult } } - public sealed interface SuspendableRunner { - public suspend operator fun invoke(value: ValueType): ValidationResult + public sealed interface SuspendableRunner { + public suspend operator fun invoke(value: ValueType): ValidationResult - public sealed interface WithContext { - public operator fun invoke(context: ContextType): SuspendableRunner - public suspend operator fun invoke(context: ContextType, value: ValueType): ValidationResult + public sealed interface WithContext { + public operator fun invoke(context: ContextType): SuspendableRunner + public suspend operator fun invoke(context: ContextType, value: ValueType): ValidationResult } } } //region validateWith implementations -public fun Validatable.validateWith( - validator: Validator.Runner.WithContext, +public fun GenericValidatable.validateWith( + validator: Validator.Runner.WithContext, context: ContextType, ) { validateWith(validator(context)) } -public fun Validatable.validateWith(validator: Validator.Runner) { +public fun GenericValidatable.validateWith(validator: Validator.Runner) { val result = validator(unwrap()) if (result is ValidationResult.Failure) { result.violations @@ -80,14 +106,14 @@ public fun Validatable.validateWith(validator: Validator. } } -public suspend fun Validatable.validateWith( - validator: Validator.SuspendableRunner.WithContext, +public suspend fun GenericValidatable.validateWith( + validator: Validator.SuspendableRunner.WithContext, context: ContextType, ) { validateWith(validator(context)) } -public suspend fun Validatable.validateWith(validator: Validator.SuspendableRunner) { +public suspend fun GenericValidatable.validateWith(validator: Validator.SuspendableRunner) { val result = validator(unwrap()) if (result is ValidationResult.Failure) { result.violations @@ -100,69 +126,75 @@ public suspend fun Validatable.validateWith(validator: Va //region Private API -private class ValidatorRunner( +private class ValidatorRunner( + private val defaultMetadata: MetadataType, private val configuration: Configuration, - private val block: Validatable.(context: ContextType) -> Unit, -) : Validator.Runner.WithContext { + private val block: GenericValidatable.(context: ContextType) -> Unit, +) : Validator.Runner.WithContext { - override operator fun invoke(context: ContextType) = Contextualized(configuration, context, block) + override operator fun invoke(context: ContextType) = Contextualized(defaultMetadata, configuration, context, block) - override operator fun invoke(context: ContextType, value: ValueType): ValidationResult = invoke(context)(value) + override operator fun invoke(context: ContextType, value: ValueType): ValidationResult = invoke(context)(value) - class Contextualized( + class Contextualized( + private val defaultMetadata: MetadataType, private val configuration: Configuration, private val context: ContextType, - private val block: Validatable.(context: ContextType) -> Unit, - ) : Validator.Runner { - override operator fun invoke(value: ValueType): ValidationResult = + private val block: GenericValidatable.(context: ContextType) -> Unit, + ) : Validator.Runner { + override operator fun invoke(value: ValueType): ValidationResult = runWithConstraintRegistry(value, configuration) { registry -> val block = this.block - Validatable(value, registry).run { block(context) } + GenericValidatable(value, registry, defaultMetadata).run { block(context) } } } - class WithoutContext( + class WithoutContext( + private val defaultMetadata: MetadataType, private val configuration: Configuration, - private val block: Validatable.() -> Unit, - ) : Validator.Runner { - override operator fun invoke(value: ValueType): ValidationResult = + private val block: GenericValidatable.() -> Unit, + ) : Validator.Runner { + override operator fun invoke(value: ValueType): ValidationResult = runWithConstraintRegistry(value, configuration) { registry -> val block = this.block - Validatable(value, registry).run { block() } + GenericValidatable(value, registry, defaultMetadata).run { block() } } } } -private class SuspendableValidatorRunner( +private class SuspendableValidatorRunner( + private val defaultMetadata: MetadataType, private val configuration: Configuration, - private val block: suspend Validatable.(context: ContextType) -> Unit, -) : Validator.SuspendableRunner.WithContext { + private val block: suspend GenericValidatable.(context: ContextType) -> Unit, +) : Validator.SuspendableRunner.WithContext { - override operator fun invoke(context: ContextType) = Contextualized(configuration, context, block) + override operator fun invoke(context: ContextType) = Contextualized(defaultMetadata, configuration, context, block) - override suspend operator fun invoke(context: ContextType, value: ValueType): ValidationResult = invoke(context)(value) + override suspend operator fun invoke(context: ContextType, value: ValueType): ValidationResult = invoke(context)(value) - class Contextualized( + class Contextualized( + private val defaultMetadata: MetadataType, private val configuration: Configuration, private val context: ContextType, - private val block: suspend Validatable.(context: ContextType) -> Unit, - ) : Validator.SuspendableRunner { - override suspend operator fun invoke(value: ValueType): ValidationResult = + private val block: suspend GenericValidatable.(context: ContextType) -> Unit, + ) : Validator.SuspendableRunner { + override suspend operator fun invoke(value: ValueType): ValidationResult = runWithConstraintRegistry(value, configuration) { registry -> val block = this.block - Validatable(value, registry).run { block(context) } + GenericValidatable(value, registry, defaultMetadata).run { block(context) } } } - class WithoutContext( + class WithoutContext( + private val defaultMetadata: MetadataType, private val configuration: Configuration, - private val block: suspend Validatable.() -> Unit, - ) : Validator.SuspendableRunner { - override suspend operator fun invoke(value: ValueType): ValidationResult = + private val block: suspend GenericValidatable.() -> Unit, + ) : Validator.SuspendableRunner { + override suspend operator fun invoke(value: ValueType): ValidationResult = runWithConstraintRegistry(value, configuration) { registry -> val block = this.block - Validatable(value, registry).run { block() } + GenericValidatable(value, registry, defaultMetadata).run { block() } } } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Array.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Array.kt index 97fbbde5..76e77fee 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Array.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Array.kt @@ -17,10 +17,11 @@ package dev.nesk.akkurate.accessors +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable import dev.nesk.akkurate.validatables.validatableOf -public operator fun Validatable?>.get(index: Int): Validatable { +public operator fun GenericValidatable?, MetadataType>.get(index: Int): GenericValidatable { val wrappedValue = unwrap()?.let { if (index in it.indices) { it[index] @@ -28,17 +29,17 @@ public operator fun Validatable?>.get(index: Int): Validatable null } } - return Validatable(wrappedValue, index.toString(), this) + return GenericValidatable(wrappedValue, index.toString(), this) } -public fun Validatable?>.first(): Validatable = try { +public fun GenericValidatable?, MetadataType>.first(): GenericValidatable = try { validatableOf(Array::first) } catch (e: NoSuchElementException) { - Validatable(null, "first", this) + GenericValidatable(null, "first", this) } -public fun Validatable?>.last(): Validatable = try { +public fun GenericValidatable?, MetadataType>.last(): GenericValidatable = try { validatableOf(Array::last) } catch (e: NoSuchElementException) { - Validatable(null, "last", this) + GenericValidatable(null, "last", this) } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Iterable.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Iterable.kt index d1998652..40cc111a 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Iterable.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Iterable.kt @@ -17,7 +17,7 @@ package dev.nesk.akkurate.accessors -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.validatableOf private val emptyIterator = object : Iterator { @@ -26,40 +26,40 @@ private val emptyIterator = object : Iterator { } /** - * Returns an iterator over the elements of this object. Each element is wrapped with a [Validatable]. + * Returns an iterator over the elements of this object. Each element is wrapped with a [GenericValidatable]. */ -public operator fun Validatable?>.iterator(): Iterator> = unwrap()?.let { iterable -> +public operator fun GenericValidatable?, MetadataType>.iterator(): Iterator> = unwrap()?.let { iterable -> iterable .asSequence() .withIndex() - .map { Validatable(it.value, it.index.toString(), this) } + .map { GenericValidatable(it.value, it.index.toString(), this) } .iterator() } ?: emptyIterator /** - * Returns the first element, wrapped in [Validatable]. + * Returns the first element, wrapped in [GenericValidatable]. * * The wrapped value is `null` if the collection is empty. */ -public fun Validatable?>.first(): Validatable = try { +public fun GenericValidatable?, MetadataType>.first(): GenericValidatable = try { validatableOf(Iterable::first) } catch (e: NoSuchElementException) { - Validatable(null, "first", this) + GenericValidatable(null, "first", this) } /** - * Returns the last element, wrapped in [Validatable]. + * Returns the last element, wrapped in [GenericValidatable]. * * The wrapped value is `null` if the collection is empty. */ -public fun Validatable?>.last(): Validatable = try { +public fun GenericValidatable?, MetadataType>.last(): GenericValidatable = try { validatableOf(Iterable::last) } catch (e: NoSuchElementException) { - Validatable(null, "last", this) + GenericValidatable(null, "last", this) } /** - * Iterates over each element of this object and wraps them with a [Validatable] before passing them to the [block]. + * Iterates over each element of this object and wraps them with a [GenericValidatable] before passing them to the [block]. * * ``` * Validator> { @@ -68,6 +68,6 @@ public fun Validatable?>.last(): Validatable = try { * } * ``` */ -public inline fun Validatable?>.each(block: Validatable.() -> Unit) { +public inline fun GenericValidatable?, MetadataType>.each(block: GenericValidatable.() -> Unit) { for (row in this) row.invoke(block) } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/List.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/List.kt index 02d8c229..b1799fb6 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/List.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/List.kt @@ -17,12 +17,12 @@ package dev.nesk.akkurate.accessors -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable /** - * Returns the element at the specified index in the list, wrapped in a [Validatable]. + * Returns the element at the specified index in the list, wrapped in a [GenericValidatable]. */ -public operator fun Validatable?>.get(index: Int): Validatable { +public operator fun GenericValidatable?, MetadataType>.get(index: Int): GenericValidatable { val wrappedValue = unwrap()?.let { if (index in it.indices) { it[index] @@ -30,5 +30,5 @@ public operator fun Validatable?>.get(index: Int): Validatable { null } } - return Validatable(wrappedValue, index.toString(), this) + return GenericValidatable(wrappedValue, index.toString(), this) } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Map.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Map.kt index a2593c36..133b496b 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Map.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/accessors/Map.kt @@ -17,7 +17,7 @@ package dev.nesk.akkurate.accessors -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable -public operator fun Validatable?>.get(index: K): Validatable = - Validatable(unwrap()?.get(index), index.toString(), this) +public operator fun GenericValidatable?, MetadataType>.get(index: K): GenericValidatable = + GenericValidatable(unwrap()?.get(index), index.toString(), this) diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/Constraint.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/Constraint.kt index 35c8785f..3a31c996 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/Constraint.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/Constraint.kt @@ -19,11 +19,15 @@ package dev.nesk.akkurate.constraints import dev.nesk.akkurate.Path import dev.nesk.akkurate.PathBuilder +import dev.nesk.akkurate.validatables.DefaultMetadataType +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable +public typealias Constraint = GenericConstraint + // This could be a data class in the future if Kotlin adds a feature to remove the `copy()` method when a constructor is internal or private. // https://youtrack.jetbrains.com/issue/KT-11914 -public class Constraint(public val satisfied: Boolean, public var validatable: Validatable<*>) : ConstraintDescriptor { +public class GenericConstraint(public val satisfied: Boolean, public var validatable: GenericValidatable<*, MetadataType>, public override var metadata: MetadataType) : GenericConstraintDescriptor { private var customPath: Path? = null public override var path: Path @@ -36,9 +40,9 @@ public class Constraint(public val satisfied: Boolean, public var validatable: V public operator fun component1(): Boolean = satisfied - internal fun toConstraintViolation(defaultMessage: String, rootPath: Path): ConstraintViolation { + internal fun toConstraintViolation(defaultMessage: String, rootPath: Path): GenericConstraintViolation { require(!satisfied) { "Converting to `ConstrainViolation` can only be done when the constraint is not satisfied." } - return ConstraintViolation(message.ifEmpty { defaultMessage }, rootPath + path) + return GenericConstraintViolation(message.ifEmpty { defaultMessage }, rootPath + path, metadata) } /** @@ -54,7 +58,7 @@ public class Constraint(public val satisfied: Boolean, public var validatable: V if (this === other) return true if (other == null || this::class != other::class) return false - other as Constraint + other as GenericConstraint if (satisfied != other.satisfied) return false if (path != other.path) return false @@ -81,28 +85,38 @@ public class Constraint(public val satisfied: Boolean, public var validatable: V } override fun toString(): String { - return "Constraint(satisfied=$satisfied, validatable=$validatable, path=$customPath, message=$message)" + return "Constraint(satisfied=$satisfied, validatable=$validatable, path=$customPath, metadata=$metadata, message=$message)" + } + + public companion object { + public operator fun invoke(satisfied: Boolean, validatable: Validatable<*>): GenericConstraint { + return GenericConstraint(satisfied, validatable, emptyMap()) + } } } -public inline infix fun Constraint.otherwise(block: () -> String): Constraint = apply { +public inline infix fun GenericConstraint.otherwise(block: () -> String): GenericConstraint = apply { if (!satisfied) message = block() } -public inline infix fun Constraint.withPath(block: PathBuilder.(originalPath: Path) -> Path): Constraint { +public inline infix fun GenericConstraint.withPath(block: PathBuilder.(originalPath: Path) -> Path): GenericConstraint { if (!satisfied) { path = PathBuilder(validatable).block(validatable.path()) } return this } -public inline fun Validatable.constrain(block: (value: T) -> Boolean): Constraint { +public inline infix fun GenericConstraint.withMetadata(block: () -> MetadataType): GenericConstraint { + return GenericConstraint(satisfied, validatable, block()) +} + +public inline fun GenericValidatable.constrain(block: (value: T) -> Boolean): GenericConstraint { runChecksBeforeConstraintRegistration() - return Constraint(block(unwrap()), this) + return GenericConstraint(block(unwrap()), this, defaultMetadata) .also(::registerConstraint) } -public inline fun Validatable.constrainIfNotNull(block: (value: T) -> Boolean): Constraint { +public inline fun GenericValidatable.constrainIfNotNull(block: (value: T) -> Boolean): GenericConstraint { val constraint = unwrap() ?.let { value -> constrain { block(value) } } ?: constrain { true } // We do not want the constraint to fail when the value is null. diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintDescriptor.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintDescriptor.kt index 7284a9a8..79b1aab8 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintDescriptor.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintDescriptor.kt @@ -18,8 +18,12 @@ package dev.nesk.akkurate.constraints import dev.nesk.akkurate.Path +import dev.nesk.akkurate.validatables.DefaultMetadataType -public sealed interface ConstraintDescriptor { +public typealias ConstraintDescriptor = GenericConstraintDescriptor + +public sealed interface GenericConstraintDescriptor { public val message: String public val path: Path + public val metadata: MetadataType } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistry.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistry.kt index 2b2a3283..8dbe6e00 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistry.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistry.kt @@ -19,15 +19,19 @@ package dev.nesk.akkurate.constraints import dev.nesk.akkurate.Configuration import dev.nesk.akkurate.ValidationResult +import dev.nesk.akkurate.validatables.DefaultMetadataType import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.jvm.JvmName + +internal typealias ConstraintRegistry = GenericConstraintRegistry /** * A storage for all the unsatisfied constraints encountered during a validation. */ -internal class ConstraintRegistry(private val configuration: Configuration) { - private val constraints = mutableSetOf() +internal class GenericConstraintRegistry private constructor(private val configuration: Configuration) { + private val constraints = mutableSetOf>() /** * Runs all the checks for [Configuration.failOnFirstViolation]. Should be called between each constraint registration. @@ -44,41 +48,48 @@ internal class ConstraintRegistry(private val configuration: Configuration) { /** * Registers the provided constraint if it's unsatisfied. */ - fun register(constraint: Constraint) { + fun register(constraint: GenericConstraint) { if (!constraint.satisfied) constraints += constraint } /** * Registers the provided constraint violation. */ - fun register(constraint: ConstraintViolation) { + fun register(constraint: GenericConstraintViolation) { constraints += constraint } fun toSet() = constraints.toSet() + + internal companion object { + internal operator fun invoke(configuration: Configuration): ConstraintRegistry = ConstraintRegistry(configuration) + + @JvmName("genericInvoke") + internal operator fun invoke(configuration: Configuration): GenericConstraintRegistry = GenericConstraintRegistry(configuration) + } } /** - * Runs the provided [block] with a new [ConstraintRegistry] provided as a parameter. + * Runs the provided [block] with a new [GenericConstraintRegistry] provided as a parameter. * * @param value The value to validate, will be used if the validation is successful. - * @return A [ValidationResult] based on the registered constraints in the [ConstraintRegistry]. + * @return A [ValidationResult] based on the registered constraints in the [GenericConstraintRegistry]. */ -internal inline fun runWithConstraintRegistry( - value: T, configuration: Configuration, block: (ConstraintRegistry) -> Unit, -) = ConstraintRegistry(configuration) - .alsoCatching(block) +internal inline fun runWithConstraintRegistry( + value: T, configuration: Configuration, block: (GenericConstraintRegistry) -> Unit, +) = GenericConstraintRegistry(configuration) + .alsoCatching, FirstViolationException>(block) .map { it.toSet() } - .recover { setOf((it as FirstViolationException).violation) } + .recover { setOf((it as FirstViolationException).violation as GenericConstraintViolation) } .getOrThrow() .takeIf { it.isNotEmpty() } ?.let { ValidationResult.Failure(it.toViolationSet(configuration)) } ?: ValidationResult.Success(value) -private fun Iterable.toViolationSet(configuration: Configuration) = +private fun Iterable>.toViolationSet(configuration: Configuration) = map { it.toConstraintViolation(configuration) } .toSet() - .let(::ConstraintViolationSet) + .let(::GenericConstraintViolationSet) /** * Calls the specified function [block] with `this` value as its argument and returns a result. @@ -103,12 +114,12 @@ private inline fun T.alsoCatching(block: (T) -> Unit) } // TODO: Benchmark `is` usage in all runtimes, compared to the visitor pattern. -private fun ConstraintDescriptor.toConstraintViolation(configuration: Configuration) = when (this) { - is Constraint -> toConstraintViolation(configuration.defaultViolationMessage, configuration.rootPath) - is ConstraintViolation -> this +private fun GenericConstraintDescriptor.toConstraintViolation(configuration: Configuration): GenericConstraintViolation = when (this) { + is GenericConstraint -> toConstraintViolation(configuration.defaultViolationMessage, configuration.rootPath) + is GenericConstraintViolation -> this } /** * An internal exception used in conjunction with [Configuration.failOnFirstViolation]. */ -internal class FirstViolationException(val violation: ConstraintViolation) : RuntimeException() +internal class FirstViolationException(val violation: GenericConstraintViolation<*>) : RuntimeException() diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolation.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolation.kt index 8e9e3b14..6add1203 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolation.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolation.kt @@ -18,20 +18,29 @@ package dev.nesk.akkurate.constraints import dev.nesk.akkurate.Path +import dev.nesk.akkurate.validatables.DefaultMetadataType -public class ConstraintViolation(public override val message: String, public override val path: Path) : ConstraintDescriptor { +public typealias ConstraintViolation = GenericConstraintViolation + +public class GenericConstraintViolation( + public override val message: String, + public override val path: Path, + override val metadata: MetadataType +) : GenericConstraintDescriptor { public operator fun component1(): String = message public operator fun component2(): Path = path - internal fun copy(path: Path = this.path) = ConstraintViolation(message, path) + internal fun copy(path: Path = this.path): GenericConstraintViolation = GenericConstraintViolation(message, path, metadata) + internal fun copy(metadata: NEW_META): GenericConstraintViolation = GenericConstraintViolation(message, path, metadata) override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false - other as ConstraintViolation + other as GenericConstraintViolation if (message != other.message) return false + if (metadata != other.metadata) return false return path == other.path } @@ -41,5 +50,10 @@ public class ConstraintViolation(public override val message: String, public ove return result } - override fun toString(): String = "ConstraintViolation(message='$message', path=$path)" + override fun toString(): String = "ConstraintViolation(message='$message', path=$path, metadata=$metadata)" + + public companion object { + public operator fun invoke(message: String, path: Path): ConstraintViolation = + GenericConstraintViolation(message, path, emptyMap()) + } } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolationSet.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolationSet.kt index d68e8939..aeed2de0 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolationSet.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/ConstraintViolationSet.kt @@ -18,9 +18,12 @@ package dev.nesk.akkurate.constraints import dev.nesk.akkurate.Path +import dev.nesk.akkurate.validatables.DefaultMetadataType -public class ConstraintViolationSet(private val messages: Set) : Set by messages { - public val byPath: Map> by lazy { messages.groupBy { it.path }.mapValues { it.value.toSet() } } +public typealias ConstraintViolationSet = GenericConstraintViolationSet + +public class GenericConstraintViolationSet(private val messages: Set>) : Set> by messages { + public val byPath: Map>> by lazy { messages.groupBy { it.path }.mapValues { it.value.toSet() } } override fun equals(other: Any?): Boolean = messages == other diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Any.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Any.kt index 2f7b3b34..a0303927 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Any.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Any.kt @@ -18,9 +18,13 @@ package dev.nesk.akkurate.constraints.builders import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrain import dev.nesk.akkurate.constraints.otherwise +import dev.nesk.akkurate.validatables.DefaultMetadataType +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable +import kotlin.jvm.JvmName /** * The validatable value must be null when this constraint is applied. @@ -33,7 +37,7 @@ import dev.nesk.akkurate.validatables.Validatable * validate(1234) // Failure (message: Must be null) * ``` */ -public fun Validatable.isNull(): Constraint = constrain { it == null } otherwise { "Must be null" } +public fun GenericValidatable.isNull(): GenericConstraint = constrain { it == null } otherwise { "Must be null" } /** * The validatable value must not be null when this constraint is applied. @@ -46,7 +50,7 @@ public fun Validatable.isNull(): Constraint = constrain { it == null } o * validate(null) // Failure (message: Must not be null) * ``` */ -public fun Validatable.isNotNull(): Constraint = constrain { it != null } otherwise { "Must not be null" } +public fun GenericValidatable.isNotNull(): GenericConstraint = constrain { it != null } otherwise { "Must not be null" } /** * The validatable value must be equal to [other] when this constraint is applied. @@ -59,7 +63,7 @@ public fun Validatable.isNotNull(): Constraint = constrain { it != null * validate("bar") // Failure (message: Must be equal to "foo") * ``` */ -public fun Validatable.isEqualTo(other: T?): Constraint = constrain { it == other } otherwise { "Must be equal to \"$other\"" } +public fun GenericValidatable.isEqualTo(other: T?): GenericConstraint = constrain { it == other } otherwise { "Must be equal to \"$other\"" } /** * The validatable value must be equal to [other] when this constraint is applied. @@ -72,7 +76,7 @@ public fun Validatable.isEqualTo(other: T?): Constraint = constrain { it * validate("foo" to "bar") // Failure (message: Must be equal to "bar") * ``` */ -public fun Validatable.isEqualTo(other: Validatable): Constraint = constrain { it == other.unwrap() } otherwise { "Must be equal to \"${other.unwrap()}\"" } +public fun GenericValidatable.isEqualTo(other: Validatable): GenericConstraint = constrain { it == other.unwrap() } otherwise { "Must be equal to \"${other.unwrap()}\"" } /** * The validatable value must be different from [other] when this constraint is applied. @@ -85,7 +89,7 @@ public fun Validatable.isEqualTo(other: Validatable): Constraint = co * validate("foo") // Failure (message: Must be different from "foo") * ``` */ -public fun Validatable.isNotEqualTo(other: T?): Constraint = constrain { it != other } otherwise { "Must be different from \"$other\"" } +public fun GenericValidatable.isNotEqualTo(other: T?): GenericConstraint = constrain { it != other } otherwise { "Must be different from \"$other\"" } /** * The validatable value must be different from [other] when this constraint is applied. @@ -98,7 +102,7 @@ public fun Validatable.isNotEqualTo(other: T?): Constraint = constrain { * validate("foo" to "foo") // Failure (message: Must be different from "foo") * ``` */ -public fun Validatable.isNotEqualTo(other: Validatable): Constraint = constrain { it != other.unwrap() } otherwise { "Must be different from \"${other.unwrap()}\"" } +public fun GenericValidatable.isNotEqualTo(other: Validatable): GenericConstraint = constrain { it != other.unwrap() } otherwise { "Must be different from \"${other.unwrap()}\"" } /** * The validatable value must be identical to [other] when this constraint is applied. @@ -116,7 +120,7 @@ public fun Validatable.isNotEqualTo(other: Validatable): Constraint = * validate(Bar) // Failure (message: Must be identical to "Foo") * ``` */ -public fun Validatable.isIdenticalTo(other: T?): Constraint = constrain { it === other } otherwise { "Must be identical to \"$other\"" } +public fun GenericValidatable.isIdenticalTo(other: T?): GenericConstraint = constrain { it === other } otherwise { "Must be identical to \"$other\"" } /** * The validatable value must not be identical to [other] when this constraint is applied. @@ -134,7 +138,7 @@ public fun Validatable.isIdenticalTo(other: T?): Constraint = constrain * validate(Foo) // Failure (message: Must not be identical to "Foo") * ``` */ -public fun Validatable.isNotIdenticalTo(other: T?): Constraint = constrain { it !== other } otherwise { "Must not be identical to \"$other\"" } +public fun GenericValidatable.isNotIdenticalTo(other: T?): GenericConstraint = constrain { it !== other } otherwise { "Must not be identical to \"$other\"" } /** * The validatable value must be an instance of type parameter R when this constraint is applied. @@ -148,6 +152,10 @@ public fun Validatable.isNotIdenticalTo(other: T?): Constraint = constra * ``` */ public inline fun Validatable<*>.isInstanceOf(): Constraint = + this.isInstanceOf() + +@JvmName("genericIsInstanceOf") +public inline fun GenericValidatable<*, MetadataType>.isInstanceOf(): GenericConstraint = constrain { it is T } otherwise { "Must be an instance of \"${T::class.simpleName}\"" } /** @@ -162,4 +170,8 @@ public inline fun Validatable<*>.isInstanceOf(): Constraint = * ``` */ public inline fun Validatable<*>.isNotInstanceOf(): Constraint = + this.isNotInstanceOf() + +@JvmName("genericIsNotInstanceOf") +public inline fun GenericValidatable<*, MetadataType>.isNotInstanceOf(): GenericConstraint = constrain { it !is T } otherwise { "Must not be an instance of \"${T::class.simpleName}\"" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Array.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Array.kt index 85befa28..4c07d5e9 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Array.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Array.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.jvm.JvmName /* @@ -44,7 +44,7 @@ import kotlin.jvm.JvmName * ``` */ @JvmName("arrayIsEmpty") -public fun Validatable?>.isEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isEmpty(): GenericConstraint = constrainIfNotNull { it.isEmpty() } otherwise { "Must be empty" } /** @@ -59,7 +59,7 @@ public fun Validatable?>.isEmpty(): Constraint = * ``` */ @JvmName("arrayIsNotEmpty") -public fun Validatable?>.isNotEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isNotEmpty(): GenericConstraint = constrainIfNotNull { it.isNotEmpty() } otherwise { "Must not be empty" } /** @@ -74,7 +74,7 @@ public fun Validatable?>.isNotEmpty(): Constraint = * ``` */ @JvmName("arrayHasSizeEqualTo") -public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size == size } otherwise { "The number of items must be equal to $size" } /** @@ -89,7 +89,7 @@ public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint * ``` */ @JvmName("arrayHasSizeNotEqualTo") -public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeNotEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size != size } otherwise { "The number of items must be different from $size" } /** @@ -104,7 +104,7 @@ public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constrai * ``` */ @JvmName("arrayHasSizeLowerThan") -public fun Validatable?>.hasSizeLowerThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThan(size: Int): GenericConstraint = constrainIfNotNull { it.size < size } otherwise { "The number of items must be lower than $size" } /** @@ -120,7 +120,7 @@ public fun Validatable?>.hasSizeLowerThan(size: Int): Constrain * ``` */ @JvmName("arrayHasSizeLowerThanOrEqualTo") -public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size <= size } otherwise { "The number of items must be lower than or equal to $size" } /** @@ -135,7 +135,7 @@ public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): * ``` */ @JvmName("arrayHasSizeGreaterThan") -public fun Validatable?>.hasSizeGreaterThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThan(size: Int): GenericConstraint = constrainIfNotNull { it.size > size } otherwise { "The number of items must be greater than $size" } /** @@ -151,7 +151,7 @@ public fun Validatable?>.hasSizeGreaterThan(size: Int): Constra * ``` */ @JvmName("arrayHasSizeGreaterThanOrEqualTo") -public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size >= size } otherwise { "The number of items must be greater than or equal to $size" } /** @@ -168,7 +168,7 @@ public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int) * ``` */ @JvmName("arrayHasSizeBetween") -public fun Validatable?>.hasSizeBetween(range: IntRange): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it.size in range } otherwise { "The number of items must be between ${range.first} and ${range.last}" } /** @@ -183,7 +183,7 @@ public fun Validatable?>.hasSizeBetween(range: IntRange): Const * ``` */ @JvmName("arrayIsContaining") -public fun Validatable?>.isContaining(element: T): Constraint = +public fun GenericValidatable?, MetadataType>.isContaining(element: T): GenericConstraint = constrainIfNotNull { it.contains(element) } otherwise { "Must contain \"$element\"" } /** @@ -198,7 +198,7 @@ public fun Validatable?>.isContaining(element: T): Constraint = * ``` */ @JvmName("arrayIsNotContaining") -public fun Validatable?>.isNotContaining(element: T): Constraint = +public fun GenericValidatable?, MetadataType>.isNotContaining(element: T): GenericConstraint = constrainIfNotNull { !it.contains(element) } otherwise { "Must not contain \"$element\"" } /** @@ -212,5 +212,5 @@ public fun Validatable?>.isNotContaining(element: T): Constrain * validate(arrayOf('a', 'b', 'c', 'a')) // Failure (message: Must contain unique elements) * ``` */ -public fun Validatable?>.hasNoDuplicates(): Constraint = +public fun GenericValidatable?, MetadataType>.hasNoDuplicates(): GenericConstraint = constrainIfNotNull { array -> array.toSet().size == array.size } otherwise { "Must contain unique elements" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Boolean.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Boolean.kt index b436f99c..63971c01 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Boolean.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Boolean.kt @@ -17,13 +17,19 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrain import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable -public fun Validatable.isTrue(): Constraint = constrain { it == true } otherwise { "Must be true" } -public fun Validatable.isNotTrue(): Constraint = constrain { it != true } otherwise { "Must not be true" } +public fun GenericValidatable.isTrue(): GenericConstraint = + constrain { it == true } otherwise { "Must be true" } -public fun Validatable.isFalse(): Constraint = constrain { it == false } otherwise { "Must be false" } -public fun Validatable.isNotFalse(): Constraint = constrain { it != false } otherwise { "Must not be false" } +public fun GenericValidatable.isNotTrue(): GenericConstraint = + constrain { it != true } otherwise { "Must not be true" } + +public fun GenericValidatable.isFalse(): GenericConstraint = + constrain { it == false } otherwise { "Must be false" } + +public fun GenericValidatable.isNotFalse(): GenericConstraint = + constrain { it != false } otherwise { "Must not be false" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/CharSequence.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/CharSequence.kt index 17ed6b36..c0799511 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/CharSequence.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/CharSequence.kt @@ -17,64 +17,64 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable -public fun Validatable.isEmpty(): Constraint = +public fun GenericValidatable.isEmpty(): GenericConstraint = constrainIfNotNull { it.isEmpty() } otherwise { "Must be empty" } -public fun Validatable.isNotEmpty(): Constraint = +public fun GenericValidatable.isNotEmpty(): GenericConstraint = constrainIfNotNull { it.isNotEmpty() } otherwise { "Must not be empty" } -public fun Validatable.isBlank(): Constraint = +public fun GenericValidatable.isBlank(): GenericConstraint = constrainIfNotNull { it.isBlank() } otherwise { "Must be blank" } -public fun Validatable.isNotBlank(): Constraint = +public fun GenericValidatable.isNotBlank(): GenericConstraint = constrainIfNotNull { it.isNotBlank() } otherwise { "Must not be blank" } -public fun Validatable.hasLengthEqualTo(length: Int): Constraint = +public fun GenericValidatable.hasLengthEqualTo(length: Int): GenericConstraint = constrainIfNotNull { it.length == length } otherwise { "Length must be equal to $length" } -public fun Validatable.hasLengthNotEqualTo(length: Int): Constraint = +public fun GenericValidatable.hasLengthNotEqualTo(length: Int): GenericConstraint = constrainIfNotNull { it.length != length } otherwise { "Length must be different from $length" } -public fun Validatable.hasLengthLowerThan(length: Int): Constraint = +public fun GenericValidatable.hasLengthLowerThan(length: Int): GenericConstraint = constrainIfNotNull { it.length < length } otherwise { "Length must be lower than $length" } -public fun Validatable.hasLengthLowerThanOrEqualTo(length: Int): Constraint = +public fun GenericValidatable.hasLengthLowerThanOrEqualTo(length: Int): GenericConstraint = constrainIfNotNull { it.length <= length } otherwise { "Length must be lower than or equal to $length" } -public fun Validatable.hasLengthGreaterThan(length: Int): Constraint = +public fun GenericValidatable.hasLengthGreaterThan(length: Int): GenericConstraint = constrainIfNotNull { it.length > length } otherwise { "Length must be greater than $length" } -public fun Validatable.hasLengthGreaterThanOrEqualTo(length: Int): Constraint = +public fun GenericValidatable.hasLengthGreaterThanOrEqualTo(length: Int): GenericConstraint = constrainIfNotNull { it.length >= length } otherwise { "Length must be greater than or equal to $length" } -public fun Validatable.hasLengthBetween(range: IntRange): Constraint = +public fun GenericValidatable.hasLengthBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it.length in range } otherwise { "Length must be between ${range.first} and ${range.last}" } -public fun Validatable.isMatching(regex: Regex): Constraint = +public fun GenericValidatable.isMatching(regex: Regex): GenericConstraint = constrainIfNotNull { regex.matches(it) } otherwise { "Must match the following pattern: ${regex.pattern}" } -public fun Validatable.isNotMatching(regex: Regex): Constraint = +public fun GenericValidatable.isNotMatching(regex: Regex): GenericConstraint = constrainIfNotNull { !regex.matches(it) } otherwise { "Must not match the following pattern: ${regex.pattern}" } -public fun Validatable.isStartingWith(prefix: CharSequence): Constraint = +public fun GenericValidatable.isStartingWith(prefix: CharSequence): GenericConstraint = constrainIfNotNull { it.startsWith(prefix) } otherwise { "Must start with \"$prefix\"" } -public fun Validatable.isNotStartingWith(prefix: CharSequence): Constraint = +public fun GenericValidatable.isNotStartingWith(prefix: CharSequence): GenericConstraint = constrainIfNotNull { !it.startsWith(prefix) } otherwise { "Must not start with \"$prefix\"" } -public fun Validatable.isEndingWith(suffix: CharSequence): Constraint = +public fun GenericValidatable.isEndingWith(suffix: CharSequence): GenericConstraint = constrainIfNotNull { it.endsWith(suffix) } otherwise { "Must end with \"$suffix\"" } -public fun Validatable.isNotEndingWith(suffix: CharSequence): Constraint = +public fun GenericValidatable.isNotEndingWith(suffix: CharSequence): GenericConstraint = constrainIfNotNull { !it.endsWith(suffix) } otherwise { "Must not end with \"$suffix\"" } -public fun Validatable.isContaining(other: CharSequence): Constraint = +public fun GenericValidatable.isContaining(other: CharSequence): GenericConstraint = constrainIfNotNull { it.contains(other) } otherwise { "Must contain \"$other\"" } -public fun Validatable.isNotContaining(other: CharSequence): Constraint = +public fun GenericValidatable.isNotContaining(other: CharSequence): GenericConstraint = constrainIfNotNull { !it.contains(other) } otherwise { "Must not contain \"$other\"" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Collection.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Collection.kt index 33cce5f4..5e0eb120 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Collection.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Collection.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.jvm.JvmName /* @@ -44,7 +44,7 @@ import kotlin.jvm.JvmName * ``` */ @JvmName("collectionIsEmpty") -public fun Validatable?>.isEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isEmpty(): GenericConstraint = constrainIfNotNull { it.isEmpty() } otherwise { "Must be empty" } /** @@ -59,7 +59,7 @@ public fun Validatable?>.isEmpty(): Constraint = * ``` */ @JvmName("collectionIsNotEmpty") -public fun Validatable?>.isNotEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isNotEmpty(): GenericConstraint = constrainIfNotNull { it.isNotEmpty() } otherwise { "Must not be empty" } /** @@ -74,7 +74,7 @@ public fun Validatable?>.isNotEmpty(): Constraint = * ``` */ @JvmName("collectionHasSizeEqualTo") -public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size == size } otherwise { "The number of items must be equal to $size" } /** @@ -89,7 +89,7 @@ public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint * ``` */ @JvmName("collectionHasSizeNotEqualTo") -public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeNotEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size != size } otherwise { "The number of items must be different from $size" } /** @@ -104,7 +104,7 @@ public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constra * ``` */ @JvmName("collectionHasSizeLowerThan") -public fun Validatable?>.hasSizeLowerThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThan(size: Int): GenericConstraint = constrainIfNotNull { it.size < size } otherwise { "The number of items must be lower than $size" } /** @@ -120,7 +120,7 @@ public fun Validatable?>.hasSizeLowerThan(size: Int): Constrai * ``` */ @JvmName("collectionHasSizeLowerThanOrEqualTo") -public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size <= size } otherwise { "The number of items must be lower than or equal to $size" } /** @@ -135,7 +135,7 @@ public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): * ``` */ @JvmName("collectionHasSizeGreaterThan") -public fun Validatable?>.hasSizeGreaterThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThan(size: Int): GenericConstraint = constrainIfNotNull { it.size > size } otherwise { "The number of items must be greater than $size" } /** @@ -151,7 +151,7 @@ public fun Validatable?>.hasSizeGreaterThan(size: Int): Constr * ``` */ @JvmName("collectionHasSizeGreaterThanOrEqualTo") -public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size >= size } otherwise { "The number of items must be greater than or equal to $size" } /** @@ -168,5 +168,5 @@ public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int * ``` */ @JvmName("collectionHasSizeBetween") -public fun Validatable?>.hasSizeBetween(range: IntRange): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it.size in range } otherwise { "The number of items must be between ${range.first} and ${range.last}" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Iterable.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Iterable.kt index fa14f9c2..29569965 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Iterable.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Iterable.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.jvm.JvmName /* @@ -44,7 +44,7 @@ import kotlin.jvm.JvmName * ``` */ @JvmName("iterableIsContaining") -public fun Validatable?>.isContaining(element: T): Constraint = +public fun GenericValidatable?, MetadataType>.isContaining(element: T): GenericConstraint = constrainIfNotNull { it.contains(element) } otherwise { "Must contain \"$element\"" } /** @@ -59,7 +59,7 @@ public fun Validatable?>.isContaining(element: T): Constraint = * ``` */ @JvmName("iterableIsNotContaining") -public fun Validatable?>.isNotContaining(element: T): Constraint = +public fun GenericValidatable?, MetadataType>.isNotContaining(element: T): GenericConstraint = constrainIfNotNull { !it.contains(element) } otherwise { "Must not contain \"$element\"" } /** @@ -73,5 +73,5 @@ public fun Validatable?>.isNotContaining(element: T): Constraint * validate(listOf('a', 'b', 'c', 'a')) // Failure (message: Must contain unique elements) * ``` */ -public fun Validatable?>.hasNoDuplicates(): Constraint = +public fun GenericValidatable?, MetadataType>.hasNoDuplicates(): GenericConstraint = constrainIfNotNull { array -> array.toSet().size == array.count() } otherwise { "Must contain unique elements" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Map.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Map.kt index 1ac74c6b..cc946447 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Map.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Map.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.jvm.JvmName /* @@ -44,7 +44,7 @@ import kotlin.jvm.JvmName * ``` */ @JvmName("mapIsEmpty") -public fun Validatable?>.isEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isEmpty(): GenericConstraint = constrainIfNotNull { it.isEmpty() } otherwise { "Must be empty" } /** @@ -59,7 +59,7 @@ public fun Validatable?>.isEmpty(): Constraint = * ``` */ @JvmName("mapIsNotEmpty") -public fun Validatable?>.isNotEmpty(): Constraint = +public fun GenericValidatable?, MetadataType>.isNotEmpty(): GenericConstraint = constrainIfNotNull { it.isNotEmpty() } otherwise { "Must not be empty" } /** @@ -74,7 +74,7 @@ public fun Validatable?>.isNotEmpty(): Constraint = * ``` */ @JvmName("mapHasSizeEqualTo") -public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size == size } otherwise { "The number of items must be equal to $size" } /** @@ -89,7 +89,7 @@ public fun Validatable?>.hasSizeEqualTo(size: Int): Constraint = * ``` */ @JvmName("mapHasSizeNotEqualTo") -public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeNotEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size != size } otherwise { "The number of items must be different from $size" } /** @@ -104,7 +104,7 @@ public fun Validatable?>.hasSizeNotEqualTo(size: Int): Constraint * ``` */ @JvmName("mapHasSizeLowerThan") -public fun Validatable?>.hasSizeLowerThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThan(size: Int): GenericConstraint = constrainIfNotNull { it.size < size } otherwise { "The number of items must be lower than $size" } /** @@ -120,7 +120,7 @@ public fun Validatable?>.hasSizeLowerThan(size: Int): Constraint = * ``` */ @JvmName("mapHasSizeLowerThanOrEqualTo") -public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeLowerThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size <= size } otherwise { "The number of items must be lower than or equal to $size" } /** @@ -135,7 +135,7 @@ public fun Validatable?>.hasSizeLowerThanOrEqualTo(size: Int): Con * ``` */ @JvmName("mapHasSizeGreaterThan") -public fun Validatable?>.hasSizeGreaterThan(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThan(size: Int): GenericConstraint = constrainIfNotNull { it.size > size } otherwise { "The number of items must be greater than $size" } /** @@ -151,7 +151,7 @@ public fun Validatable?>.hasSizeGreaterThan(size: Int): Constraint * ``` */ @JvmName("mapHasSizeGreaterThanOrEqualTo") -public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeGreaterThanOrEqualTo(size: Int): GenericConstraint = constrainIfNotNull { it.size >= size } otherwise { "The number of items must be greater than or equal to $size" } /** @@ -168,7 +168,7 @@ public fun Validatable?>.hasSizeGreaterThanOrEqualTo(size: Int): C * ``` */ @JvmName("mapHasSizeBetween") -public fun Validatable?>.hasSizeBetween(range: IntRange): Constraint = +public fun GenericValidatable?, MetadataType>.hasSizeBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it.size in range } otherwise { "The number of items must be between ${range.first} and ${range.last}" } /** @@ -182,7 +182,7 @@ public fun Validatable?>.hasSizeBetween(range: IntRange): Constrai * validate(emptyMap()) // Failure (message: Must contain key "b") * ``` */ -public fun Validatable?>.isContainingKey(key: K): Constraint = +public fun GenericValidatable?, MetadataType>.isContainingKey(key: K): GenericConstraint = constrainIfNotNull { it.containsKey(key) } otherwise { "Must contain key \"$key\"" } /** @@ -196,7 +196,7 @@ public fun Validatable?>.isContainingKey(key: K): Constraint = * validate(mapOf('a' to 1, 'b' to 2, 'c' to 3)) // Failure (message: Must not contain key "b") * ``` */ -public fun Validatable?>.isNotContainingKey(key: K): Constraint = +public fun GenericValidatable?, MetadataType>.isNotContainingKey(key: K): GenericConstraint = constrainIfNotNull { !it.containsKey(key) } otherwise { "Must not contain key \"$key\"" } /** @@ -210,7 +210,7 @@ public fun Validatable?>.isNotContainingKey(key: K): Constrain * validate(emptyMap()) // Failure (message: Must contain value "2") * ``` */ -public fun Validatable?>.isContainingValue(value: V): Constraint = +public fun GenericValidatable?, MetadataType>.isContainingValue(value: V): GenericConstraint = constrainIfNotNull { it.containsValue(value) } otherwise { "Must contain value \"$value\"" } /** @@ -224,5 +224,5 @@ public fun Validatable?>.isContainingValue(value: V): Constraint = * validate(mapOf('a' to 1, 'b' to 2, 'c' to 3)) // Failure (message: Must not contain value "2") * ``` */ -public fun Validatable?>.isNotContainingValue(value: V): Constraint = +public fun GenericValidatable?, MetadataType>.isNotContainingValue(value: V): GenericConstraint = constrainIfNotNull { !it.containsValue(value) } otherwise { "Must not contain value \"$value\"" } diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Number.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Number.kt index bd8b4051..4deacfb8 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Number.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Number.kt @@ -17,39 +17,39 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.jvm.JvmName //region isNotNaN @JvmName("floatIsNotNaN") -public fun Validatable.isNotNaN(): Constraint = +public fun GenericValidatable.isNotNaN(): GenericConstraint = constrainIfNotNull { !it.isNaN() } otherwise { "Must be a valid number" } @JvmName("doubleIsNotNaN") -public fun Validatable.isNotNaN(): Constraint = +public fun GenericValidatable.isNotNaN(): GenericConstraint = constrainIfNotNull { !it.isNaN() } otherwise { "Must be a valid number" } //endregion //region isFinite @JvmName("floatIsFinite") -public fun Validatable.isFinite(): Constraint = +public fun GenericValidatable.isFinite(): GenericConstraint = constrainIfNotNull { it.isFinite() } otherwise { "Must be finite" } @JvmName("doubleIsFinite") -public fun Validatable.isFinite(): Constraint = +public fun GenericValidatable.isFinite(): GenericConstraint = constrainIfNotNull { it.isFinite() } otherwise { "Must be finite" } //endregion //region isInfinite @JvmName("floatIsInfinite") -public fun Validatable.isInfinite(): Constraint = +public fun GenericValidatable.isInfinite(): GenericConstraint = constrainIfNotNull { it.isInfinite() } otherwise { "Must be infinite" } @JvmName("doubleIsInfinite") -public fun Validatable.isInfinite(): Constraint = +public fun GenericValidatable.isInfinite(): GenericConstraint = constrainIfNotNull { it.isInfinite() } otherwise { "Must be infinite" } //endregion @@ -57,23 +57,23 @@ public fun Validatable.isInfinite(): Constraint = private const val negativeMessage = "Must be negative" @JvmName("shortIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it < 0 } otherwise { negativeMessage } @JvmName("intIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it < 0 } otherwise { negativeMessage } @JvmName("longIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it < 0 } otherwise { negativeMessage } @JvmName("floatIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it < 0 } otherwise { negativeMessage } @JvmName("doubleIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it < 0 } otherwise { negativeMessage } //endregion @@ -81,23 +81,23 @@ public fun Validatable.isNegative(): Constraint = private const val negativeOrZeroMessage = "Must be negative or equal to zero" @JvmName("shortIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it <= 0 } otherwise { negativeOrZeroMessage } @JvmName("intIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it <= 0 } otherwise { negativeOrZeroMessage } @JvmName("longIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it <= 0 } otherwise { negativeOrZeroMessage } @JvmName("floatIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it <= 0 } otherwise { negativeOrZeroMessage } @JvmName("doubleIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it <= 0 } otherwise { negativeOrZeroMessage } //endregion @@ -105,23 +105,23 @@ public fun Validatable.isNegativeOrZero(): Constraint = private const val positiveMessage = "Must be positive" @JvmName("shortIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { it > 0 } otherwise { positiveMessage } @JvmName("intIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { it > 0 } otherwise { positiveMessage } @JvmName("longIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { it > 0 } otherwise { positiveMessage } @JvmName("floatIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { it > 0 } otherwise { positiveMessage } @JvmName("doubleIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { it > 0 } otherwise { positiveMessage } //endregion @@ -129,129 +129,129 @@ public fun Validatable.isPositive(): Constraint = private const val positiveOrZeroMessage = "Must be positive or equal to zero" @JvmName("shortIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { it >= 0 } otherwise { positiveOrZeroMessage } @JvmName("intIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { it >= 0 } otherwise { positiveOrZeroMessage } @JvmName("longIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { it >= 0 } otherwise { positiveOrZeroMessage } @JvmName("floatIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { it >= 0 } otherwise { positiveOrZeroMessage } @JvmName("doubleIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { it >= 0 } otherwise { positiveOrZeroMessage } //endregion //region isLowerThan -private infix fun Constraint.otherwiseLowerThan(value: Number) = otherwise { "Must be lower than $value" } +private infix fun GenericConstraint.otherwiseLowerThan(value: Number) = otherwise { "Must be lower than $value" } -public fun Validatable.isLowerThan(value: Short): Constraint = +public fun GenericValidatable.isLowerThan(value: Short): GenericConstraint = constrainIfNotNull { it < value } otherwiseLowerThan value -public fun Validatable.isLowerThan(value: Int): Constraint = +public fun GenericValidatable.isLowerThan(value: Int): GenericConstraint = constrainIfNotNull { it < value } otherwiseLowerThan value -public fun Validatable.isLowerThan(value: Long): Constraint = +public fun GenericValidatable.isLowerThan(value: Long): GenericConstraint = constrainIfNotNull { it < value } otherwiseLowerThan value -public fun Validatable.isLowerThan(value: Float): Constraint = +public fun GenericValidatable.isLowerThan(value: Float): GenericConstraint = constrainIfNotNull { it < value } otherwiseLowerThan value -public fun Validatable.isLowerThan(value: Double): Constraint = +public fun GenericValidatable.isLowerThan(value: Double): GenericConstraint = constrainIfNotNull { it < value } otherwiseLowerThan value //endregion //region isLowerThanOrEqualTo -private infix fun Constraint.otherwiseLowerThanOrEqualTo(value: Number) = otherwise { "Must be lower than or equal to $value" } +private infix fun GenericConstraint.otherwiseLowerThanOrEqualTo(value: Number) = otherwise { "Must be lower than or equal to $value" } -public fun Validatable.isLowerThanOrEqualTo(value: Short): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Short): GenericConstraint = constrainIfNotNull { it <= value } otherwiseLowerThanOrEqualTo value -public fun Validatable.isLowerThanOrEqualTo(value: Int): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Int): GenericConstraint = constrainIfNotNull { it <= value } otherwiseLowerThanOrEqualTo value -public fun Validatable.isLowerThanOrEqualTo(value: Long): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Long): GenericConstraint = constrainIfNotNull { it <= value } otherwiseLowerThanOrEqualTo value -public fun Validatable.isLowerThanOrEqualTo(value: Float): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Float): GenericConstraint = constrainIfNotNull { it <= value } otherwiseLowerThanOrEqualTo value -public fun Validatable.isLowerThanOrEqualTo(value: Double): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Double): GenericConstraint = constrainIfNotNull { it <= value } otherwiseLowerThanOrEqualTo value //endregion //region isGreaterThan -private infix fun Constraint.otherwiseGreaterThan(value: Number) = otherwise { "Must be greater than $value" } +private infix fun GenericConstraint.otherwiseGreaterThan(value: Number) = otherwise { "Must be greater than $value" } -public fun Validatable.isGreaterThan(value: Short): Constraint = +public fun GenericValidatable.isGreaterThan(value: Short): GenericConstraint = constrainIfNotNull { it > value } otherwiseGreaterThan value -public fun Validatable.isGreaterThan(value: Int): Constraint = +public fun GenericValidatable.isGreaterThan(value: Int): GenericConstraint = constrainIfNotNull { it > value } otherwiseGreaterThan value -public fun Validatable.isGreaterThan(value: Long): Constraint = +public fun GenericValidatable.isGreaterThan(value: Long): GenericConstraint = constrainIfNotNull { it > value } otherwiseGreaterThan value -public fun Validatable.isGreaterThan(value: Float): Constraint = +public fun GenericValidatable.isGreaterThan(value: Float): GenericConstraint = constrainIfNotNull { it > value } otherwiseGreaterThan value -public fun Validatable.isGreaterThan(value: Double): Constraint = +public fun GenericValidatable.isGreaterThan(value: Double): GenericConstraint = constrainIfNotNull { it > value } otherwiseGreaterThan value //endregion //region isGreaterThanOrEqualTo -private infix fun Constraint.otherwiseGreaterThanOrEqualTo(value: Number) = otherwise { "Must be greater than or equal to $value" } +private infix fun GenericConstraint.otherwiseGreaterThanOrEqualTo(value: Number) = otherwise { "Must be greater than or equal to $value" } -public fun Validatable.isGreaterThanOrEqualTo(value: Short): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Short): GenericConstraint = constrainIfNotNull { it >= value } otherwiseGreaterThanOrEqualTo value -public fun Validatable.isGreaterThanOrEqualTo(value: Int): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Int): GenericConstraint = constrainIfNotNull { it >= value } otherwiseGreaterThanOrEqualTo value -public fun Validatable.isGreaterThanOrEqualTo(value: Long): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Long): GenericConstraint = constrainIfNotNull { it >= value } otherwiseGreaterThanOrEqualTo value -public fun Validatable.isGreaterThanOrEqualTo(value: Float): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Float): GenericConstraint = constrainIfNotNull { it >= value } otherwiseGreaterThanOrEqualTo value -public fun Validatable.isGreaterThanOrEqualTo(value: Double): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Double): GenericConstraint = constrainIfNotNull { it >= value } otherwiseGreaterThanOrEqualTo value //endregion //region isBetween @JvmName("shortIsBetween") -public fun Validatable.isBetween(range: IntRange): Constraint = +public fun GenericValidatable.isBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.first} and ${range.last} (inclusive)" } -public fun Validatable.isBetween(range: IntRange): Constraint = +public fun GenericValidatable.isBetween(range: IntRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.first} and ${range.last} (inclusive)" } -public fun Validatable.isBetween(range: LongRange): Constraint = +public fun GenericValidatable.isBetween(range: LongRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.first} and ${range.last} (inclusive)" } @JvmName("closedFloatIsBetween") -public fun Validatable.isBetween(range: ClosedFloatingPointRange): Constraint = +public fun GenericValidatable.isBetween(range: ClosedFloatingPointRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endInclusive} (inclusive)" } @JvmName("closedDoubleIsBetween") -public fun Validatable.isBetween(range: ClosedFloatingPointRange): Constraint = +public fun GenericValidatable.isBetween(range: ClosedFloatingPointRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endInclusive} (inclusive)" } @OptIn(ExperimentalStdlibApi::class) @JvmName("openFloatIsBetween") -public fun Validatable.isBetween(range: OpenEndRange): Constraint = +public fun GenericValidatable.isBetween(range: OpenEndRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endExclusive} (exclusive)" } @OptIn(ExperimentalStdlibApi::class) @JvmName("openDoubleIsBetween") -public fun Validatable.isBetween(range: OpenEndRange): Constraint = +public fun GenericValidatable.isBetween(range: OpenEndRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endExclusive} (exclusive)" } //endregion @@ -262,10 +262,10 @@ private fun requireCountGreaterThanZero(count: Int) { require(count > 0) { "'count' cannot be lower than 1" } } -private fun Validatable.constrainDigitCount( +private fun GenericValidatable.constrainDigitCount( runDecimalChecks: Boolean, block: (integralCount: Int, fractionalCount: Int?) -> Boolean, -): Constraint { +): GenericConstraint { return constrainIfNotNull { if (runDecimalChecks) { val floatValue = it.toFloat() @@ -277,44 +277,44 @@ private fun Validatable.constrainDigitCount( } } -private fun Validatable.constrainIntegralPart(count: Int, runDecimalChecks: Boolean): Constraint { +private fun GenericValidatable.constrainIntegralPart(count: Int, runDecimalChecks: Boolean): GenericConstraint { requireCountGreaterThanZero(count) return constrainDigitCount(runDecimalChecks) { integralCount, _ -> integralCount == count } } -internal fun Validatable.constrainDecimalPart(count: Int): Constraint { +internal fun GenericValidatable.constrainDecimalPart(count: Int): GenericConstraint { requireCountGreaterThanZero(count) return constrainDigitCount(runDecimalChecks = true) { _, fractionalCount -> if (fractionalCount == null) false else fractionalCount == count } } -private infix fun Constraint.otherwiseIntegralCountEqualTo(count: Int) = otherwise { "Must contain $count integral digits" } -internal infix fun Constraint.otherwiseFractionalCountEqualTo(count: Int) = otherwise { "Must contain $count fractional digits" } +private infix fun GenericConstraint.otherwiseIntegralCountEqualTo(count: Int) = otherwise { "Must contain $count integral digits" } +internal infix fun GenericConstraint.otherwiseFractionalCountEqualTo(count: Int) = otherwise { "Must contain $count fractional digits" } @JvmName("shortHasIntegralCountEqualTo") -public fun Validatable.hasIntegralCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasIntegralCountEqualTo(count: Int): GenericConstraint = constrainIntegralPart(count, runDecimalChecks = false) otherwiseIntegralCountEqualTo count @JvmName("intHasIntegralCountEqualTo") -public fun Validatable.hasIntegralCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasIntegralCountEqualTo(count: Int): GenericConstraint = constrainIntegralPart(count, runDecimalChecks = false) otherwiseIntegralCountEqualTo count @JvmName("longHasIntegralCountEqualTo") -public fun Validatable.hasIntegralCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasIntegralCountEqualTo(count: Int): GenericConstraint = constrainIntegralPart(count, runDecimalChecks = false) otherwiseIntegralCountEqualTo count @JvmName("floatHasIntegralCountEqualTo") -public fun Validatable.hasIntegralCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasIntegralCountEqualTo(count: Int): GenericConstraint = constrainIntegralPart(count, runDecimalChecks = true) otherwiseIntegralCountEqualTo count @JvmName("doubleHasIntegralCountEqualTo") -public fun Validatable.hasIntegralCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasIntegralCountEqualTo(count: Int): GenericConstraint = constrainIntegralPart(count, runDecimalChecks = true) otherwiseIntegralCountEqualTo count @JvmName("doubleHasFractionalCountEqualTo") -public fun Validatable.hasFractionalCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasFractionalCountEqualTo(count: Int): GenericConstraint = constrainDecimalPart(count) otherwiseFractionalCountEqualTo count //endregion diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Time.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Time.kt index c2649313..06949183 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Time.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/Time.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import kotlin.time.Duration /** @@ -35,7 +35,7 @@ import kotlin.time.Duration * validate(1.seconds) // Failure (message: Must be negative) * ``` */ -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it.isNegative() && it != Duration.ZERO } otherwise { "Must be negative" } /** @@ -50,7 +50,7 @@ public fun Validatable.isNegative(): Constraint = * validate(1.seconds) // Failure (message: Must be negative or equal to zero) * ``` */ -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it.isNegative() || it == Duration.ZERO } otherwise { "Must be negative or equal to zero" } /** @@ -65,7 +65,7 @@ public fun Validatable.isNegativeOrZero(): Constraint = * validate((-1).seconds) // Failure (message: Must be positive) * ``` */ -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { !it.isNegative() && it != Duration.ZERO } otherwise { "Must be positive" } /** @@ -80,7 +80,7 @@ public fun Validatable.isPositive(): Constraint = * validate((-1).seconds) // Failure (message: Must be positive or equal to zero) * ``` */ -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { !it.isNegative() || it == Duration.ZERO } otherwise { "Must be positive or equal to zero" } /** @@ -95,7 +95,7 @@ public fun Validatable.isPositiveOrZero(): Constraint = * validate(1.seconds) // Failure (message: Must be lower than 0s) * ``` */ -public fun Validatable.isLowerThan(value: Duration): Constraint = +public fun GenericValidatable.isLowerThan(value: Duration): GenericConstraint = constrainIfNotNull { it < value } otherwise { "Must be lower than $value" } /** @@ -110,7 +110,7 @@ public fun Validatable.isLowerThan(value: Duration): Constraint = * validate(1.seconds) // Failure (message: Must be lower than or equal to 0s) * ``` */ -public fun Validatable.isLowerThanOrEqualTo(value: Duration): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Duration): GenericConstraint = constrainIfNotNull { it <= value } otherwise { "Must be lower than or equal to $value" } /** @@ -125,7 +125,7 @@ public fun Validatable.isLowerThanOrEqualTo(value: Duration): Constra * validate((-1).seconds) // Failure (message: Must be greater than 0s) * ``` */ -public fun Validatable.isGreaterThan(value: Duration): Constraint = +public fun GenericValidatable.isGreaterThan(value: Duration): GenericConstraint = constrainIfNotNull { it > value } otherwise { "Must be greater than $value" } /** @@ -140,7 +140,7 @@ public fun Validatable.isGreaterThan(value: Duration): Constraint = * validate((-1).seconds) // Failure (message: Must be greater than or equal to 0s) * ``` */ -public fun Validatable.isGreaterThanOrEqualTo(value: Duration): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Duration): GenericConstraint = constrainIfNotNull { it >= value } otherwise { "Must be greater than or equal to $value" } //region isBetween @@ -159,7 +159,7 @@ public fun Validatable.isGreaterThanOrEqualTo(value: Duration): Const * validate(15.seconds) // Failure (message: Must be between 0s and 10s (inclusive)) * ``` */ -public fun Validatable.isBetween(range: ClosedRange): Constraint = +public fun GenericValidatable.isBetween(range: ClosedRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endInclusive} (inclusive)" } /** @@ -177,6 +177,6 @@ public fun Validatable.isBetween(range: ClosedRange): Const * validate(15.seconds) // Failure (message: Must be between 0s and 10s (exclusive)) * ``` */ -public fun Validatable.isBetween(range: OpenEndRange): Constraint = +public fun GenericValidatable.isBetween(range: OpenEndRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endExclusive} (exclusive)" } //endregion diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/Validatable.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/Validatable.kt index 842fb7f2..03fb4e89 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/Validatable.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/Validatable.kt @@ -19,10 +19,8 @@ package dev.nesk.akkurate.validatables import dev.nesk.akkurate.Path import dev.nesk.akkurate.Validator -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.* import dev.nesk.akkurate.constraints.ConstraintRegistry -import dev.nesk.akkurate.constraints.ConstraintViolation -import dev.nesk.akkurate.constraints.constrain import dev.nesk.akkurate.validateWith import kotlin.jvm.JvmName import kotlin.reflect.KFunction1 @@ -31,38 +29,51 @@ import kotlin.reflect.KProperty1 @DslMarker private annotation class ValidatableDslMarker +public typealias DefaultMetadataType = Map +public typealias Validatable = GenericValidatable + @ValidatableDslMarker -public class Validatable private constructor( +public class GenericValidatable private constructor( private val wrappedValue: T, pathSegment: String?, - private val constraintRegistry: ConstraintRegistry, - internal val parent: Validatable<*>?, + private val constraintRegistry: GenericConstraintRegistry, + internal val parent: GenericValidatable<*, MetadataType>?, private val path: Path = buildPathFromParentAndSegment(parent, pathSegment), + public val defaultMetadata: MetadataType ) { - private companion object { - fun buildPathFromParentAndSegment(parent: Validatable<*>?, segment: String?) = buildList { + internal companion object { + fun buildPathFromParentAndSegment(parent: GenericValidatable<*, MetadataType>?, segment: String?) = buildList { addAll(parent?.path ?: emptyList()) if (!segment.isNullOrEmpty()) { add(segment) } } + + operator fun invoke(wrappedValue: T, pathSegment: String?, constraintRegistry: GenericConstraintRegistry, parent: Validatable<*>?, path: Path): Validatable = + GenericValidatable(wrappedValue, pathSegment, constraintRegistry, parent, path, emptyMap()) + + operator fun invoke(wrappedValue: T, constraintRegistry: GenericConstraintRegistry): Validatable = + GenericValidatable(wrappedValue, constraintRegistry, emptyMap()) + + operator fun invoke(wrappedValue: T, pathSegment: String?, constraintRegistry: GenericConstraintRegistry, parent: GenericValidatable<*, MetadataType>?, path: Path, defaultMeta: MetadataType): GenericValidatable = + GenericValidatable(wrappedValue, pathSegment, constraintRegistry, parent, path, defaultMeta) } /** - * Instantiates a root [Validatable] with its [value][wrappedValue] and a [ConstraintRegistry]. + * Instantiates a root [GenericValidatable] with its [value][wrappedValue] and a [GenericConstraintRegistry]. * * Used by [Validator] instances to start the validation process. */ - internal constructor(wrappedValue: T, constraintRegistry: ConstraintRegistry) : - this(wrappedValue, pathSegment = null, constraintRegistry, parent = null) + internal constructor(wrappedValue: T, constraintRegistry: GenericConstraintRegistry, defaultMetadata: MetadataType) : + this(wrappedValue, pathSegment = null, constraintRegistry, parent = null, defaultMetadata = defaultMetadata) /** - * Instantiates a child [Validatable] with [its relative path][pathSegment] and its [Validatable parent][parent]. + * Instantiates a child [GenericValidatable] with [its relative path][pathSegment] and its [GenericValidatable parent][parent]. * * Used by [validatableOf] functions to validate nested properties. */ - internal constructor(wrappedValue: T, pathSegment: String, parent: Validatable<*>) : - this(wrappedValue, pathSegment, parent.constraintRegistry, parent) + internal constructor(wrappedValue: T, pathSegment: String, parent: GenericValidatable<*, MetadataType>) : + this(wrappedValue, pathSegment, parent.constraintRegistry, parent, defaultMetadata = parent.defaultMetadata) public fun path(): Path = path @@ -82,23 +93,23 @@ public class Validatable private constructor( * * This method shouldn't be called in user code, [constrain] should be used instead. */ - public fun registerConstraint(constraint: Constraint): Unit = constraintRegistry.register(constraint) + public fun registerConstraint(constraint: GenericConstraint): Unit = constraintRegistry.register(constraint) /** * Registers the provided constraint violation. * * This method shouldn't be called in user code, [constrain] or [validateWith] should be used instead. */ - public fun registerConstraint(constraint: ConstraintViolation): Unit = constraintRegistry.register(constraint) + public fun registerConstraint(constraint: GenericConstraintViolation): Unit = constraintRegistry.register(constraint) /** - * Duplicates the [Validatable] with the new provided [value]. The path remains the same. + * Duplicates the [GenericValidatable] with the new provided [value]. The path remains the same. */ - public fun withValue(value: NEW_TYPE): Validatable = - Validatable(value, pathSegment = null, constraintRegistry, parent, path) + public fun withValue(value: NEW_TYPE): GenericValidatable = + GenericValidatable(value, pathSegment = null, constraintRegistry, parent, path, defaultMetadata) // TODO: Convert to extension function (breaking change) once JetBrains fixes imports: https://youtrack.jetbrains.com/issue/KTIJ-22147 - public inline operator fun invoke(block: Validatable.() -> Unit): Unit = this.block() + public inline operator fun invoke(block: GenericValidatable.() -> Unit): Unit = this.block() /** * Indicates whether some other object is "equal to" this validatable. @@ -116,7 +127,7 @@ public class Validatable private constructor( if (this === other) return true if (other == null || this::class != other::class) return false - other as Validatable<*> + other as GenericValidatable<*, MetadataType> return wrappedValue == other.wrappedValue } @@ -132,28 +143,27 @@ public class Validatable private constructor( override fun toString(): String = "Validatable(unwrap=$wrappedValue, path=$path)" } - -public fun Validatable.validatableOf(getter: KProperty1): Validatable { - return Validatable(getter.get(unwrap()), getter.name, this) +public fun GenericValidatable.validatableOf(getter: KProperty1): GenericValidatable { + return GenericValidatable(getter.get(unwrap()), getter.name, this) } @JvmName("nullableValidatableOfProperty") -public fun Validatable.validatableOf(getter: KProperty1): Validatable { - return Validatable(unwrap()?.let { getter.get(it) }, getter.name, this) +public fun GenericValidatable.validatableOf(getter: KProperty1): GenericValidatable { + return GenericValidatable(unwrap()?.let { getter.get(it) }, getter.name, this) } -public fun Validatable.validatableOf(getter: KFunction1): Validatable { - return Validatable(getter.invoke(unwrap()), getter.name, this) +public fun GenericValidatable.validatableOf(getter: KFunction1): GenericValidatable { + return GenericValidatable(getter.invoke(unwrap()), getter.name, this) } @JvmName("nullableValidatableOfFunction") -public fun Validatable.validatableOf(getter: KFunction1): Validatable { - return Validatable(unwrap()?.let { getter.invoke(it) }, getter.name, this) +public fun GenericValidatable.validatableOf(getter: KFunction1): GenericValidatable { + return GenericValidatable(unwrap()?.let { getter.invoke(it) }, getter.name, this) } /** - * Returns a [Validatable] wrapping the result of the given [transform] function. - * The latter is applied to the wrapped value of the [Validatable] receiver. + * Returns a [GenericValidatable] wrapping the result of the given [transform] function. + * The latter is applied to the wrapped value of the [GenericValidatable] receiver. */ -public inline fun Validatable.map(transform: (T) -> R): Validatable = +public inline fun GenericValidatable.map(transform: (T) -> R): GenericValidatable = withValue(unwrap().let(transform)) diff --git a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/ValidatableCompound.kt b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/ValidatableCompound.kt index fc799333..0169dd7d 100644 --- a/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/ValidatableCompound.kt +++ b/akkurate-core/src/commonMain/kotlin/dev/nesk/akkurate/validatables/ValidatableCompound.kt @@ -29,18 +29,18 @@ package dev.nesk.akkurate.validatables To ease development, the first release will make parentheses mandatory. */ -public class ValidatableCompound internal constructor(validatables: List>) { - public val validatables: List> = validatables.distinctBy { it.path() } +public class ValidatableCompound internal constructor(validatables: List>) { + public val validatables: List> = validatables.distinctBy { it.path() } - public infix fun and(other: Validatable): ValidatableCompound = ValidatableCompound(validatables + setOf(other)) - public infix fun and(other: ValidatableCompound): ValidatableCompound = ValidatableCompound(validatables + other.validatables) + public infix fun and(other: GenericValidatable): ValidatableCompound = ValidatableCompound(validatables + setOf(other)) + public infix fun and(other: ValidatableCompound): ValidatableCompound = ValidatableCompound(validatables + other.validatables) // TODO: Convert to extension function (breaking change) once JetBrains fixes imports: https://youtrack.jetbrains.com/issue/KTIJ-22147 - public inline operator fun invoke(block: Validatable.() -> Unit): Unit = validatables.forEach { it.block() } + public inline operator fun invoke(block: GenericValidatable.() -> Unit): Unit = validatables.forEach { it.block() } } -public infix fun Validatable.and(other: Validatable): ValidatableCompound = ValidatableCompound(listOf(this, other)) -public infix fun Validatable.and(other: ValidatableCompound): ValidatableCompound = ValidatableCompound(listOf(this) + other.validatables) +public infix fun GenericValidatable.and(other: GenericValidatable): ValidatableCompound = ValidatableCompound(listOf(this, other)) +public infix fun GenericValidatable.and(other: ValidatableCompound): ValidatableCompound = ValidatableCompound(listOf(this) + other.validatables) // TODO: find a solution to improve type combinations //infix fun ValidatableCompound.and(other: Validatable): ValidatableCompound = TODO() diff --git a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidationResultTest.kt b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidationResultTest.kt index 8a72e747..b0d1b059 100644 --- a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidationResultTest.kt +++ b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidationResultTest.kt @@ -46,7 +46,7 @@ class ValidationResultTest { val failure = ValidationResult.Failure(violations) // Act & Assert val exception = assertFailsWith { failure.orThrow() } - assertSame(violations, exception.violations) + assertEquals(violations, exception.violations) } @Test diff --git a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidatorTest.kt b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidatorTest.kt index 786e09e6..a7b382ef 100644 --- a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidatorTest.kt +++ b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/ValidatorTest.kt @@ -21,6 +21,7 @@ import dev.nesk.akkurate.constraints.ConstraintViolation import dev.nesk.akkurate.constraints.constrain import dev.nesk.akkurate.constraints.otherwise import dev.nesk.akkurate.constraints.withPath +import dev.nesk.akkurate.validatables.DefaultMetadataType import dev.nesk.akkurate.validatables.validatableOf import kotlinx.coroutines.test.runTest import kotlin.test.* @@ -57,7 +58,7 @@ class ValidatorTest { // Act val result = validate(null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(expectedViolations, result.violations, "The result contains the corresponding violations") } @@ -83,7 +84,7 @@ class ValidatorTest { // Act val result = validate(Context(), null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(expectedViolations, result.violations, "The result contains the corresponding violations") } @@ -109,7 +110,7 @@ class ValidatorTest { // Act val result = validate(null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(expectedViolations, result.violations, "The result contains the corresponding violations") } @@ -135,7 +136,7 @@ class ValidatorTest { // Act val result = validate(Context(), null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(expectedViolations, result.violations, "The result contains the corresponding violations") } @@ -146,7 +147,7 @@ class ValidatorTest { validatableOf(Value::name).constrain { false } } val result = validate(Value()) - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(listOf("foo", "bar", "name"), result.violations.single().path) } @@ -157,7 +158,7 @@ class ValidatorTest { constrain { false } } val result = validate(null) - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals("default", result.violations.single().message) } @@ -174,7 +175,7 @@ class ValidatorTest { val result = validate(null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals("first message", result.violations.single().message) } @@ -191,7 +192,7 @@ class ValidatorTest { val result = validate(null) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(2, result.violations.size) assertContentEquals(listOf("first message", "second message"), result.violations.map { it.message }) } @@ -221,7 +222,7 @@ class ValidatorTest { val result = validate1(First(Second(Third()))) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertContentEquals(expectedViolations, result.violations.toList(), "All the constraints violations are reported") } @@ -250,7 +251,7 @@ class ValidatorTest { val result = validate1(Context(), First(Second(Third()))) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertContentEquals(expectedViolations, result.violations.toList(), "All the constraints violations are reported") } @@ -279,7 +280,7 @@ class ValidatorTest { val result = validate1(First(Second(Third()))) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertContentEquals(expectedViolations, result.violations.toList(), "All the constraints violations are reported") } @@ -308,7 +309,7 @@ class ValidatorTest { val result = validate1(Context(), First(Second(Third()))) // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertContentEquals(expectedViolations, result.violations.toList(), "All the constraints violations are reported") } } diff --git a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistryTest.kt b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistryTest.kt index 5228650e..d4194eb6 100644 --- a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistryTest.kt +++ b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/constraints/ConstraintRegistryTest.kt @@ -21,13 +21,14 @@ import dev.nesk.akkurate.Configuration import dev.nesk.akkurate.ValidationResult import dev.nesk.akkurate._test.Validatable import dev.nesk.akkurate.test.Validatable +import dev.nesk.akkurate.validatables.DefaultMetadataType import kotlin.test.* class ConstraintRegistryTest { @Test fun calling__register__with_a_satisfied_constraint_leaves_the_collection_empty() { // Arrange - val constraint = Constraint(true, Validatable(null)) + val constraint = GenericConstraint(true, Validatable(null)) val registry = ConstraintRegistry(Configuration()) // Act registry.register(constraint) @@ -38,7 +39,7 @@ class ConstraintRegistryTest { @Test fun calling__register__with_an_unsatisfied_constraint_adds_it_to_the_collection() { // Arrange - val constraint = Constraint(false, Validatable(null)) + val constraint = GenericConstraint(false, Validatable(null)) val registry = ConstraintRegistry(Configuration()) // Act registry.register(constraint) @@ -49,7 +50,7 @@ class ConstraintRegistryTest { @Test fun calling__register__with_a_constraint_violation_adds_it_to_the_collection() { // Arrange - val constraint = Constraint(false, Validatable(null)) + val constraint = GenericConstraint(false, Validatable(null)) val registry = ConstraintRegistry(Configuration()) // Act registry.register(constraint) @@ -60,9 +61,9 @@ class ConstraintRegistryTest { @Test fun calling__register__multiples_times_with_constraints_adds_them_to_the_collection_if_they_are_unsatisfied() { // Arrange - val constraint1 = Constraint(false, Validatable(null, "path1")) - val constraint2 = Constraint(true, Validatable(null, "path2")) - val constraint3 = Constraint(false, Validatable(null, "path3")) + val constraint1 = GenericConstraint(false, Validatable(null, "path1")) + val constraint2 = GenericConstraint(true, Validatable(null, "path2")) + val constraint3 = GenericConstraint(false, Validatable(null, "path3")) val registry = ConstraintRegistry(Configuration()) // Act registry.register(constraint1) @@ -76,9 +77,9 @@ class ConstraintRegistryTest { fun __runWithConstraintRegistry__returns_a_validation_success_if_all_the_constraint_where_satisfied() { // Arrange val value = object {} - val constraint1 = Constraint(true, Validatable(null, "path1")) - val constraint2 = Constraint(true, Validatable(null, "path2")) - val constraint3 = Constraint(true, Validatable(null, "path3")) + val constraint1 = GenericConstraint(true, Validatable(null, "path1")) + val constraint2 = GenericConstraint(true, Validatable(null, "path2")) + val constraint3 = GenericConstraint(true, Validatable(null, "path3")) // Act val result = runWithConstraintRegistry(value, Configuration()) { it.register(constraint1) @@ -94,9 +95,9 @@ class ConstraintRegistryTest { fun __runWithConstraintRegistry__returns_a_validation_failure_if_any_constraint_was_unsatisfied() { // Arrange val value = object {} - val constraint1 = Constraint(true, Validatable(null, "path1")) otherwise { "message 1" } - val constraint2 = Constraint(false, Validatable(null, "path2")) otherwise { "message 2" } - val constraint3 = Constraint(false, Validatable(null, "path3")) otherwise { "message 3" } + val constraint1 = GenericConstraint(true, Validatable(null, "path1")) otherwise { "message 1" } + val constraint2 = GenericConstraint(false, Validatable(null, "path2")) otherwise { "message 2" } + val constraint3 = GenericConstraint(false, Validatable(null, "path3")) otherwise { "message 3" } // Act val result = runWithConstraintRegistry(value, Configuration()) { it.register(constraint1) @@ -104,7 +105,7 @@ class ConstraintRegistryTest { it.register(constraint3) } // Assert - assertIs(result) + assertIs>(result) assertContentEquals(listOf("message 2", "message 3"), result.violations.map { it.message }) } @@ -112,11 +113,11 @@ class ConstraintRegistryTest { fun definining_the_root_path_in_the_configuration_will_prepend_all_the_paths_in_the_returned_constraint_violations() { // Arrange val config = Configuration { rootPath("foo", "bar") } - val constraint = Constraint(false, Validatable(null, "baz")) + val constraint = GenericConstraint(false, Validatable(null, "baz")) // Act val result = runWithConstraintRegistry(null, config) { it.register(constraint) } // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(listOf("foo", "bar", "baz"), result.violations.single().path) } @@ -124,11 +125,11 @@ class ConstraintRegistryTest { fun definining_the_default_message_in_the_configuration_will_replace_empty_messages_in_constraints() { // Arrange val config = Configuration { defaultViolationMessage = "default" } - val constraint = Constraint(false, Validatable(null)) + val constraint = GenericConstraint(false, Validatable(null)) // Act val result = runWithConstraintRegistry(null, config) { it.register(constraint) } // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals("default", result.violations.single().message) } @@ -136,8 +137,8 @@ class ConstraintRegistryTest { fun calling__checkFirstViolationConfiguration__with_failOnFirstViolation_set_to_true_skips_all_the_upcoming_constraints_after_the_first_constraint_violation() { // Arrange val config = Configuration { failOnFirstViolation = true } - val constraint1 = Constraint(false, Validatable(null)) otherwise { "first message" } - val constraint2 = Constraint(false, Validatable(null)) otherwise { "second message" } + val constraint1 = GenericConstraint(false, Validatable(null)) otherwise { "first message" } + val constraint2 = GenericConstraint(false, Validatable(null)) otherwise { "second message" } // Act val result = runWithConstraintRegistry(null, config) { it.register(constraint1) @@ -145,7 +146,7 @@ class ConstraintRegistryTest { it.register(constraint2) } // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals("first message", result.violations.single().message) } @@ -153,8 +154,8 @@ class ConstraintRegistryTest { fun calling__checkFirstViolationConfiguration__with_failOnFirstViolation_set_to_false_executes_all_the_constraints_before_returning_a_failure() { // Arrange val config = Configuration { failOnFirstViolation = false } - val constraint1 = Constraint(false, Validatable(null)) otherwise { "first message" } - val constraint2 = Constraint(false, Validatable(null)) otherwise { "second message" } + val constraint1 = GenericConstraint(false, Validatable(null)) otherwise { "first message" } + val constraint2 = GenericConstraint(false, Validatable(null)) otherwise { "second message" } // Act val result = runWithConstraintRegistry(null, config) { it.register(constraint1) @@ -162,7 +163,7 @@ class ConstraintRegistryTest { it.register(constraint2) } // Assert - assertIs(result, "The result is a failure") + assertIs>(result, "The result is a failure") assertEquals(2, result.violations.size) assertContentEquals(listOf("first message", "second message"), result.violations.map { it.message }) } diff --git a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/validatables/ValidatableTest.kt b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/validatables/ValidatableTest.kt index 8a47fa02..28c7224e 100644 --- a/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/validatables/ValidatableTest.kt +++ b/akkurate-core/src/commonTest/kotlin/dev/nesk/akkurate/validatables/ValidatableTest.kt @@ -19,7 +19,7 @@ package dev.nesk.akkurate.validatables import dev.nesk.akkurate.Configuration import dev.nesk.akkurate._test.Validatable -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.ConstraintRegistry import dev.nesk.akkurate.constraints.ConstraintViolation import dev.nesk.akkurate.test.Validatable @@ -57,7 +57,7 @@ class ValidatableTest { // Arrange val registry = ConstraintRegistry(Configuration()) val validatable = Validatable(null, registry) - val constraint = Constraint(false, validatable) + val constraint = GenericConstraint(false, validatable) // Act validatable.registerConstraint(constraint) // Assert diff --git a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/accessors/Instant.kt b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/accessors/Instant.kt index 443b96e7..6cc65f7b 100644 --- a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/accessors/Instant.kt +++ b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/accessors/Instant.kt @@ -17,9 +17,9 @@ package dev.nesk.akkurate.accessors -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.validatableOf import java.time.Instant -public val Validatable.epochSecond: Validatable get() = validatableOf(Instant::getEpochSecond) -public val Validatable.nanos: Validatable get() = validatableOf(Instant::getNano) +public val GenericValidatable.epochSecond: GenericValidatable get() = validatableOf(Instant::getEpochSecond) +public val GenericValidatable.nanos: GenericValidatable get() = validatableOf(Instant::getNano) diff --git a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/Float.kt b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/Float.kt index f8dc0e9d..009ed870 100644 --- a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/Float.kt +++ b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/Float.kt @@ -17,12 +17,12 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.constraints.GenericConstraint +import dev.nesk.akkurate.validatables.GenericValidatable // Keep this constraint only available to the JVM target to avoid potential issues. // For example, the JS target fails to properly determine the fractional count of a Float. // We'll see later to make this constraint available to all targets. @JvmName("floatHasFractionalCountEqualTo") -public fun Validatable.hasFractionalCountEqualTo(count: Int): Constraint = +public fun GenericValidatable.hasFractionalCountEqualTo(count: Int): GenericConstraint = constrainDecimalPart(count) otherwiseFractionalCountEqualTo count diff --git a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/TemporalAccessor.kt b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/TemporalAccessor.kt index b97c6b5b..ba967d38 100644 --- a/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/TemporalAccessor.kt +++ b/akkurate-core/src/jvmMain/kotlin/dev/nesk/akkurate/constraints/builders/TemporalAccessor.kt @@ -17,10 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise -import dev.nesk.akkurate.validatables.Validatable +import dev.nesk.akkurate.validatables.GenericValidatable import java.time.* /** @@ -37,19 +37,19 @@ private val currentZonedDateTime get() = ZonedDateTime.now(clock) private const val pastMessage = "Must be in the past" @JvmName("instantIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentInstant } otherwise { pastMessage } @JvmName("localDateIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentLocalDate } otherwise { pastMessage } @JvmName("localDateTimeIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentLocalDateTime } otherwise { pastMessage } @JvmName("zonedDateTimeIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentZonedDateTime } otherwise { pastMessage } //endregion @@ -57,19 +57,19 @@ public fun Validatable.isInPast(): Constraint = private const val pastOrPresentMessage = "Must be in the past or present" @JvmName("instantIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentInstant } otherwise { pastOrPresentMessage } @JvmName("localDateIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentLocalDate } otherwise { pastOrPresentMessage } @JvmName("localDateTimeIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentLocalDateTime } otherwise { pastOrPresentMessage } @JvmName("zonedDateTimeIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentZonedDateTime } otherwise { pastOrPresentMessage } //endregion @@ -77,19 +77,19 @@ public fun Validatable.isInPastOrIsPresent(): Constraint = private const val futureMessage = "Must be in the future" @JvmName("instantFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentInstant } otherwise { futureMessage } @JvmName("localDateFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentLocalDate } otherwise { futureMessage } @JvmName("localDateTimeFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentLocalDateTime } otherwise { futureMessage } @JvmName("zonedDateTimeFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentZonedDateTime } otherwise { futureMessage } //endregion @@ -97,83 +97,83 @@ public fun Validatable.isInFuture(): Constraint = private const val futureOrPresentMessage = "Must be in the future or present" @JvmName("instantIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentInstant } otherwise { futureOrPresentMessage } @JvmName("localDateIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentLocalDate } otherwise { futureOrPresentMessage } @JvmName("localDateTimeIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentLocalDateTime } otherwise { futureOrPresentMessage } @JvmName("zonedDateTimeIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentZonedDateTime } otherwise { futureOrPresentMessage } //endregion //region isBefore -private infix fun Constraint.otherwiseBefore(other: Any) = otherwise { "Must be before \"$other\"" } +private infix fun GenericConstraint.otherwiseBefore(other: Any) = otherwise { "Must be before \"$other\"" } -public fun Validatable.isBefore(other: Instant): Constraint = +public fun GenericValidatable.isBefore(other: Instant): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other -public fun Validatable.isBefore(other: LocalDate): Constraint = +public fun GenericValidatable.isBefore(other: LocalDate): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other -public fun Validatable.isBefore(other: LocalDateTime): Constraint = +public fun GenericValidatable.isBefore(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other -public fun Validatable.isBefore(other: ZonedDateTime): Constraint = +public fun GenericValidatable.isBefore(other: ZonedDateTime): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other //endregion //region isBeforeOrEqualTo -private infix fun Constraint.otherwiseBeforeOrEqual(other: Any) = otherwise { "Must be before or equal to \"$other\"" } +private infix fun GenericConstraint.otherwiseBeforeOrEqual(other: Any) = otherwise { "Must be before or equal to \"$other\"" } -public fun Validatable.isBeforeOrEqualTo(other: Instant): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: Instant): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other -public fun Validatable.isBeforeOrEqualTo(other: LocalDate): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: LocalDate): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other -public fun Validatable.isBeforeOrEqualTo(other: LocalDateTime): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other -public fun Validatable.isBeforeOrEqualTo(other: ZonedDateTime): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: ZonedDateTime): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other //endregion //region isAfter -private infix fun Constraint.otherwiseAfter(other: Any) = otherwise { "Must be after \"$other\"" } +private infix fun GenericConstraint.otherwiseAfter(other: Any) = otherwise { "Must be after \"$other\"" } -public fun Validatable.isAfter(other: Instant): Constraint = +public fun GenericValidatable.isAfter(other: Instant): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other -public fun Validatable.isAfter(other: LocalDate): Constraint = +public fun GenericValidatable.isAfter(other: LocalDate): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other -public fun Validatable.isAfter(other: LocalDateTime): Constraint = +public fun GenericValidatable.isAfter(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other -public fun Validatable.isAfter(other: ZonedDateTime): Constraint = +public fun GenericValidatable.isAfter(other: ZonedDateTime): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other //endregion //region isAfterOrEqualTo -private infix fun Constraint.otherwiseAfterOrEqual(other: Any) = otherwise { "Must be after or equal to \"$other\"" } +private infix fun GenericConstraint.otherwiseAfterOrEqual(other: Any) = otherwise { "Must be after or equal to \"$other\"" } -public fun Validatable.isAfterOrEqualTo(other: Instant): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: Instant): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other -public fun Validatable.isAfterOrEqualTo(other: LocalDate): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: LocalDate): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other -public fun Validatable.isAfterOrEqualTo(other: LocalDateTime): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other -public fun Validatable.isAfterOrEqualTo(other: ZonedDateTime): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: ZonedDateTime): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other //endregion @@ -181,11 +181,11 @@ public fun Validatable.isAfterOrEqualTo(other: ZonedDateTime): C private const val negativeMessage = "Must be negative" @JvmName("durationIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it.isNegative && it != Duration.ZERO } otherwise { negativeMessage } @JvmName("periodIsNegative") -public fun Validatable.isNegative(): Constraint = +public fun GenericValidatable.isNegative(): GenericConstraint = constrainIfNotNull { it.isNegative && it != Period.ZERO } otherwise { negativeMessage } //endregion @@ -193,11 +193,11 @@ public fun Validatable.isNegative(): Constraint = private const val negativeMessageOrZero = "Must be negative or equal to zero" @JvmName("durationIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it.isNegative || it == Duration.ZERO } otherwise { negativeMessageOrZero } @JvmName("periodIsNegativeOrZero") -public fun Validatable.isNegativeOrZero(): Constraint = +public fun GenericValidatable.isNegativeOrZero(): GenericConstraint = constrainIfNotNull { it.isNegative || it == Period.ZERO } otherwise { negativeMessageOrZero } //endregion @@ -205,11 +205,11 @@ public fun Validatable.isNegativeOrZero(): Constraint = private const val positiveMessage = "Must be positive" @JvmName("durationIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { !it.isNegative && it != Duration.ZERO } otherwise { positiveMessage } @JvmName("periodIsPositive") -public fun Validatable.isPositive(): Constraint = +public fun GenericValidatable.isPositive(): GenericConstraint = constrainIfNotNull { !it.isNegative && it != Period.ZERO } otherwise { positiveMessage } //endregion @@ -217,39 +217,39 @@ public fun Validatable.isPositive(): Constraint = private const val positiveMessageOrZero = "Must be positive or equal to zero" @JvmName("durationIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { !it.isNegative || it == Duration.ZERO } otherwise { positiveMessageOrZero } @JvmName("periodIsPositiveOrZero") -public fun Validatable.isPositiveOrZero(): Constraint = +public fun GenericValidatable.isPositiveOrZero(): GenericConstraint = constrainIfNotNull { !it.isNegative || it == Period.ZERO } otherwise { positiveMessageOrZero } //endregion //region isLowerThan -public fun Validatable.isLowerThan(value: Duration): Constraint = +public fun GenericValidatable.isLowerThan(value: Duration): GenericConstraint = constrainIfNotNull { it < value } otherwise { "Must be lower than $value" } //endregion //region isLowerThanOrEqualTo -public fun Validatable.isLowerThanOrEqualTo(value: Duration): Constraint = +public fun GenericValidatable.isLowerThanOrEqualTo(value: Duration): GenericConstraint = constrainIfNotNull { it <= value } otherwise { "Must be lower than or equal to $value" } //endregion //region isGreaterThan -public fun Validatable.isGreaterThan(value: Duration): Constraint = +public fun GenericValidatable.isGreaterThan(value: Duration): GenericConstraint = constrainIfNotNull { it > value } otherwise { "Must be greater than $value" } //endregion //region isGreaterThanOrEqualTo -public fun Validatable.isGreaterThanOrEqualTo(value: Duration): Constraint = +public fun GenericValidatable.isGreaterThanOrEqualTo(value: Duration): GenericConstraint = constrainIfNotNull { it >= value } otherwise { "Must be greater than or equal to $value" } //endregion //region isBetween -public fun Validatable.isBetween(range: ClosedRange): Constraint = +public fun GenericValidatable.isBetween(range: ClosedRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endInclusive} (inclusive)" } -public fun Validatable.isBetween(range: OpenEndRange): Constraint = +public fun GenericValidatable.isBetween(range: OpenEndRange): GenericConstraint = constrainIfNotNull { it in range } otherwise { "Must be between ${range.start} and ${range.endExclusive} (exclusive)" } //endregion diff --git a/akkurate-kotlinx-datetime/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/KotlinxDatetime.kt b/akkurate-kotlinx-datetime/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/KotlinxDatetime.kt index 34c18533..3dfaa0a6 100644 --- a/akkurate-kotlinx-datetime/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/KotlinxDatetime.kt +++ b/akkurate-kotlinx-datetime/src/commonMain/kotlin/dev/nesk/akkurate/constraints/builders/KotlinxDatetime.kt @@ -17,9 +17,10 @@ package dev.nesk.akkurate.constraints.builders -import dev.nesk.akkurate.constraints.Constraint +import dev.nesk.akkurate.constraints.GenericConstraint import dev.nesk.akkurate.constraints.constrainIfNotNull import dev.nesk.akkurate.constraints.otherwise +import dev.nesk.akkurate.validatables.GenericValidatable import dev.nesk.akkurate.validatables.Validatable import kotlinx.datetime.* import kotlin.jvm.JvmName @@ -53,7 +54,7 @@ private const val pastMessage = "Must be in the past" * ``` */ @JvmName("instantIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentInstant } otherwise { pastMessage } /** @@ -72,7 +73,7 @@ public fun Validatable.isInPast(): Constraint = * ``` */ @JvmName("localDateIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentLocalDate } otherwise { pastMessage } /** @@ -91,7 +92,7 @@ public fun Validatable.isInPast(): Constraint = * ``` */ @JvmName("localTimeIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentLocalTime } otherwise { pastMessage } /** @@ -111,7 +112,7 @@ public fun Validatable.isInPast(): Constraint = * ``` */ @JvmName("localDateTimeIsInPast") -public fun Validatable.isInPast(): Constraint = +public fun GenericValidatable.isInPast(): GenericConstraint = constrainIfNotNull { it < currentLocalDateTime } otherwise { pastMessage } //endregion @@ -133,7 +134,7 @@ private const val pastOrPresentMessage = "Must be in the past or present" * ``` */ @JvmName("instantIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentInstant } otherwise { pastOrPresentMessage } /** @@ -152,7 +153,7 @@ public fun Validatable.isInPastOrIsPresent(): Constraint = * ``` */ @JvmName("localDateIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentLocalDate } otherwise { pastOrPresentMessage } /** @@ -171,7 +172,7 @@ public fun Validatable.isInPastOrIsPresent(): Constraint = * ``` */ @JvmName("localTimeIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentLocalTime } otherwise { pastOrPresentMessage } /** @@ -191,7 +192,7 @@ public fun Validatable.isInPastOrIsPresent(): Constraint = * ``` */ @JvmName("localDateTimeIsInPastOrIsPresent") -public fun Validatable.isInPastOrIsPresent(): Constraint = +public fun GenericValidatable.isInPastOrIsPresent(): GenericConstraint = constrainIfNotNull { it <= currentLocalDateTime } otherwise { pastOrPresentMessage } //endregion @@ -213,7 +214,7 @@ private const val futureMessage = "Must be in the future" * ``` */ @JvmName("instantFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentInstant } otherwise { futureMessage } /** @@ -232,7 +233,7 @@ public fun Validatable.isInFuture(): Constraint = * ``` */ @JvmName("localDateFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentLocalDate } otherwise { futureMessage } /** @@ -251,7 +252,7 @@ public fun Validatable.isInFuture(): Constraint = * ``` */ @JvmName("localTimeFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentLocalTime } otherwise { futureMessage } /** @@ -271,7 +272,7 @@ public fun Validatable.isInFuture(): Constraint = * ``` */ @JvmName("localDateTimeFuture") -public fun Validatable.isInFuture(): Constraint = +public fun GenericValidatable.isInFuture(): GenericConstraint = constrainIfNotNull { it > currentLocalDateTime } otherwise { futureMessage } //endregion @@ -293,7 +294,7 @@ private const val futureOrPresentMessage = "Must be in the future or present" * ``` */ @JvmName("instantIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentInstant } otherwise { futureOrPresentMessage } /** @@ -312,7 +313,7 @@ public fun Validatable.isInFutureOrIsPresent(): Constraint = * ``` */ @JvmName("localDateIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentLocalDate } otherwise { futureOrPresentMessage } /** @@ -331,7 +332,7 @@ public fun Validatable.isInFutureOrIsPresent(): Constraint = * ``` */ @JvmName("localTimeIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentLocalTime } otherwise { futureOrPresentMessage } /** @@ -351,12 +352,12 @@ public fun Validatable.isInFutureOrIsPresent(): Constraint = * ``` */ @JvmName("localDateTimeIsInFutureOrIsPresent") -public fun Validatable.isInFutureOrIsPresent(): Constraint = +public fun GenericValidatable.isInFutureOrIsPresent(): GenericConstraint = constrainIfNotNull { it >= currentLocalDateTime } otherwise { futureOrPresentMessage } //endregion //region isBefore -private infix fun Constraint.otherwiseBefore(other: Any) = otherwise { "Must be before \"$other\"" } +private infix fun GenericConstraint.otherwiseBefore(other: Any) = otherwise { "Must be before \"$other\"" } /** * The validatable [Instant] must be before [other] when this constraint is applied. @@ -373,7 +374,7 @@ private infix fun Constraint.otherwiseBefore(other: Any) = otherwise { "Must be * validate(now + 5.seconds) // Failure (message: Must be before "2024-08-31T12:30:15Z") * ``` */ -public fun Validatable.isBefore(other: Instant): Constraint = +public fun GenericValidatable.isBefore(other: Instant): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other /** @@ -391,7 +392,7 @@ public fun Validatable.isBefore(other: Instant): Constraint = * validate(now.plus(2, DateTimeUnit.DAY)) // Failure (message: Must be before "2024-08-31") * ``` */ -public fun Validatable.isBefore(other: LocalDate): Constraint = +public fun GenericValidatable.isBefore(other: LocalDate): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other /** @@ -409,7 +410,7 @@ public fun Validatable.isBefore(other: LocalDate): Constraint = * validate(LocalTime.fromSecondOfDay(now.toSecondOfDay() + 5)) // Failure (message: Must be before "12:30:15") * ``` */ -public fun Validatable.isBefore(other: LocalTime): Constraint = +public fun GenericValidatable.isBefore(other: LocalTime): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other /** @@ -428,12 +429,12 @@ public fun Validatable.isBefore(other: LocalTime): Constraint = * validate((now.toInstant(timezone) + 5.seconds).toLocalDateTime(timezone)) // Failure (message: Must be before "2024-08-31T12:30:15") * ``` */ -public fun Validatable.isBefore(other: LocalDateTime): Constraint = +public fun GenericValidatable.isBefore(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it < other } otherwiseBefore other //endregion //region isBeforeOrEqualTo -private infix fun Constraint.otherwiseBeforeOrEqual(other: Any) = otherwise { "Must be before or equal to \"$other\"" } +private infix fun GenericConstraint.otherwiseBeforeOrEqual(other: Any) = otherwise { "Must be before or equal to \"$other\"" } /** * The validatable [Instant] must be before or equal to [other] when this constraint is applied. @@ -450,7 +451,7 @@ private infix fun Constraint.otherwiseBeforeOrEqual(other: Any) = otherwise { "M * validate(now + 5.seconds) // Failure (message: Must be before or equal to "2024-08-31T12:30:15Z") * ``` */ -public fun Validatable.isBeforeOrEqualTo(other: Instant): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: Instant): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other /** @@ -468,7 +469,7 @@ public fun Validatable.isBeforeOrEqualTo(other: Instant): Constraint = * validate(now.plus(2, DateTimeUnit.DAY)) // Failure (message: Must be before or equal to "2024-08-31") * ``` */ -public fun Validatable.isBeforeOrEqualTo(other: LocalDate): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: LocalDate): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other /** @@ -486,7 +487,7 @@ public fun Validatable.isBeforeOrEqualTo(other: LocalDate): Constrai * validate(LocalTime.fromSecondOfDay(now.toSecondOfDay() + 5)) // Failure (message: Must be before or equal to "12:30:15") * ``` */ -public fun Validatable.isBeforeOrEqualTo(other: LocalTime): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: LocalTime): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other /** @@ -505,12 +506,12 @@ public fun Validatable.isBeforeOrEqualTo(other: LocalTime): Constrai * validate((now.toInstant(timezone) + 5.seconds).toLocalDateTime(timezone)) // Failure (message: Must be before or equal to "2024-08-31T12:30:15") * ``` */ -public fun Validatable.isBeforeOrEqualTo(other: LocalDateTime): Constraint = +public fun GenericValidatable.isBeforeOrEqualTo(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it <= other } otherwiseBeforeOrEqual other //endregion //region isAfter -private infix fun Constraint.otherwiseAfter(other: Any) = otherwise { "Must be after \"$other\"" } +private infix fun GenericConstraint.otherwiseAfter(other: Any) = otherwise { "Must be after \"$other\"" } /** * The validatable [Instant] must be after [other] when this constraint is applied. @@ -527,7 +528,7 @@ private infix fun Constraint.otherwiseAfter(other: Any) = otherwise { "Must be a * validate(now - 5.seconds) // Failure (message: Must be after "2024-08-31T12:30:15Z") * ``` */ -public fun Validatable.isAfter(other: Instant): Constraint = +public fun GenericValidatable.isAfter(other: Instant): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other /** @@ -545,7 +546,7 @@ public fun Validatable.isAfter(other: Instant): Constraint = * validate(now.minus(2, DateTimeUnit.DAY)) // Failure (message: Must be after "2024-08-31") * ``` */ -public fun Validatable.isAfter(other: LocalDate): Constraint = +public fun GenericValidatable.isAfter(other: LocalDate): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other /** @@ -563,7 +564,7 @@ public fun Validatable.isAfter(other: LocalDate): Constraint = * validate(LocalTime.fromSecondOfDay(now.toSecondOfDay() - 5)) // Failure (message: Must be after "12:30:15") * ``` */ -public fun Validatable.isAfter(other: LocalTime): Constraint = +public fun GenericValidatable.isAfter(other: LocalTime): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other /** @@ -582,12 +583,12 @@ public fun Validatable.isAfter(other: LocalTime): Constraint = * validate((now.toInstant(timezone) - 5.seconds).toLocalDateTime(timezone)) // Failure (message: Must be after "2024-08-31T12:30:15") * ``` */ -public fun Validatable.isAfter(other: LocalDateTime): Constraint = +public fun GenericValidatable.isAfter(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it > other } otherwiseAfter other //endregion //region isAfterOrEqualTo -private infix fun Constraint.otherwiseAfterOrEqual(other: Any) = otherwise { "Must be after or equal to \"$other\"" } +private infix fun GenericConstraint.otherwiseAfterOrEqual(other: Any) = otherwise { "Must be after or equal to \"$other\"" } /** * The validatable [Instant] must be after or equal to [other] when this constraint is applied. @@ -604,7 +605,7 @@ private infix fun Constraint.otherwiseAfterOrEqual(other: Any) = otherwise { "Mu * validate(now - 5.seconds) // Failure (message: Must be after or equal to "2024-08-31T12:30:15Z") * ``` */ -public fun Validatable.isAfterOrEqualTo(other: Instant): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: Instant): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other /** @@ -622,7 +623,7 @@ public fun Validatable.isAfterOrEqualTo(other: Instant): Constraint = * validate(now.minus(2, DateTimeUnit.DAY)) // Failure (message: Must be after or equal to "2024-08-31") * ``` */ -public fun Validatable.isAfterOrEqualTo(other: LocalDate): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: LocalDate): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other /** @@ -640,7 +641,7 @@ public fun Validatable.isAfterOrEqualTo(other: LocalDate): Constrain * validate(LocalTime.fromSecondOfDay(now.toSecondOfDay() - 5)) // Failure (message: Must be after or equal to "12:30:15") * ``` */ -public fun Validatable.isAfterOrEqualTo(other: LocalTime): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: LocalTime): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other /** @@ -659,6 +660,6 @@ public fun Validatable.isAfterOrEqualTo(other: LocalTime): Constrain * validate((now.toInstant(timezone) - 5.seconds).toLocalDateTime(timezone)) // Failure (message: Must be after or equal to "2024-08-31T12:30:15") * ``` */ -public fun Validatable.isAfterOrEqualTo(other: LocalDateTime): Constraint = +public fun GenericValidatable.isAfterOrEqualTo(other: LocalDateTime): GenericConstraint = constrainIfNotNull { it >= other } otherwiseAfterOrEqual other //endregion diff --git a/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/AkkurateConfig.kt b/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/AkkurateConfig.kt index 59d28b7e..4433307d 100644 --- a/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/AkkurateConfig.kt +++ b/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/AkkurateConfig.kt @@ -36,8 +36,8 @@ public class AkkurateConfig internal constructor() { * Registers a new [validator], which will be executed for each deserialized response body. * The [contextProvider] is called on each execution, then its result is feed to the validator. */ - public inline fun registerValidator( - validator: Validator.Runner.WithContext, + public inline fun registerValidator( + validator: Validator.Runner.WithContext, noinline contextProvider: suspend () -> ContextType, ) { registerValidator(ClientValidator.Runner.WithContext(ValueType::class, validator, contextProvider)) @@ -46,7 +46,7 @@ public class AkkurateConfig internal constructor() { /** * Registers a new [validator], which will be executed for each deserialized response body. */ - public inline fun registerValidator(validator: Validator.Runner) { + public inline fun registerValidator(validator: Validator.Runner) { registerValidator(ClientValidator.Runner(ValueType::class, validator)) } @@ -54,8 +54,8 @@ public class AkkurateConfig internal constructor() { * Registers a new [validator], which will be executed for each deserialized response body. * The [contextProvider] is called on each execution, then its result is feed to the validator. */ - public inline fun registerValidator( - validator: Validator.SuspendableRunner.WithContext, + public inline fun registerValidator( + validator: Validator.SuspendableRunner.WithContext, noinline contextProvider: suspend () -> ContextType, ) { registerValidator(ClientValidator.SuspendableRunner.WithContext(ValueType::class, validator, contextProvider)) @@ -64,7 +64,7 @@ public class AkkurateConfig internal constructor() { /** * Registers a new [validator], which will be executed for each deserialized response body. */ - public inline fun registerValidator(validator: Validator.SuspendableRunner) { + public inline fun registerValidator(validator: Validator.SuspendableRunner) { registerValidator(ClientValidator.SuspendableRunner(ValueType::class, validator)) } } diff --git a/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/ClientValidator.kt b/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/ClientValidator.kt index 1e9feb6e..00cb7cb8 100644 --- a/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/ClientValidator.kt +++ b/akkurate-ktor-client/src/commonMain/kotlin/dev/nesk/akkurate/ktor/client/ClientValidator.kt @@ -35,9 +35,9 @@ public interface ClientValidator { /** * A validator for [Validator.Runner] instances. */ - public class Runner( + public class Runner( private val valueType: KClass<*>, - private val validator: Validator.Runner, + private val validator: Validator.Runner, ) : ClientValidator { override suspend fun validate(value: Any?) { if (!valueType.isInstance(value)) return @@ -49,9 +49,9 @@ public interface ClientValidator { /** * A validator for [Validator.Runner.WithContext] instances. */ - public class WithContext( + public class WithContext( private val valueType: KClass<*>, - private val validator: Validator.Runner.WithContext, + private val validator: Validator.Runner.WithContext, private val contextProvider: suspend () -> ContextType, ) : ClientValidator { override suspend fun validate(value: Any?) { @@ -66,9 +66,9 @@ public interface ClientValidator { /** * A validator for [Validator.SuspendableRunner] instances. */ - public class SuspendableRunner( + public class SuspendableRunner( private val valueType: KClass<*>, - private val validator: Validator.SuspendableRunner, + private val validator: Validator.SuspendableRunner, ) : ClientValidator { override suspend fun validate(value: Any?) { if (!valueType.isInstance(value)) return @@ -80,9 +80,9 @@ public interface ClientValidator { /** * A validator for [Validator.SuspendableRunner.WithContext] instances. */ - public class WithContext( + public class WithContext( private val valueType: KClass<*>, - private val validator: Validator.SuspendableRunner.WithContext, + private val validator: Validator.SuspendableRunner.WithContext, private val contextProvider: suspend () -> ContextType, ) : ClientValidator { override suspend fun validate(value: Any?) { diff --git a/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/AkkurateConfig.kt b/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/AkkurateConfig.kt index 3d33cede..7ebd043b 100644 --- a/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/AkkurateConfig.kt +++ b/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/AkkurateConfig.kt @@ -18,6 +18,7 @@ package dev.nesk.akkurate.ktor.server import dev.nesk.akkurate.constraints.ConstraintViolationSet +import dev.nesk.akkurate.constraints.GenericConstraintViolationSet import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* diff --git a/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/RegisterValidator.kt b/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/RegisterValidator.kt index 1a5b7e30..68f6eba5 100644 --- a/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/RegisterValidator.kt +++ b/akkurate-ktor-server/src/commonMain/kotlin/dev/nesk/akkurate/ktor/server/RegisterValidator.kt @@ -18,6 +18,7 @@ package dev.nesk.akkurate.ktor.server import dev.nesk.akkurate.Validator +import dev.nesk.akkurate.validatables.DefaultMetadataType import io.ktor.server.plugins.requestvalidation.* import io.ktor.server.request.* @@ -26,7 +27,7 @@ import io.ktor.server.request.* * The [contextProvider] is called on each execution, then its result is feed to the validator. */ public inline fun RequestValidationConfig.registerValidator( - validator: Validator.Runner.WithContext, + validator: Validator.Runner.WithContext, noinline contextProvider: suspend () -> ContextType, ) { validate { @@ -38,7 +39,7 @@ public inline fun RequestValidationConfig /** * Registers a new [validator], which will be executed for each [received][receive] request body. */ -public inline fun RequestValidationConfig.registerValidator(validator: Validator.Runner) { +public inline fun RequestValidationConfig.registerValidator(validator: Validator.Runner) { validate { validator(it).orThrow() ValidationResult.Valid @@ -50,7 +51,7 @@ public inline fun RequestValidationConfig.registerVali * The [contextProvider] is called on each execution, then its result is feed to the validator. */ public inline fun RequestValidationConfig.registerValidator( - validator: Validator.SuspendableRunner.WithContext, + validator: Validator.SuspendableRunner.WithContext, noinline contextProvider: suspend () -> ContextType, ) { validate { @@ -62,7 +63,7 @@ public inline fun RequestValidationConfig /** * Registers a new [validator], which will be executed for each [received][receive] request body. */ -public inline fun RequestValidationConfig.registerValidator(validator: Validator.SuspendableRunner) { +public inline fun RequestValidationConfig.registerValidator(validator: Validator.SuspendableRunner) { validate { validator(it).orThrow() ValidationResult.Valid diff --git a/akkurate-ktor-server/src/commonTest/kotlin/dev/nesk/akkurate/ktor/server/AkkurateTest.kt b/akkurate-ktor-server/src/commonTest/kotlin/dev/nesk/akkurate/ktor/server/AkkurateTest.kt index 2cad23b0..55a2f153 100644 --- a/akkurate-ktor-server/src/commonTest/kotlin/dev/nesk/akkurate/ktor/server/AkkurateTest.kt +++ b/akkurate-ktor-server/src/commonTest/kotlin/dev/nesk/akkurate/ktor/server/AkkurateTest.kt @@ -84,7 +84,7 @@ class AkkurateTest { val response = client.sendBoolean(false) assertEquals( - "ConstraintViolationSet(messages=[ConstraintViolation(message='Must be true', path=[])])", + "ConstraintViolationSet(messages=[ConstraintViolation(message='Must be true', path=[], metadata={})])", response.bodyAsText(), "The body contains a string representation of ConstraintViolationSet." )