Validation
Module for validating classes/records and methods using annotations.
Dependency¶
Dependency build.gradle
:
Module:
Dependency build.gradle.kts
:
Module:
Validation annotations¶
Special validation annotations are used by Kora to validate fields/arguments, they represent simple checks.
Available validation annotations:
@NotEmpty
- Checks that the string is not empty@NotBlank
- Checks that the string does not consist of empty characters@Pattern
- Checks if the string matches Regular Expression (RegEx)@Range
- Checks that the number is in the specified range@Size
- Checks that a collection (List, Set, Map) orString
has a size in the specified range.
Class validation¶
It is suggested to use the @Valid
annotation to mark a class that needs a validator from the Kora framework.
An example of a labeled class for validation looks like this:
A validator of that class will then be available in the dependency container:
Created validators can be implemented as dependencies in any component, in the examples above the validator for the Foo
class,
can be implemented by its signature Validator<Foo>
as a component dependency and used manually for validation.
The validator returns a list of violations after validation, they can be used to manually compose the error either
you can use the validateAndThrow
method which throws a ViolationException
exception in case of a validation error.
Field validation¶
It is expected to use a special provided validation annotation validation set for field validation.
An example of an object marked up for validation looks like this:
For Record classes, the syntax for accessing fields via Record-like getter contracts is used,
in the case of Foo
and the code
field, getter code()
will be used in the created Validator
.
For a regular class it is expected that Java Getters syntax will be used, for example for the id
field getter getId()
will be used,
where getter should have at least package-private visibility.
Required fields¶
All fields are required (NotNull
) by default, so NotNull
checks will be created for all of them in the Validator
.
Optional fields¶
In order to specify a field as not required, you need to mark it with any @Nullable
annotation,
will not create a null check for such a field:
- Any
@Nullable
annotation will do, such asjavax.annotation.Nullable
/jakarta.annotation.Nullable
/org.jetbrains.annotations.Nullable
/ etc.
It is expected to use the Kotlin Nullability syntax and mark such a field as Nullable:
Embedded fields¶
In order to validate fields of complex objects for which validators are created (or provided independently),
or fields that are not supported by standard validation tools,
the @Valid
annotation is supposed to be used:
In the example above, a Validator<Bar>
validator would be created for Bar
and a Validator<Foo>
would be created for Foo
,
where when the Validator<Foo>
validator is called, the validator for Validator<Bar>
will be called internally.
Validation options¶
There are two types of validation:
Full
- all fields that are just marked up are checked, all possible validation errors are collected and only then an exception is thrown. (Default behavior)FailFast
- exception is thrown on the first validation error encountered.
Example of FailFast validation:
ValidatorContext context = ValidationContext.builder().failFast(true).build();
List<Violation> violations = fooValidator.validate(value,context);
Method validation¶
It is expected to use a special provided set of annotations validation for validating method arguments and result.
Argument validation¶
It is required to use the @Validate
annotation over the method to validate method arguments:
Required arguments¶
All arguments are required (NotNull
) by default, so NotNull
checks will be created for all of them.
Optional arguments¶
In order to specify an argument as not required requires marking it with any @Nullable
annotation,
will not create a null check for such an argument:
@Component
Public class SomeService {
@Validate
public int validate(@Nullable String argument) { //(1)!
return 1;
}
}
- Any
@Nullable
annotation will do, such asjavax.annotation.Nullable
/jakarta.annotation.Nullable
/org.jetbrains.annotations.Nullable
/ etc.
It is expected to use the Kotlin Nullability syntax and mark such an argument as Nullable:
Embedded arguments¶
In order to validate fields of complex objects for which validators are created (or provided independently),
or fields that are not supported by standard validation tools,
@Valid
annotation is supposed to be used:
In the example above, a Validator<Bar>
validator would be created for Bar
and a Validator<Foo>
would be created for Foo
,
where when the Validator<Foo>
validator is called, the validator for Validator<Bar>
will be called internally.
Result validation¶
In order to validate the result of a method, it is required to use the @Validate
annotation over the method and mark it up with the appropriate annotations:
@Valid
public record Foo(@Valid Bar bar) { }
@Component
public class SomeService {
@Size(min = 1, max = 3) //(3)!
@Valid //(2)!
@Validate //(1)!
public List<Foo> validate() {
// do something
}
}
- Indicates that the method requires validation
- Indicates that the result requires validation with a validator from the return value type
- Standard validation annotation
@Component
open class SomeService {
@Size(min = 1, max = 3) //(3)!
@Valid //(2)!
@Validate //(1)!
fun validate(): List<Foo> {
// do something
}
}
- Indicates that the method requires validation
- Indicates that the result requires validation with a validator from the return value type
- Standard validation annotation
Validation options¶
There are two types of validation:
Full
- all fields that are just marked up are validated, all possible validation errors are collected and only then an exception is thrown. (Default behavior)FailFast
- exception is thrown on the first validation error encountered.
Example of FailFast validation:
Custom validation annotations¶
Creating your custom annotation requires:
1) Create an inheritor of Validator
:
final class MyValidStringValidator implements Validator<String> {
@Nonnull
@Override
public List<Violation> validate(String value, @Nonnull ValidationContext context) {
if (value == null) {
return List.of(context.violates("Should be not empty, but was null"));
} else if (value.isEmpty()) {
return List.of(context.violates("Should be not empty, but was empty"));
}
return Collections.emptyList();
}
}
class MyValidStringValidator : Validator<String?> {
fun validate(value: String?, context: ValidationContext): List<Violation> {
if (value == null) {
return listOf(context.violates("Should be not empty, but was null"))
} else if (value.isEmpty()) {
return listOf(context.violates("Should be not empty, but was empty"))
}
return listOf()
}
}
2) Create ValidatorFactory
implementation:
3) Register the inheritor of ValidatorFactory
as a component:
4) Create a validation annotation and annotate it @ValidatedBy
with the previously created ValidatorFactory
inheritor:
5) Annotate field/argument/result:
Signatures¶
Available signatures for repository methods out of the box:
Class must be non final
in order for aspects to work.
The T
refers to the type of the return value.
T myMethod()
Optional<T> myMethod()
Mono<T> myMethod()
Project Reactor (require dependency)Flux<T> myMethod()
Project Reactor (require dependency)
Class must be open
in order for aspects to work.
By T
we mean the type of the return value, either T?
, or Unit
.
myMethod(): T
suspend myMethod(): T
Kotlin Coroutine (require dependency asimplementation
)myMethod(): Flow<T>
Kotlin Coroutine (require dependency asimplementation
)