All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.internal.actions.UpdateStatefulValidationsStateAction.kt Maven / Gradle / Ivy

There is a newer version: 0.23.0
Show newest version
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)
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy