agentsclimarketplace

Kora aop validation

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-aop-validation

Kora declarative validation via its own constraint annotations (@NotBlank, @NotEmpty, @Pattern, @Range, @Size), class/record validation with @Valid (generates Validator<T>), method argument and result validation with @Validate (AOP), custom constraints via @ValidatedBy + ValidatorFactory, and ViolationException handling mapped to HTTP 400 with ValidationHttpServerInterceptor. Use when validating request DTOs, enforcing argument/return rules on services or controllers, building custom constraints, or turning validation failures into structured 400 responses. Kora validation is NOT Jakarta/JSR-380 — annotations live in ru.tinkoff.kora.validation.common.annotation.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-aop-validation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.8 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it

Kora AOP Validation

Kora validates classes/records and method arguments/results at compile time using its own constraint annotations and generated Validator<T> components. There is no reflection: @Valid generates a Validator<T>, @Validate weaves an aspect into a method.

Key fact: Kora validation annotations come from ru.tinkoff.kora.validation.common.annotation, NOT from jakarta.validation / JSR-380. The full constraint set is only these five: @NotBlank, @NotEmpty, @Pattern, @Range, @Size. There is no @NotNull constraint — every field/argument is implicitly required (null fails) unless marked @Nullable.


Quick Start

1. Dependency + module

Two setups exist — pick by whether you need HTTP integration. All Kora artifacts inherit their version from the kora-parent BOM; never pin individual ru.tinkoff.kora:* versions.

Plain validation (no HTTP) — artifact validation-common, module ValidatorModule:

dependencies {
    annotationProcessor "ru.tinkoff.kora:annotation-processors" // mandatory: generates validators + aspects
    implementation "ru.tinkoff.kora:validation-common"
}
import ru.tinkoff.kora.validation.common.constraint.ValidatorModule;

@KoraApp
public interface Application extends ValidatorModule { }

With HTTP-server integration (maps ViolationException → 400) — artifact validation-module, module ValidationModule:

dependencies {
    annotationProcessor "ru.tinkoff.kora:annotation-processors"
    implementation "ru.tinkoff.kora:validation-module"
}
import ru.tinkoff.kora.validation.module.ValidationModule;

@KoraApp
public interface Application extends ValidationModule, UndertowHttpServerModule, JsonModule { }

Kotlin: replace annotationProcessor/testAnnotationProcessor with ksp "ru.tinkoff.kora:symbol-processors".

2. Validated record — generates Validator<CreateUserRequest>

import jakarta.annotation.Nullable;
import ru.tinkoff.kora.validation.common.annotation.*;

@Valid
public record CreateUserRequest(
    @NotBlank @Size(min = 2, max = 100) String name,
    @NotBlank @Pattern("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$") String email,
    @Range(from = 18, to = 120) Integer age,
    @Nullable String note                                // opt out of the implicit null check
) {}

3. Validate a method with @Validate

@Component
public class UserController {

    @HttpRoute(method = HttpMethod.POST, path = "/users")
    @Json
    @Validate
    public UserResponse createUser(@Valid @Json CreateUserRequest request) {
        return userService.create(request); // ViolationException thrown before the body runs if invalid
    }
}

4. Inject the generated validator directly (manual validation)

@Component
public final class Example {

    private final Validator<CreateUserRequest> validator;

    public Example(Validator<CreateUserRequest> validator) {
        this.validator = validator;
    }

    public void check(CreateUserRequest req) {
        validator.validateAndThrow(req); // collects all violations, then throws ViolationException
    }
}

How it works

AnnotationWhereEffect
@Valid on a type/recordtypeGenerates a Validator<T> component in the graph
@Valid on a field/parameterfield/paramRecurses into a nested validated type's Validator
@Validate on a methodmethodWeaves an aspect: validates @-annotated args (and the result if the method itself is annotated) before/after the body
@NotBlank/@NotEmpty/@Pattern/@Range/@Sizefield/param/methodThe actual constraint checks
@Nullable (any flavor)field/paramSuppresses the implicit null check

A Validator<T> returns List<Violation>. Use validate(value) to inspect, or validateAndThrow(value) to throw ViolationException on the first non-empty result. The @Validate aspect throws ViolationException automatically.


Class / record validation

Every field is implicitly required. Mark optional fields with any @Nullable annotation (jakarta.annotation.Nullable, javax.annotation.Nullable, org.jetbrains.annotations.Nullable).

@Valid
public record OrderRequest(
    @NotBlank String id,
    @Valid Customer customer,            // nested → uses generated Validator<Customer>
    @Valid List<OrderItem> items,        // elements validated via Validator<OrderItem>
    @Nullable String comment
) {}

For a regular (non-record) class, getters must be at least package-private (getId()); records use the component accessor (id()).

Kotlin

