Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
@file:OptIn(ExperimentalTypeInference::class)
package io.kform.validations
import io.kform.Validation
import io.kform.ValidationContext
import io.kform.ValidationIssue
import io.kform.ValidationIssueSeverity
import io.kform.datatypes.Table
import io.kform.validations.UniqueItems.Companion.DEFAULT_CODE
import io.kform.validations.UniqueItemsBy.Companion.DEFAULT_CODE
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmOverloads
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
/**
* Validation that ensures that a value does not contain repeated items, where the uniqueness of an
* item is determined by its key as returned by [selector]. Values of type [Collection], [Array]
* (including variants), and [Table] are supported.
*
* Depending on [emitAllRepetitions], an issue with the provided [code] (defaults to [DEFAULT_CODE])
* is emitted for each pair of repeated items (when `true`), or only for the first found repetition
* (when `false`). [emitAllRepetitions] defaults to `true`.
*
* Each emitted issue has a `firstIndex` and `secondIndex` data properties with the two indices of
* the conflicting items.
*
* [Pair]s, [Triple]s, and [List]s work well to represent composite keys. E.g. say that you have a
* value of items that should be unique in respect to their fields `A` and `B`; you can use
* `UniqueBy` as such:
* ```kotlin
* UniqueBy { Pair(it.A, it.B) }
* ```
*
* When the result of calling [selector] on an item is `null`, that item is always considered
* unique. I.e. if `selector(A) == null` and `selector(B) == null`, then `A` is considered different
* to `B`.
*
* If you wish to treat multiple `null` values as equal to one another, consider wrapping said
* values in an object (e.g. a list with a single item).
*
* This validation is **not** stateful and depends on all descendants of the value.
*
* @property emitAllRepetitions Whether to emit an issue per each repeated item or only for the
* first repetition.
* @property code Issue code to use when two items of the value are repeated.
* @property severity Severity of the issue emitted when two items of the value are repeated.
* @property selector Selector function used to specify the key of an item of the value.
*/
public open class UniqueItemsBy
@JvmOverloads
constructor(
public val emitAllRepetitions: Boolean = true,
public val code: String = DEFAULT_CODE,
public val severity: ValidationIssueSeverity = ValidationIssueSeverity.Error,
@BuilderInference public val selector: (item: T) -> TKey?,
) : Validation() {
override fun toString(): String = "UniqueItemsBy"
override val dependsOnDescendants: Boolean = true
@Suppress("UNCHECKED_CAST")
override fun ValidationContext.validate(): Flow = flow {
val map = HashMap(size(value))
for ((i, el) in iterableWithIndex(value)) {
val key = selector(el as T)
if (key != null) {
val conflict = map[key]
if (conflict != null) {
emit(
ValidationIssue(
code,
severity,
mapOf("firstIndex" to "$conflict", "secondIndex" to "$i")
)
)
if (!emitAllRepetitions) {
break
}
} else {
map[key] = i
}
}
}
}
public companion object {
/** Default issue code representing that two items of the value are repeated. */
public const val DEFAULT_CODE: String = "itemsRepeated"
}
}
/**
* Validation that ensures that a value does not contain repeated items. Values of type
* [Collection], [Array] (including variants), and [Table] are supported.
*
* Depending on [emitAllRepetitions], an issue with the provided [code] (defaults to [DEFAULT_CODE])
* is emitted for each pair of repeated items (when `true`), or only for the first found repetition
* (when `false`). [emitAllRepetitions] defaults to `true`.
*
* Each emitted issue has a `firstIndex` and `secondIndex` data properties with the two indices of
* the conflicting items.
*
* Depending on [treatNullAsUnique], `null` values can be considered unique. I.e. when `true` (the
* default), the value `listOf(null, null)` is considered to **not** contain repeated items.
*
* This validation is **not** stateful and depends on all descendants of the value.
*
* @param emitAllRepetitions Whether to emit an issue per each repeated item or only for the first
* repetition.
* @param code Issue code to use when two items of the value are repeated.
* @param severity Severity of the issue emitted when two items of the value are repeated.
* @property treatNullAsUnique Whether to treat `null` values as being unique.
*/
public open class UniqueItems
@JvmOverloads
constructor(
emitAllRepetitions: Boolean = true,
public val treatNullAsUnique: Boolean = true,
code: String = DEFAULT_CODE,
severity: ValidationIssueSeverity = ValidationIssueSeverity.Error,
) :
UniqueItemsBy(
emitAllRepetitions,
code,
severity,
{ if (treatNullAsUnique) it else listOf(it) }
) {
override fun toString(): String = "UniqueItems"
public companion object {
/** Default issue code representing that two items of the value are repeated. */
public const val DEFAULT_CODE: String = "itemsRepeated"
}
}
// private typealias StatefulUniqueState = Pair>, MutableList>
//
// public open class StatefulUniqueBy(
// public val code: String = DEFAULT_CODE,
// public val emitAllRepetitions: Boolean = true,
// @BuilderInference public val selector: (T) -> TKey?,
// ) : StatefulValidation, StatefulUniqueState>() {
// override suspend fun ValidationContext.initState(value: Collection):
// StatefulUniqueState
// {
// val keyIndices = HashMap>(value.size)
// val repeatedKeys = mutableListOf()
// for ((i, el) in value.withIndex()) {
// val key = selector(el)
// if (key != null) {
// val indices = keyIndices.getOrPut(key) { mutableListOf() }
// indices += i
// if (indices.size == 2) {
// repeatedKeys += key
// }
// }
// }
// return Pair(keyIndices, repeatedKeys)
// }
//
// override fun ValidationContext.validateFromState(
// value: Collection,
// state: StatefulUniqueState
// ): Flow = flow {
// val (keyIndices, repeatedKeys) = state
// for (key in repeatedKeys) {
// val indices = keyIndices[key] ?: error("Invalid 'StatefulUniqueBy' validation state.")
// for (i in 0.. -> value.size
is Array<*> -> value.size
is Table<*> -> value.size
// Array variants
is BooleanArray -> value.size
is ByteArray -> value.size
is CharArray -> value.size
is DoubleArray -> value.size
is FloatArray -> value.size
is IntArray -> value.size
is LongArray -> value.size
is ShortArray -> value.size
else ->
error(
"Unsupported value type: supported types are `Collection`, `Array` (and " +
"variants), and `Table`."
)
}
/** Iterable with index over a [Collection], [Array] (including variants), or [Table]. */
private fun iterableWithIndex(value: Any): Iterable> =
when (value) {
is Collection<*> -> value.withIndex()
is Array<*> -> value.withIndex()
is Table<*> -> value.values.withIndex()
// Array variants
is BooleanArray -> value.withIndex()
is ByteArray -> value.withIndex()
is CharArray -> value.withIndex()
is DoubleArray -> value.withIndex()
is FloatArray -> value.withIndex()
is IntArray -> value.withIndex()
is LongArray -> value.withIndex()
is ShortArray -> value.withIndex()
else ->
error(
"Unsupported value type: supported types are `Collection`, `Array` (including " +
"variants), and `Table`."
)
}