Valiktor Documentation

repository·master·Indexed 19 days ago

https://github.com/valiktor/valiktor

A type-safe, fluent DSL for object validation in Kotlin. Valiktor provides an extensible way to define constraints on data models, supporting nested objects, collections, and coroutines. It includes a core engine with 40+ constraints, internationalization (i18n) support, a dedicated test module for assertions, and integrations for Spring WebMvc and WebFlux via the valiktor-spring module. Version 0.12.0.

Tokens
6.3K
Snippets
17
Records
26
Agent score
66%

What's inside Valiktor

  1. Overview of Valiktor modules

    master

    Valiktor is composed of several modules catering to different needs, ranging from the core validation engine to specific integrations for frameworks and data types:

    Core Engine

    • valiktor-core: The primary module containing the validation engine, 40+ constraints, 200+ validation functions for standard Kotlin/Java types, internationalization support, and default formatters.
    • valiktor-test: Provides fluent assertions specifically designed for writing validation tests.

    Type-Specific Support

    • valiktor-javamoney: Support for JavaMoney API (MonetaryAmount).
    • valiktor-javatime: Support for JavaTime API (LocalDate, LocalDateTime, OffsetDateTime, ZonedDateTime).
    • valiktor-jodamoney: Support for Joda-Money API (Money, BigMoney).
    • valiktor-jodatime: Support for Joda-Time API (LocalDate, LocalDateTime, DateTime).

    Spring Framework Integration

    • valiktor-spring: Integration for Spring WebMvc and WebFlux. Includes exception handlers for ConstraintViolationException (Valiktor), and InvalidFormatException/MissingKotlinParameterException (Jackson).
    • valiktor-spring-boot-autoconfigure: Provides auto-configuration for valiktor-spring using properties and WebMvc/WebFlux exception handlers.
    • valiktor-spring-boot-starter: A Spring Boot Starter that bundles both valiktor-spring and valiktor-spring-boot-autoconfigure.
  2. How Valiktor works

    master

    Valiktor uses a type-safe DSL to validate object properties. The primary entry point is org.valiktor.validate, which takes an object and a lambda defining the constraints. If any constraint is violated, a ConstraintViolationException is thrown. This exception contains a set of ConstraintViolation objects, each specifying the property name, the invalid value, and the violated constraint.

    To handle validation errors, catch ConstraintViolationException and iterate over its constraintViolations property.

    try {
        validate(employee) {
            validate(Employee::id).isPositive()
            validate(Employee::name).isNotEmpty()
        }
    } catch (ex: ConstraintViolationException) {
        ex.constraintViolations
            .map { "${it.property}: ${it.constraint.name}" }
            .forEach(::println)
    }
  3. Internationalization (i18n) in Valiktor

    master

    Valiktor decouples validation logic from error messaging. You can convert ConstraintViolation objects into human-readable ConstraintViolationMessage objects using the org.valiktor.i18n.mapToMessage extension function.

    Parameters:

    • baseName: The prefix for message properties (default: org/valiktor/messages).
    • locale: The java.util.Locale to use (default: system default).

    Supported Locales: ca (Catalan), de (German), en (English), es (Spanish), ja (Japanese), pt_BR (Portuguese/Brazil).

    Customizing Messages: Overwrite messages by adding keys to your bundle. The key format is the qualified class name of the constraint plus the .message suffix (e.g., org.valiktor.constraints.NotEmpty.message).

    try {
        validate(employee) {
            validate(Employee::id).isPositive()
        }
    } catch (ex: ConstraintViolationException) {
        ex.constraintViolations
            .mapToMessage(baseName = "messages", locale = Locale.ENGLISH)
            .map { "${it.property}: ${it.message}" }
            .forEach(::println)
    }
  4. Install Valiktor

    master

    Add the valiktor-core dependency to your project using Gradle (Groovy or Kotlin DSL) or Maven. The current version is 0.12.0.

    // Gradle (Groovy)
    implementation 'org.valiktor:valiktor-core:0.12.0'
    // Gradle (Kotlin DSL)
    implementation("org.valiktor:valiktor-core:0.12.0")
    <!-- Maven -->
    <dependency>
      <groupId>org.valiktor</groupId>
      <artifactId>valiktor-core</artifactId>
      <version>0.12.0</version>
    </dependency>
  5. Create a custom message formatter

    master

    If constraints contain parameters that require specific formatting (like dates or money) instead of the default toString(), you can implement the org.valiktor.i18n.Formatter interface.

    Option 1: Programmatic Registration Implement Formatter<T> and add it to org.valiktor.i18n.Formatters:

    object CustomFormatter : Formatter<Custom> {
        override fun format(value: Custom, messageBundle: MessageBundle) = value.toString()
    }
    Formatters[Custom::class] = CustomFormatter

    Option 2: SPI (Service Provider Interface) Implement org.valiktor.i18n.FormatterSpi and register it in META-INF/services/org.valiktor.i18n.FormatterSpi.

    object CustomFormatter : Formatter<Custom> {
        override fun format(value: Custom, messageBundle: MessageBundle) = value.toString()
    }
    
    Formatters[Custom::class] = CustomFormatter
  6. Validate nested object properties

    master

    Valiktor supports recursive validation of nested objects. You can nest validate calls within the main validation block to traverse the object graph. The resulting property paths in a ConstraintViolation will reflect the nesting (e.g., company.city.name).

    try {
        validate(employee) {
            validate(Employee::company).validate {
                validate(Company::city).validate {
                    validate(City::name).isNotEmpty()
                }
            }
        }
    } catch (ex: ConstraintViolationException) {
        // property will be 'company.city.name'
    }
  7. Validate array and collection properties

    master

    To validate elements within a collection or array, use the validateForEach function. This allows you to apply constraints to every element in the collection. When a violation occurs, the property path will include the index of the invalid element (e.g., dependents[0].name).

    try {
        validate(employee) {
            validate(Employee::dependents).validateForEach {
                validate(Dependent::name).isNotEmpty()
            }
        }
    } catch (ex: ConstraintViolationException) {
        // property will be 'dependents[0].name'
    }
  8. Spring integration for RESTful APIs

    master

    The valiktor-spring module provides automatic exception handling for Spring WebMvc and WebFlux. When a ConstraintViolationException is thrown, the handlers return an HTTP 422 Unprocessable Entity status with a JSON payload describing the errors.

    Payload Format:

    {
      "errors": [
        {
          "property": "string",
          "value": "any",
          "message": "string",
          "constraint": {
            "name": "string",
            "params": [{ "name": "string", "value": "any" }]
          }
        }
      ]
    }

    Spring Boot Configuration: Use the valiktor-spring-boot-starter for auto-configuration. You can configure the message bundle name via:

    • YAML: valiktor.base-bundle-name: messages
    • Properties: valiktor.baseBundleName=messages
  9. Create a custom validation constraint

    master

    Creating a custom validation involves three steps:

    1. Define the Constraint: Implement org.valiktor.Constraint. The messageParams property (a Map<String, *>) is used to pass variables into the error message.
    2. Create the Extension Function: Implement an extension function on org.valiktor.Validator<E>.Property<T>. Use the validate helper to link the constraint to the logic. For coroutines, use coValidate.
    3. Add i18n Messages: Add the constraint key to your message bundles (e.g., org.valiktor.constraints.MyConstraint.message=Value must be {param}).
    // 1. Define Constraint
    data class Between<T>(val start: T, val end: T) : Constraint
    
    // 2. Create Extension
    fun <E> Validator<E>.Property<Int?>.isBetween(start: Int, end: Int) = 
        this.validate(Between(start, end)) { it == null || it in start.rangeTo(end) }
    
    // 3. Usage
    validate(employee) {
        validate(Employee::age).isBetween(start = 1, end = 99)
    }
  10. Test assertions with Valiktor

    master

    The Valiktor test module provides a fluent DSL for asserting validation failures in unit tests.

    • shouldFailValidation<T> { ... }: Asserts that the block throws a ConstraintViolationException.
    • .verify { ... }: Allows inspecting specific violations.
    • expect(property, value, constraint): Checks if a specific property failed with a specific value and constraint.
    • expectAll(collection) { ... }: Validates all elements in a collection.
    • expectElement { ... }: Validates a specific element within a collection loop.
    shouldFailValidation<Employee> {
        // code that triggers validation
    }.verify {
        expect(Employee::name, " ", NotBlank)
        expectAll(Employee::dependents) {
            expectElement {
                expect(Dependent::name, " ", NotBlank)
            }
        }
    }
  11. Enable Valiktor WebMvc exception handling in Spring Boot

    master

    When using Spring WebMvc, ValiktorWebMvcAutoConfiguration automatically configures specialized exception handlers to translate Valiktor validation errors into appropriate HTTP responses.

    This auto-configuration triggers if:

    1. org.springframework.web.servlet.DispatcherServlet is present on the classpath.
    2. A ValiktorExceptionHandler bean is already defined.

    It provides the following handlers automatically (unless you define your own beans):

    • ConstraintViolationExceptionHandler: Handles standard Valiktor constraint violations.
    • InvalidFormatExceptionHandler: Handles Jackson InvalidFormatException (requires Jackson on classpath).
    • MissingKotlinParameterExceptionHandler: Handles Kotlin-specific missing parameter exceptions (requires jackson-module-kotlin on classpath).