commonMain.validations.Uniques.kt Maven / Gradle / Ivy
@file:OptIn(ExperimentalStdlibApi::class)
package pt.lightweightform.lfkotlin.validations
import pt.lightweightform.lfkotlin.Context
import pt.lightweightform.lfkotlin.Issue
import pt.lightweightform.lfkotlin.SyncValidation
import pt.lightweightform.lfkotlin.objectOf
/**
* Default issue code emitted by a `Unique` or `UniqueBy` validation when a collection has repeated
* elements.
*/
public const val REPEATED_ELEMENTS_CODE: String = "LF_REPEATED_ELEMENTS"
/**
* Issue typeof issues emitted by a `Unique` or `UniqueBy` validation when a collection has repeated
* elements.
*/
public const val REPEATED_ELEMENTS_TYPE: String = "repeatedElements"
/**
* Validation that ensures that an array doesn't contain duplicated elements.
*
* Depending on [emitAllRepetitions], an issue is emitted for each pair of repeated elements (when
* `true`), or only for the first found repetition (when `false`). [emitAllRepetitions] defaults to
* `true`.
*
* Each emitted issue has an `indices` data property with a list containing the two indices of the
* conflicting elements. E.g. in JSON notation `{ indices: [0, 2] }` would indicate that elements
* with index `0` and `2` are repeated.
*/
public open class Unique(
private val repeatedElementsCode: String? = null,
private val emitAllRepetitions: Boolean = true
) : SyncValidation> {
override fun Context.validate(value: Array): Iterable = buildList {
val map = mutableMapOf()
for ((i, el) in value.withIndex()) {
val conflict = map[el]
if (conflict != null) {
add(
Issue(
repeatedElementsCode ?: REPEATED_ELEMENTS_CODE,
data =
objectOf(
"type" to REPEATED_ELEMENTS_TYPE,
"indices" to listOf(conflict, i)
)
)
)
if (!emitAllRepetitions) {
break
}
} else {
map[el] = i
}
}
}
}
/**
* Validation that ensures that an array doesn't contain duplicated elements, where the uniqueness
* of an element is represented by its key as returned by [selector].
*
* Depending on [emitAllRepetitions], an issue is emitted for each pair of repeated elements (when
* `true`), or only for the first found repetition (when `false`). [emitAllRepetitions] defaults to
* `true`.
*
* Each emitted issue has an `indices` data property with a list containing the two indices of the
* conflicting elements. E.g. in JSON notation `{ indices: [0, 2] }` would indicate that elements
* with index `0` and `2` are repeated.
*
* `Pair`s, `Triple`s, and `List`s work well to represent composite keys. E.g. say that you have an
* array of rows 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 element is `null`, that element 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 element).
*/
public open class UniqueBy(
private val repeatedElementsCode: String? = null,
private val emitAllRepetitions: Boolean = true,
private val selector: (T) -> K?,
) : SyncValidation> {
override fun Context.validate(value: Array): Iterable = buildList {
val map = mutableMapOf()
for ((i, el) in value.withIndex()) {
val key = selector(el)
if (key != null) {
val conflict = map[key]
if (conflict != null) {
add(
Issue(
repeatedElementsCode ?: REPEATED_ELEMENTS_CODE,
data =
objectOf(
"type" to REPEATED_ELEMENTS_TYPE,
"indices" to listOf(conflict, i)
)
)
)
if (!emitAllRepetitions) {
break
}
} else {
map[key] = i
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy