diff --git a/api/konform.api b/api/konform.api index 386f03e..3d8d089 100644 --- a/api/konform.api +++ b/api/konform.api @@ -195,6 +195,13 @@ public final class io/konform/validation/builders/RequiredValidationBuilder$Comp public final fun buildWithNew (Lkotlin/jvm/functions/Function1;)Lio/konform/validation/types/RequireNotNullValidation; } +public final class io/konform/validation/constraints/ComparableConstraintsKt { + public static final fun exclusiveMaximum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Comparable;)Lio/konform/validation/Constraint; + public static final fun exclusiveMinimum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Comparable;)Lio/konform/validation/Constraint; + public static final fun maximum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Comparable;)Lio/konform/validation/Constraint; + public static final fun minimum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Comparable;)Lio/konform/validation/Constraint; +} + public final class io/konform/validation/constraints/EnumConstraintsKt { public static final fun const (Lio/konform/validation/ValidationBuilder;Ljava/lang/Object;)Lio/konform/validation/Constraint; public static final fun enum (Lio/konform/validation/ValidationBuilder;[Ljava/lang/Object;)Lio/konform/validation/Constraint; @@ -221,10 +228,14 @@ public final class io/konform/validation/constraints/MapConstraintsKt { } public final class io/konform/validation/constraints/NumberConstraintsKt { - public static final fun exclusiveMaximum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Number;)Lio/konform/validation/Constraint; - public static final fun exclusiveMinimum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Number;)Lio/konform/validation/Constraint; - public static final fun maximum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Number;)Lio/konform/validation/Constraint; - public static final fun minimum (Lio/konform/validation/ValidationBuilder;Ljava/lang/Number;)Lio/konform/validation/Constraint; + public static final fun exclusiveMaximumDouble (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun exclusiveMaximumFloat (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun exclusiveMinimumDouble (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun exclusiveMinimumFloat (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun maximumDouble (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun maximumFloat (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun minimumDouble (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; + public static final fun minimumFloat (Lio/konform/validation/ValidationBuilder;I)Lio/konform/validation/Constraint; public static final fun multipleOf (Lio/konform/validation/ValidationBuilder;Ljava/lang/Number;)Lio/konform/validation/Constraint; } diff --git a/src/commonMain/kotlin/io/konform/validation/constraints/ComparableConstraints.kt b/src/commonMain/kotlin/io/konform/validation/constraints/ComparableConstraints.kt new file mode 100644 index 0000000..ee114e9 --- /dev/null +++ b/src/commonMain/kotlin/io/konform/validation/constraints/ComparableConstraints.kt @@ -0,0 +1,16 @@ +package io.konform.validation.constraints + +import io.konform.validation.Constraint +import io.konform.validation.ValidationBuilder + +public fun ValidationBuilder.maximum(maximumInclusive: Comparable): Constraint = + constrain("must be at most '$maximumInclusive'") { maximumInclusive >= it } + +public fun ValidationBuilder.exclusiveMaximum(maximumExclusive: Comparable): Constraint = + constrain("must be less than '$maximumExclusive'") { maximumExclusive > it } + +public fun ValidationBuilder.minimum(minimumInclusive: Comparable): Constraint = + constrain("must be at least '$minimumInclusive'") { minimumInclusive <= it } + +public fun ValidationBuilder.exclusiveMinimum(minimumExclusive: Comparable): Constraint = + constrain("must be greater than '$minimumExclusive'") { minimumExclusive < it } diff --git a/src/commonMain/kotlin/io/konform/validation/constraints/NumberConstraints.kt b/src/commonMain/kotlin/io/konform/validation/constraints/NumberConstraints.kt index c51e2fb..6535cc9 100644 --- a/src/commonMain/kotlin/io/konform/validation/constraints/NumberConstraints.kt +++ b/src/commonMain/kotlin/io/konform/validation/constraints/NumberConstraints.kt @@ -2,6 +2,7 @@ package io.konform.validation.constraints import io.konform.validation.Constraint import io.konform.validation.ValidationBuilder +import kotlin.jvm.JvmName import kotlin.math.roundToInt public fun ValidationBuilder.multipleOf(factor: Number): Constraint { @@ -13,26 +14,34 @@ public fun ValidationBuilder.multipleOf(factor: Number): Constra } } -public fun ValidationBuilder.maximum(maximumInclusive: Number): Constraint = - addConstraint( - "must be at most '{0}'", - maximumInclusive.toString(), - ) { it.toDouble() <= maximumInclusive.toDouble() } - -public fun ValidationBuilder.exclusiveMaximum(maximumExclusive: Number): Constraint = - addConstraint( - "must be less than '{0}'", - maximumExclusive.toString(), - ) { it.toDouble() < maximumExclusive.toDouble() } - -public fun ValidationBuilder.minimum(minimumInclusive: Number): Constraint = - addConstraint( - "must be at least '{0}'", - minimumInclusive.toString(), - ) { it.toDouble() >= minimumInclusive.toDouble() } - -public fun ValidationBuilder.exclusiveMinimum(minimumExclusive: Number): Constraint = - addConstraint( - "must be greater than '{0}'", - minimumExclusive.toString(), - ) { it.toDouble() > minimumExclusive.toDouble() } +@JvmName("maximumFloat") +public fun ValidationBuilder.maximum(maximumInclusive: Int): Constraint = + constrain("must be at most '$maximumInclusive'") { it <= maximumInclusive.toFloat() } + +@JvmName("maximumDouble") +public fun ValidationBuilder.maximum(maximumInclusive: Int): Constraint = + constrain("must be at most '$maximumInclusive'") { it <= maximumInclusive.toDouble() } + +@JvmName("exclusiveMaximumFloat") +public fun ValidationBuilder.exclusiveMaximum(maximumExclusive: Int): Constraint = + constrain("must be at most '$maximumExclusive'") { it < maximumExclusive.toFloat() } + +@JvmName("exclusiveMaximumDouble") +public fun ValidationBuilder.exclusiveMaximum(maximumExclusive: Int): Constraint = + constrain("must be at most '$maximumExclusive'") { it < maximumExclusive.toDouble() } + +@JvmName("minimumFloat") +public fun ValidationBuilder.minimum(minimumInclusive: Int): Constraint = + constrain("must be at most '$minimumInclusive'") { it >= minimumInclusive.toFloat() } + +@JvmName("minimumDouble") +public fun ValidationBuilder.minimum(minimumInclusive: Int): Constraint = + constrain("must be at most '$minimumInclusive'") { it >= minimumInclusive.toDouble() } + +@JvmName("exclusiveMinimumFloat") +public fun ValidationBuilder.exclusiveMinimum(minimumExclusive: Int): Constraint = + constrain("must be at most '$minimumExclusive'") { it > minimumExclusive.toFloat() } + +@JvmName("exclusiveMinimumDouble") +public fun ValidationBuilder.exclusiveMinimum(minimumExclusive: Int): Constraint = + constrain("must be at most '$minimumExclusive'") { it > minimumExclusive.toDouble() } diff --git a/src/commonMain/kotlin/io/konform/validation/jsonschema/JsonSchema.kt b/src/commonMain/kotlin/io/konform/validation/jsonschema/JsonSchema.kt index 7565a47..ce7852a 100644 --- a/src/commonMain/kotlin/io/konform/validation/jsonschema/JsonSchema.kt +++ b/src/commonMain/kotlin/io/konform/validation/jsonschema/JsonSchema.kt @@ -4,14 +4,10 @@ import io.konform.validation.Constraint import io.konform.validation.ValidationBuilder import io.konform.validation.constraints.const as movedConst import io.konform.validation.constraints.enum as movedEnum -import io.konform.validation.constraints.exclusiveMaximum as movedExclusiveMaximum -import io.konform.validation.constraints.exclusiveMinimum as movedExclusiveMinimum import io.konform.validation.constraints.maxLength as movedMaxLength import io.konform.validation.constraints.maxProperties as movedMaxProperties -import io.konform.validation.constraints.maximum as movedMaximum import io.konform.validation.constraints.minLength as movedMinLength import io.konform.validation.constraints.minProperties as movedMinProperties -import io.konform.validation.constraints.minimum as movedMinimum import io.konform.validation.constraints.multipleOf as movedMultipleOf import io.konform.validation.constraints.pattern as movedPattern import io.konform.validation.constraints.type as movedType @@ -81,27 +77,41 @@ public fun ValidationBuilder.multipleOf(factor: Number): Constra "Moved to io.konform.validation.constraints", ReplaceWith("maximum(maximumInclusive)", "io.konform.validation.constraints.maximum"), ) -public fun ValidationBuilder.maximum(maximumInclusive: Number): Constraint = movedMaximum(maximumInclusive) +public fun ValidationBuilder.maximum(maximumInclusive: Number): Constraint = + addConstraint( + "must be at most '{0}'", + maximumInclusive.toString(), + ) { it.toDouble() <= maximumInclusive.toDouble() } @Deprecated( "Moved to io.konform.validation.constraints", ReplaceWith("exclusiveMaximum(maximumExclusive)", "io.konform.validation.constraints.exclusiveMaximum"), ) public fun ValidationBuilder.exclusiveMaximum(maximumExclusive: Number): Constraint = - movedExclusiveMaximum(maximumExclusive) + addConstraint( + "must be less than '{0}'", + maximumExclusive.toString(), + ) { it.toDouble() < maximumExclusive.toDouble() } @Deprecated( "Moved to io.konform.validation.constraints", ReplaceWith("minimum(minimumInclusive)", "io.konform.validation.constraints.minimum"), ) -public fun ValidationBuilder.minimum(minimumInclusive: Number): Constraint = movedMinimum(minimumInclusive) +public fun ValidationBuilder.minimum(minimumInclusive: Number): Constraint = + addConstraint( + "must be at least '{0}'", + minimumInclusive.toString(), + ) { it.toDouble() >= minimumInclusive.toDouble() } @Deprecated( "Moved to io.konform.validation.constraints", ReplaceWith("exclusiveMinimum(minimumExclusive)", "io.konform.validation.constraints.exclusiveMinimum"), ) public fun ValidationBuilder.exclusiveMinimum(minimumExclusive: Number): Constraint = - movedExclusiveMinimum(minimumExclusive) + addConstraint( + "must be greater than '{0}'", + minimumExclusive.toString(), + ) { it.toDouble() > minimumExclusive.toDouble() } @Suppress("UNCHECKED_CAST") @Deprecated( diff --git a/src/commonTest/kotlin/io/konform/validation/constraints/ConstraintsTest.kt b/src/commonTest/kotlin/io/konform/validation/constraints/ConstraintsTest.kt index 5727cda..c02adee 100644 --- a/src/commonTest/kotlin/io/konform/validation/constraints/ConstraintsTest.kt +++ b/src/commonTest/kotlin/io/konform/validation/constraints/ConstraintsTest.kt @@ -6,22 +6,6 @@ import io.konform.validation.ValidationResult import io.konform.validation.constraints.ConstraintsTest.TCPPacket.ACK import io.konform.validation.constraints.ConstraintsTest.TCPPacket.SYN import io.konform.validation.constraints.ConstraintsTest.TCPPacket.SYNACK -import io.konform.validation.constraints.const -import io.konform.validation.constraints.exclusiveMaximum -import io.konform.validation.constraints.exclusiveMinimum -import io.konform.validation.constraints.maxItems -import io.konform.validation.constraints.maxLength -import io.konform.validation.constraints.maxProperties -import io.konform.validation.constraints.maximum -import io.konform.validation.constraints.minItems -import io.konform.validation.constraints.minLength -import io.konform.validation.constraints.minProperties -import io.konform.validation.constraints.minimum -import io.konform.validation.constraints.multipleOf -import io.konform.validation.constraints.pattern -import io.konform.validation.constraints.type -import io.konform.validation.constraints.uniqueItems -import io.konform.validation.constraints.uuid import io.konform.validation.countFieldsWithErrors import io.konform.validation.path.ValidationPath import io.kotest.assertions.konform.shouldBeInvalid @@ -29,6 +13,7 @@ import io.kotest.assertions.konform.shouldBeValid import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldMatch import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -146,94 +131,224 @@ class ConstraintsTest { @Test fun maximumConstraint() { - val validation = Validation { maximum(10) } + assertEquals( + Valid(10), + Validation { maximum(10) }(10), + ) - assertEquals>(Valid(Double.NEGATIVE_INFINITY), validation(Double.NEGATIVE_INFINITY)) - assertEquals>(Valid(-10), validation(-10)) - assertEquals>(Valid(9), validation(9)) - assertEquals>(Valid(10), validation(10)) - assertEquals>(Valid(10.0), validation(10.0)) + assertEquals( + Valid(10), + Validation { maximum(10) }(10), + ) - assertEquals(1, countFieldsWithErrors(validation(10.00001))) - assertEquals(1, countFieldsWithErrors(validation(11))) - assertEquals(1, countFieldsWithErrors(validation(Double.POSITIVE_INFINITY))) + assertEquals( + Valid(10.0f), + Validation { maximum(10) }(10.0f), + ) + + assertEquals( + Valid(10.0), + Validation { maximum(10) }(10.0), + ) - assertEquals>( + assertEquals( + Valid("a"), + Validation { maximum("b") }("a"), + ) + + assertEquals( + Valid("b"), + Validation { maximum("b") }("b"), + ) + + assertEquals( + Valid(10.0), + Validation { maximum(10) }(10.0), + ) + + val validation = Validation { maximum(10.0) } + + assertEquals(Valid(9.0), validation(9.0)) + assertEquals(Valid(10.0), validation(10.0)) + assertEquals(Valid(-10.0), validation(-10.0)) + assertEquals(Valid(Double.NEGATIVE_INFINITY), validation(Double.NEGATIVE_INFINITY)) + assertEquals( Valid(Double.POSITIVE_INFINITY), - Validation { - maximum(Double.POSITIVE_INFINITY) - }(Double.POSITIVE_INFINITY), + Validation { maximum(Double.POSITIVE_INFINITY) }(Double.POSITIVE_INFINITY), ) - assertEquals("must be at most '10'", validation(11).get()[0]) + assertEquals(1, countFieldsWithErrors(validation(10.00001))) + assertEquals(1, countFieldsWithErrors(validation(11.0))) + assertEquals(1, countFieldsWithErrors(validation(Double.POSITIVE_INFINITY))) + + val invalid = validation shouldBeInvalid 11.0 + invalid.errors shouldHaveSize 1 + // Small difference in numbers between kotlin JS and others + invalid.errors[0].message shouldMatch "must be at most '10(\\.0)?'".toRegex() } @Test fun exclusiveMaximumConstraint() { - val validation = Validation { exclusiveMaximum(10) } + assertEquals( + Valid(9), + Validation { exclusiveMaximum(10) }(9), + ) + + assertEquals( + Valid(9), + Validation { exclusiveMaximum(10) }(9), + ) + + assertEquals( + Valid(9.0f), + Validation { exclusiveMaximum(10) }(9.0f), + ) - assertEquals>(Valid(Double.NEGATIVE_INFINITY), validation(Double.NEGATIVE_INFINITY)) - assertEquals>(Valid(-10), validation(-10)) - assertEquals>(Valid(9), validation(9)) - assertEquals>(Valid(9.99999999), validation(9.99999999)) + assertEquals( + Valid(9.0), + Validation { exclusiveMaximum(10) }(9.0), + ) + + assertEquals( + Valid("a"), + Validation { exclusiveMaximum("b") }("a"), + ) + + assertEquals( + Valid(10.0), + Validation { exclusiveMaximum(11) }(10.0), + ) + + val validation = Validation { exclusiveMaximum(10.0) } + + assertEquals(Valid(9.0), validation(9.0)) + assertEquals(Valid(9.99999999), validation(9.99999999)) + assertEquals(Valid(-10.0), validation(-10.0)) + assertEquals(Valid(Double.NEGATIVE_INFINITY), validation(Double.NEGATIVE_INFINITY)) - assertEquals(1, countFieldsWithErrors(validation(10))) assertEquals(1, countFieldsWithErrors(validation(10.0))) assertEquals(1, countFieldsWithErrors(validation(10.00001))) - assertEquals(1, countFieldsWithErrors(validation(11))) + assertEquals(1, countFieldsWithErrors(validation(11.0))) assertEquals(1, countFieldsWithErrors(validation(Double.POSITIVE_INFINITY))) assertEquals( 1, - countFieldsWithErrors(Validation { exclusiveMaximum(Double.POSITIVE_INFINITY) }(Double.POSITIVE_INFINITY)), + countFieldsWithErrors(Validation { exclusiveMaximum(Double.POSITIVE_INFINITY) }(Double.POSITIVE_INFINITY)), ) - assertEquals("must be less than '10'", validation(11).get()[0]) + val invalid = validation shouldBeInvalid 11.0 + invalid.errors shouldHaveSize 1 + // Small difference in numbers between kotlin JS and others + invalid.errors[0].message shouldMatch "must be less than '10(\\.0)?'".toRegex() } @Test fun minimumConstraint() { - val validation = Validation { minimum(10) } + assertEquals( + Valid(10), + Validation { minimum(10) }(10), + ) - assertEquals>(Valid(Double.POSITIVE_INFINITY), validation(Double.POSITIVE_INFINITY)) - assertEquals>(Valid(20), validation(20)) - assertEquals>(Valid(11), validation(11)) - assertEquals>(Valid(10.1), validation(10.1)) - assertEquals>(Valid(10.0), validation(10.0)) + assertEquals( + Valid(10), + Validation { minimum(10) }(10), + ) - assertEquals(1, countFieldsWithErrors(validation(9.99999999999))) - assertEquals(1, countFieldsWithErrors(validation(8))) - assertEquals(1, countFieldsWithErrors(validation(Double.NEGATIVE_INFINITY))) + assertEquals( + Valid(10.0f), + Validation { minimum(10) }(10.0f), + ) + + assertEquals( + Valid(10.0), + Validation { minimum(10) }(10.0), + ) + + assertEquals( + Valid("b"), + Validation { minimum("a") }("b"), + ) + + assertEquals( + Valid("a"), + Validation { minimum("a") }("a"), + ) - assertEquals>( + assertEquals( + Valid(10.0), + Validation { minimum(10) }(10.0), + ) + + val validation = Validation { minimum(10.0) } + + assertEquals(Valid(11.0), validation(11.0)) + assertEquals(Valid(10.0), validation(10.0)) + assertEquals(Valid(Double.POSITIVE_INFINITY), validation(Double.POSITIVE_INFINITY)) + assertEquals( Valid(Double.NEGATIVE_INFINITY), - Validation { - minimum(Double.NEGATIVE_INFINITY) - }(Double.NEGATIVE_INFINITY), + Validation { minimum(Double.NEGATIVE_INFINITY) }(Double.NEGATIVE_INFINITY), ) - assertEquals("must be at least '10'", validation(9).get()[0]) + assertEquals(1, countFieldsWithErrors(validation(9.99999))) + assertEquals(1, countFieldsWithErrors(validation(9.0))) + assertEquals(1, countFieldsWithErrors(validation(Double.NEGATIVE_INFINITY))) + + val invalid = validation shouldBeInvalid 9.0 + invalid.errors shouldHaveSize 1 + // Small difference in numbers between kotlin JS and others + invalid.errors[0].message shouldMatch "must be at least '10(\\.0)?'".toRegex() } @Test - fun minimumExclusiveConstraint() { - val validation = Validation { exclusiveMinimum(10) } + fun exclusiveMinimumConstraint() { + assertEquals( + Valid(11), + Validation { exclusiveMinimum(10) }(11), + ) - assertEquals>(Valid(Double.POSITIVE_INFINITY), validation(Double.POSITIVE_INFINITY)) - assertEquals>(Valid(20), validation(20)) - assertEquals>(Valid(11), validation(11)) - assertEquals>(Valid(10.1), validation(10.1)) + assertEquals( + Valid(11), + Validation { exclusiveMinimum(10) }(11), + ) - assertEquals(1, countFieldsWithErrors(validation(10))) - assertEquals(1, countFieldsWithErrors(validation(10.0))) - assertEquals(1, countFieldsWithErrors(validation(9.99999999999))) - assertEquals(1, countFieldsWithErrors(validation(8))) + assertEquals( + Valid(11.0f), + Validation { exclusiveMinimum(10) }(11.0f), + ) + + assertEquals( + Valid(11.0), + Validation { exclusiveMinimum(10) }(11.0), + ) + + assertEquals( + Valid("b"), + Validation { exclusiveMinimum("a") }("b"), + ) + + assertEquals( + Valid(10.0), + Validation { minimum(9) }(10.0), + ) + + val validation = Validation { exclusiveMinimum(10.0) } + + assertEquals(Valid(11.0), validation(11.0)) + assertEquals(Valid(10.00000001), validation(10.00000001)) + assertEquals(Valid(Double.POSITIVE_INFINITY), validation(Double.POSITIVE_INFINITY)) + + assertEquals(1, countFieldsWithErrors(validation(9.0))) + assertEquals(1, countFieldsWithErrors(validation(9.99999))) + assertEquals(1, countFieldsWithErrors(validation(-9.0))) assertEquals(1, countFieldsWithErrors(validation(Double.NEGATIVE_INFINITY))) assertEquals( 1, - countFieldsWithErrors(Validation { exclusiveMinimum(Double.NEGATIVE_INFINITY) }(Double.NEGATIVE_INFINITY)), + countFieldsWithErrors(Validation { exclusiveMinimum(Double.NEGATIVE_INFINITY) }(Double.NEGATIVE_INFINITY)), ) - assertEquals("must be greater than '10'", validation(9).get()[0]) + val invalid = validation shouldBeInvalid 9.0 + invalid.errors shouldHaveSize 1 + // Small difference in numbers between kotlin JS and others + invalid.errors[0].message shouldMatch "must be greater than '10(\\.0)?'".toRegex() } @Test