Skip to content

Commit

Permalink
Implement constraints using Comparable interface
Browse files Browse the repository at this point in the history
Allow building the constraints

- `maximum` and `exclusiveMaximum`,
- `minimum` and `exclusiveMinimum`

for any type `T`, using a `Comparable<T>`.

Previously, these constraints were only available for subtypes of
`Number` and relied on converting to `Double` for comparing numbers of
different types. This could cause issues when comparing values which
cannot be cleanly converted from the original number type.

For compatibility and convenience, we provide overloads for these
constraints that accept an `Int` as the limit value as this seems like
a rather common case.

Closes #180
  • Loading branch information
pmeinhardt committed Nov 25, 2024
1 parent f3fc6d3 commit 24eb7d6
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.konform.validation.constraints

import io.konform.validation.Constraint
import io.konform.validation.ValidationBuilder

public fun <T : Any> ValidationBuilder<T>.maximum(maximumInclusive: Comparable<T>): Constraint<T> =
constrain("must be at most '$maximumInclusive'") { maximumInclusive >= it }

public fun <T : Any> ValidationBuilder<T>.exclusiveMaximum(maximumExclusive: Comparable<T>): Constraint<T> =
constrain("must be less than '$maximumExclusive'") { maximumExclusive > it }

public fun <T : Any> ValidationBuilder<T>.minimum(minimumInclusive: Comparable<T>): Constraint<T> =
constrain("must be at least '$minimumInclusive'") { minimumInclusive <= it }

public fun <T : Any> ValidationBuilder<T>.exclusiveMinimum(minimumExclusive: Comparable<T>): Constraint<T> =
constrain("must be greater than '$minimumExclusive'") { minimumExclusive < it }
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constraint<T> {
Expand All @@ -13,26 +14,50 @@ public fun <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constra
}
}

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

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

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

public fun <T : Number> ValidationBuilder<T>.exclusiveMinimum(minimumExclusive: Number): Constraint<T> =
addConstraint(
"must be greater than '{0}'",
minimumExclusive.toString(),
) { it.toDouble() > minimumExclusive.toDouble() }
@JvmName("maximumLong")
public fun ValidationBuilder<Long>.maximum(maximumInclusive: Int): Constraint<Long> =
constrain("must be at most '$maximumInclusive'") { it <= maximumInclusive.toLong() }

@JvmName("maximumFloat")
public fun ValidationBuilder<Float>.maximum(maximumInclusive: Int): Constraint<Float> =
constrain("must be at most '$maximumInclusive'") { it <= maximumInclusive.toFloat() }

@JvmName("maximumDouble")
public fun ValidationBuilder<Double>.maximum(maximumInclusive: Int): Constraint<Double> =
constrain("must be at most '$maximumInclusive'") { it <= maximumInclusive.toDouble() }

@JvmName("exclusiveMaximumLong")
public fun ValidationBuilder<Long>.exclusiveMaximum(maximumExclusive: Int): Constraint<Long> =
constrain("must be at most '$maximumExclusive'") { it < maximumExclusive.toLong() }

@JvmName("exclusiveMaximumFloat")
public fun ValidationBuilder<Float>.exclusiveMaximum(maximumExclusive: Int): Constraint<Float> =
constrain("must be at most '$maximumExclusive'") { it < maximumExclusive.toFloat() }

@JvmName("exclusiveMaximumDouble")
public fun ValidationBuilder<Double>.exclusiveMaximum(maximumExclusive: Int): Constraint<Double> =
constrain("must be at most '$maximumExclusive'") { it < maximumExclusive.toDouble() }

@JvmName("minimumLong")
public fun ValidationBuilder<Long>.minimum(minimumInclusive: Int): Constraint<Long> =
constrain("must be at most '$minimumInclusive'") { it >= minimumInclusive.toLong() }

@JvmName("minimumFloat")
public fun ValidationBuilder<Float>.minimum(minimumInclusive: Int): Constraint<Float> =
constrain("must be at most '$minimumInclusive'") { it >= minimumInclusive.toFloat() }

@JvmName("minimumDouble")
public fun ValidationBuilder<Double>.minimum(minimumInclusive: Int): Constraint<Double> =
constrain("must be at most '$minimumInclusive'") { it >= minimumInclusive.toDouble() }

@JvmName("exclusiveMinimumLong")
public fun ValidationBuilder<Long>.exclusiveMinimum(minimumExclusive: Int): Constraint<Long> =
constrain("must be at most '$minimumExclusive'") { it > minimumExclusive.toLong() }

@JvmName("exclusiveMinimumFloat")
public fun ValidationBuilder<Float>.exclusiveMinimum(minimumExclusive: Int): Constraint<Float> =
constrain("must be at most '$minimumExclusive'") { it > minimumExclusive.toFloat() }

@JvmName("exclusiveMinimumDouble")
public fun ValidationBuilder<Double>.exclusiveMinimum(minimumExclusive: Int): Constraint<Double> =
constrain("must be at most '$minimumExclusive'") { it > minimumExclusive.toDouble() }
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,27 +77,41 @@ public fun <T : Number> ValidationBuilder<T>.multipleOf(factor: Number): Constra
"Moved to io.konform.validation.constraints",
ReplaceWith("maximum(maximumInclusive)", "io.konform.validation.constraints.maximum"),
)
public fun <T : Number> ValidationBuilder<T>.maximum(maximumInclusive: Number): Constraint<T> = movedMaximum(maximumInclusive)
public fun <T : Number> ValidationBuilder<T>.maximum(maximumInclusive: Number): Constraint<T> =
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 <T : Number> ValidationBuilder<T>.exclusiveMaximum(maximumExclusive: Number): Constraint<T> =
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 <T : Number> ValidationBuilder<T>.minimum(minimumInclusive: Number): Constraint<T> = movedMinimum(minimumInclusive)
public fun <T : Number> ValidationBuilder<T>.minimum(minimumInclusive: Number): Constraint<T> =
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 <T : Number> ValidationBuilder<T>.exclusiveMinimum(minimumExclusive: Number): Constraint<T> =
movedExclusiveMinimum(minimumExclusive)
addConstraint(
"must be greater than '{0}'",
minimumExclusive.toString(),
) { it.toDouble() > minimumExclusive.toDouble() }

@Suppress("UNCHECKED_CAST")
@Deprecated(
Expand Down
Loading

0 comments on commit 24eb7d6

Please sign in to comment.