Skip to content

Violation

tessoir edited this page Mar 24, 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:

for (violation in result.violations) {
    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