commonMain.internal.actions.UpdateStatefulValidationsStateAction.kt Maven / Gradle / Ivy
package io.kform.internal.actions
import io.kform.*
import io.kform.internal.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.launch
/**
* Action that updates the state of all stateful validations matching [path]. This action runs at
* normal priority since the updates are done in new coroutines that this action does not wait for.
*/
internal class UpdateStatefulValidationsStateAction(
formManager: FormManager,
internal val path: AbsolutePath,
internal val validation: StatefulValidation,
internal val validationIndex: Int,
internal val statefulValidationIndex: Int,
private val observerIndex: Int,
private val event: ValueEvent
) : ValueStateAction(formManager) {
override fun toString(): String = "UpdateStatefulValidationState($validation@$path, $event)"
override val accesses =
listOf(
AccessValueStateTree(ActionAccessType.Read),
AccessValidationState(ActionAccessType.Read),
AccessStatefulValidationDeferredState(ActionAccessType.Write, statefulValidationIndex)
)
override val accessedPaths = listOf(path)
override suspend fun run() {
for ((state, schema, path) in stateInfo(path)) {
if (state == null) continue
state as StateImpl
// Do nothing if the validation state hasn't yet been initialised
val oldDeferredValidationState =
state.getStatefulValidationDeferredState(statefulValidationIndex) ?: return
val newDeferredValidationState = CompletableDeferred(formManager.supervisorJob)
state.setStatefulValidationDeferredState(
newDeferredValidationState,
statefulValidationIndex
)
// Propagate cancellation downwards (when a value is destroyed before an up-to-date
// validation state has been computed)
newDeferredValidationState.invokeOnCompletion { oldDeferredValidationState.cancel() }
// By the time we update the validation state, a `ValidateAction` may be waiting for the
// new state; this only happens when the validation wasn't previously (successfully)
// validated. This means that we cannot always issue a `RemoveCachedIssuesAction` since
// we may be removing issues that were cached **after** the validation state was set.
// Thus, we check whether the validation was previously validated before issuing a
// `RemoveCachedIssuesAction`.
val validationWasValidated =
state.getCachedIssues(validationIndex).let { cachedIssues ->
cachedIssues != null && !cachedIssues.any { it is ValidationExceptionError }
}
formManager.scope.launch(CoroutineName("Update stateful validation state")) {
FormManager.logger.trace {
"At '$path': Handling validation '$validation' event: $event"
}
val oldValidationState: Any?
try {
oldValidationState = oldDeferredValidationState.await()
} catch (ex: Throwable) {
// Propagate exception upwards, so that exceptions on previous validation states
// always propagate to the latest one
newDeferredValidationState.completeExceptionally(ex)
return@launch
}
try {
val newValidationState =
validation.observers[observerIndex].updateState(oldValidationState, event)
if (newDeferredValidationState.complete(newValidationState)) {
if (oldValidationState != newValidationState) {
if (validationWasValidated) {
formManager.scheduleAction(
RemoveCachedIssuesAction(
formManager,
path,
schema,
state,
validation,
validationIndex
)
)
}
}
FormManager.logger.debug {
"At '$path': Updated validation '$validation' state: " +
"$oldValidationState -> $newValidationState"
}
} else {
// Deferred was cancelled before we completed, so new state is useless
FormManager.logger.trace {
"At '$path': Destroying validation '$validation' new state: " +
"$newValidationState"
}
validation.destroyState(newValidationState)
}
} catch (ex: Throwable) {
// An error occurred during [updateState]
FormManager.logger.debug {
"At '$path': Failed to update validation '$validation' state ($ex)"
}
newDeferredValidationState.completeExceptionally(ex)
}
// Always destroy old state
FormManager.logger.trace {
"At '$path': Destroying validation '$validation' old state: " +
"$oldValidationState"
}
validation.destroyState(oldValidationState)
}
}
}
}