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

commonMain.validations.ScopedValidations.kt Maven / Gradle / Ivy

There is a newer version: 0.23.0
Show newest version
package io.kform.validations

import io.kform.*
import io.kform.validations.ValidationScopes.NotOneOf
import io.kform.validations.ValidationScopes.OneOf
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow

/**
 * Validation scopes under which a validation is allowed to run. These can be specified via an allow
 * list with [OneOf] or via a deny list with [NotOneOf].
 *
 * Note that, if no scope is provided as an external context during runtime, the default scope will
 * be `null`. In this scenario, for the allow list case, unless `null` is explicitely passed to
 * [OneOf], the validation will never run; similarly, for the deny list case, unless `null` is
 * explicitely passed to [NotOneOf], the validation will always run.
 */
public sealed class ValidationScopes(scopes: Set) : Set by scopes {
    /** Allow list of scopes under which the validation should run. */
    public class OneOf(scopes: Iterable) : ValidationScopes(scopes.toSet()) {
        public constructor(vararg scopes: T?) : this(scopes.toSet())

        override fun allows(scope: T?): Boolean = scope in this
    }

    /** Deny list of scopes under which the validation should not run. */
    public class NotOneOf(scopes: Iterable) : ValidationScopes(scopes.toSet()) {
        public constructor(vararg scopes: T?) : this(scopes.toSet())

        override fun allows(scope: T?): Boolean = scope !in this
    }

    /** Whether the validation scopes allow the provided [scope]. */
    public abstract fun allows(scope: T?): Boolean
}

/**
 * A scoped validation is a validation that should only run when the current "validation scope" is
 * allowed by the provided [scopes].
 *
 * The current validation scope is obtained via an external context with a name defaulting to
 * [DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME] (which can be overriden during class construction).
 */
public abstract class ScopedValidation
@JvmOverloads
constructor(scopeExternalContextName: String = DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME) :
    Validation() {
    /** Validation scopes of this validation. */
    public abstract val scopes: ValidationScopes

    /** Current validation scope. */
    protected val ValidationContext.scope: TScope? by
        externalContextOrNull(scopeExternalContextName)

    public final override fun ValidationContext.validate(): Flow = flow {
        if (scopes.allows(scope)) {
            emitAll(scopedValidate())
        }
    }

    /**
     * Runs the scoped validation (when the current "validation scope" is allowed by the provided
     * [scopes]) within a [ValidationContext] containing the value being validated and the value of
     * all declared dependencies. Returns a flow over all found issues.
     */
    public abstract fun ValidationContext.scopedValidate(): Flow

    public companion object {
        /** Default name of the external context from which to obtain the current scope. */
        public const val DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME: String = "scope"

        /**
         * Creates a new scoped validation from an already existing [validation] that runs when the
         * current "validation scope" is allowed by the provided [scopes].
         *
         * The current validation scope is obtained via an external context named
         * [scopeExternalContextName] (which defaults to [DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME]).
         */
        @JvmOverloads
        @JvmStatic
        @JvmName("create")
        public operator fun  invoke(
            validation: Validation,
            scopes: ValidationScopes,
            scopeExternalContextName: String = DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME
        ): ScopedValidation =
            WrappedScopedValidation(validation, scopes, scopeExternalContextName)

        /**
         * Creates a new scoped stateful validation from an already existing stateful [validation]
         * that runs when the current "validation scope" is allowed by the provided [scopes].
         *
         * The current validation scope is obtained via an external context named
         * [scopeExternalContextName] (which defaults to [DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME]).
         */
        @JvmOverloads
        @JvmStatic
        @JvmName("create")
        public operator fun  invoke(
            validation: StatefulValidation,
            scopes: ValidationScopes,
            scopeExternalContextName: String = DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME
        ): ScopedStatefulValidation =
            WrappedScopedStatefulValidation(validation, scopes, scopeExternalContextName)
    }
}

/**
 * A scoped stateful validation is a stateful validation that should only run when the current
 * "validation scope" is allowed by the provided [scopes].
 *
 * The current validation scope is obtained via an external context with a name defaulting to
 * [ScopedValidation.DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME] (which can be overriden during class
 * construction).
 */
public abstract class ScopedStatefulValidation
@JvmOverloads
constructor(
    scopeExternalContextName: String = ScopedValidation.DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME
) : StatefulValidation() {
    /** Validation scopes of this validation. */
    public abstract val scopes: ValidationScopes

    /** Current validation scope. */
    protected val ValidationContext.scope: TScope? by
        externalContextOrNull(scopeExternalContextName)

    final override fun ValidationContext.validateFromState(state: TState): Flow =
        flow {
            if (scopes.allows(scope)) {
                emitAll(scopedValidateFromState(state))
            }
        }

    final override fun ValidationContext.validate(): Flow = flow {
        if (scopes.allows(scope)) {
            emitAll(scopedValidateFromState(initState()))
        }
    }

    /**
     * Runs the scoped stateful validation (when the current "validation scope" is allowed by the
     * provided [scopes]), given its [state], within a [ValidationContext] containing the value
     * being validated and the value of all declared dependencies. Returns a flow over all found
     * issues.
     */
    public abstract fun ValidationContext.scopedValidateFromState(
        state: TState
    ): Flow

    /**
     * Runs the scoped validation (when the current "validation scope" is allowed by the provided
     * [scopes]) within a [ValidationContext] containing the value being validated and the value of
     * all declared dependencies. Returns a flow over all found issues.
     */
    // Default [scopedValidate] implementation for scoped stateful validations.
    public open fun ValidationContext.scopedValidate(): Flow = flow {
        emitAll(scopedValidateFromState(initState()))
    }
}

/** [Scoped validation][ScopedValidation] wrapping [validation]. */
private class WrappedScopedValidation(
    val validation: Validation,
    override val scopes: ValidationScopes,
    scopeExternalContextName: String = DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME
) : ScopedValidation(scopeExternalContextName) {
    override val dependencies: Map = validation.dependencies
    override val dependsOnDescendants: Boolean = validation.dependsOnDescendants

    init {
        (externalContextDependencies as MutableSet) +=
            validation.externalContextDependencies
    }

    override fun toString(): String = "Scoped$validation"

    override fun ValidationContext.scopedValidate(): Flow =
        validation.run { validate() }
}

/** [Scoped stateful validation][ScopedStatefulValidation] wrapping [validation]. */
private class WrappedScopedStatefulValidation(
    val validation: StatefulValidation,
    override val scopes: ValidationScopes,
    scopeExternalContextName: String = ScopedValidation.DEFAULT_SCOPE_EXTERNAL_CONTEXT_NAME
) : ScopedStatefulValidation(scopeExternalContextName) {
    override val dependencies: Map = validation.dependencies
    override val dependsOnDescendants: Boolean = validation.dependsOnDescendants
    override val observers: List> = validation.observers

    init {
        (externalContextDependencies as MutableSet) +=
            validation.externalContextDependencies
    }

    override fun toString(): String = "Scoped$validation"

    override suspend fun ValidationContext.initState(): TState = validation.run { initState() }

    override suspend fun destroyState(state: TState): Unit = validation.run { destroyState(state) }

    override fun ValidationContext.scopedValidate(): Flow =
        validation.run { validate() }

    override fun ValidationContext.scopedValidateFromState(state: TState): Flow =
        validation.run { validateFromState(state) }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy