agentsclimarketplace

Kora aop validation

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

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

10.8 KB, 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.

Keep looking

Skills are one crate of 328,083. 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.