Skip to content

Violation

tessoir edited this page Mar 17, 2026 · 2 revisions

Violation

A Violation represents a single validation failure. It is the fundamental unit of failed validation in KVerify — every failed rule produces exactly one.

The interface is minimal by design:

interface Violation {
    val reason: String
}

The violation() factory

For simple, one-off violations that don't need to carry structured data, use the violation() factory function:

enforce {
    if (user.name.isBlank()) violation("Name must not be blank") else null
}

This is the quickest way to produce a violation, and it's a good fit for custom rules where the reason string is all you need.

Custom violation types

When a failure carries information beyond a message — for example, the actual value, the constraint that was violated, or a code your API can act on — implement Violation directly:

data class PasswordTooShortViolation(
    val actualLength: Int,
    val minimumLength: Int,
) : Violation {
    override val reason =
        "Password must be at least $minimumLength characters, but got $actualLength"
}

This lets you handle failures by type rather than by parsing strings:

result.onInvalid { violations ->
    violations.forEach { violation ->
        val message = when (violation) {
            is PasswordTooShortViolation ->
                "Try a longer password (minimum: ${violation.minimumLength})"
            is EmailFormatViolation ->
                "Please check your email format"
            else -> violation.reason
        }

        println(message)
    }
}

Typed violations are one of KVerify's core strengths — use them wherever the caller needs to react to a specific failure differently than others.

PathAwareViolation

If you have kverify-rule-set on your classpath, PathAwareViolation is available as an extension of Violation that also carries the path to the value that failed:

interface PathAwareViolation : Violation {
    val validationPath: ValidationPath
}

All built-in rule violations implement PathAwareViolation. If you are writing custom violations for use alongside built-in rules, implementing it keeps your violations consistent with the rest of the library:

data class PasswordTooShortViolation(
    val actualLength: Int,
    val minimumLength: Int,
    override val validationPath: ValidationPath,
) : PathAwareViolation {
    override val reason =
        "Password must be at least $minimumLength characters, but got $actualLength"
}

The validationPath is typically obtained from the active scope inside a rule:

fun Verification<String>.minPasswordLength(min: Int): Verification<String> =
    apply {
        val actualLength = value.length
        scope.failIf({ actualLength < min }) {
            PasswordTooShortViolation(
                actualLength = actualLength,
                minimumLength = min,
                validationPath = scope.validationContext.validationPath(),
            )
        }
    }

If you only depend on kverify-core or path information isn't relevant to your use case, implementing Violation directly is perfectly fine.

Next steps

Clone this wiki locally