@Valid
data class CreateUserRequest(
    @field:NotBlank @field:Size(min = 2, max = 100) val name: String,
    @field:NotBlank val email: String,
    val note: String?                    // Kotlin nullability marks it optional
)

Use the @field: prefix on constraints. A data class is fine for class validation. A class only needs open when it hosts a @Validate method (aspects require subclassing).


Method validation with @Validate

@Validate on a method validates its annotated arguments before the body and, if the method itself carries constraint annotations, its result afterward. The enclosing class must be non-final (Java) / open (Kotlin) so the aspect can subclass it.

@Component
public class UserService {

    @Validate
    public User create(@Valid CreateUserRequest request) { ... }

    // single-argument constraint, stop on the first error
    @Validate(failFast = true)
    public User getByEmail(@NotBlank @Pattern("^[^@\\s]+@[^@\\s]+$") String email) { ... }

    // result validation: @Validate enables it, @Valid validates the returned objects,
    // @Size constrains the list itself
    @Size(min = 1)
    @Valid
    @Validate
    public List<User> getAllUsers() { ... }
}

failFast = true throws on the first violation; the default collects all violations into one ViolationException.

Supported method signatures: T, Optional<T>, Mono<T>/Flux<T> (with reactor-core) in Java; T, suspend fun, Flow<T> in Kotlin.


Mapping ViolationException to HTTP 400

Requires the validation-module artifact (ValidationModule). Register an interceptor tagged for the HTTP server plus a response mapper. Adapted from .kora-agent/kora-examples/guides/java/kora-java-guide-validation-app:

@KoraApp
public interface Application extends
        ValidationModule, UndertowHttpServerModule, JsonModule, LogbackModule {

    default ViolationExceptionHttpServerResponseMapper violationMapper(
            JsonWriter<ValidationErrorResponse> writer) {
        return (request, exception) -> HttpServerResponse.of(
            400,
            HttpBody.json(writer.toByteArrayUnchecked(
                ValidationErrorResponse.of(toErrors(exception.getViolations())))));
    }

    @Tag(HttpServerModule.class)
    default ValidationHttpServerInterceptor validationInterceptor(
            ViolationExceptionHttpServerResponseMapper mapper) {
        return new ValidationHttpServerInterceptor(mapper);
    }

    private static List<ValidationErrorDetails> toErrors(List<Violation> violations) {
        return violations.stream()
            .map(v -> new ValidationErrorDetails(v.path().full(), v.message()))
            .collect(java.util.stream.Collectors.toList());
    }
}

Violation is an interface: message() and path() (a ValidationContext.Path whose full() returns the dotted path, e.g. customer.address.city). See violation-exception-reference.md.


References & assets

FilePurpose
references/validation-annotations-reference.mdThe five constraints, @Range boundary enum, @Pattern int flags, @Nullable opt-out
references/custom-validators-reference.mdCustom constraints via Validator<T> + ValidatorFactory<T> + @ValidatedBy
references/violation-exception-reference.mdViolationException, Violation, ValidationContext, HTTP 400 mapping, testing
assets/README.mdDTO / service / custom-validator / error-response templates (Java + Kotlin)

Working example apps: .kora-agent/kora-examples/examples/java/kora-java-validation (plain validation) and .kora-agent/kora-examples/guides/java/kora-java-guide-validation-app (HTTP 400 mapping).


Common pitfalls

SymptomFix
Validation never runs on a methodAdd @Validate to the method; ensure the class is non-final (Java) / open (Kotlin)
Nested object not validatedAdd @Valid to the field/parameter holding it
Field unexpectedly requiredAll fields are implicit-not-null; add @Nullable to make optional
Looking for @NotNullIt does not exist in Kora — nullability is the default; opt out with @Nullable
Reached for jakarta.validation.*Use ru.tinkoff.kora.validation.common.annotation.* instead
Caught the wrong exceptionCatch ru.tinkoff.kora.validation.common.ViolationException
HTTP 500 instead of 400 on bad inputUse validation-module; register ValidationHttpServerInterceptor + ViolationExceptionHttpServerResponseMapper
Kotlin constraints ignoredUse the @field: prefix
@Pattern(regexp = ...) won't compileThe attribute is the unnamed value: @Pattern("...")

Testing

@KoraAppTest(Application.class)
class CreateUserValidationTest {

    @TestComponent
    private Validator<CreateUserRequest> validator;

    @Test
    void failsForBlankName() {
        var request = new CreateUserRequest("   ", "[email protected]", 25, null);

        ViolationException ex = assertThrows(ViolationException.class,
            () -> validator.validateAndThrow(request));

        assertTrue(ex.getViolations().stream()
            .anyMatch(v -> v.path().full().contains("name")));
    }
}

See .kora-agent/kora-examples/examples/java/kora-java-validation/src/test/java/... for the @KoraAppTest + @TestComponent pattern over ArgumentValidator / ResultValidator.

What ships with it: 13 files

39.0 KB alongside SKILL.md

evals/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.