© 2019-2022 rxmicro.io.
Free use of this software is granted under the terms of the Apache License 2.0
.
Copies of this entity may be made for Your own use and for distribution to others, provided that You do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. If You find errors or omissions in this entity, please don’t hesitate to submit an issue or open a pull request with a fix. |
The RxMicro framework supports validation of HTTP requests and responses.
All classes and annotations required for data validation are available in the
rxmicro.validation
module.
1. Preparatory Steps
To activate the validation module in a microservices project, the following steps must be taken:
-
Add the required dependencies to
pom.xml
. -
Add the
rxmicro.validation
module tomodule-info.java
.
1.1. Adding the Required Dependencies:
To activate the validation module in a microservices project, it is necessary to add the rxmicro-validation
library:
<dependencies>
<dependency>
<groupId>io.rxmicro</groupId>
<artifactId>rxmicro-validation</artifactId>
<version>${rxmicro.version}</version>
</dependency>
</dependencies>
1.2. Adding the rxmicro.validation
Module to module-info.java
module example {
requires rxmicro.validation; (1)
}
1 | Adding the request and response validation module. |
2. Built-in Constraints.
The RxMicro framework supports the following built-in constraints, that are available at the
io.rxmicro.validation.constraint
package:
Annotation | Supported Type | Description |
---|---|---|
The annotated element may be optional, i.e. |
||
The annotated element must be |
||
The annotated element must be |
||
The annotated element must be a valid country code: |
||
The annotated element must be a string value with digit characters only. |
||
The annotated element must be a well-formed email address. |
||
The annotated element value must end with the provided suffix. |
||
The annotated element must be an element of the predefined enumeration. This validation rule is useful when a Java |
||
The annotated element must be an instant in the future. |
||
The annotated element must be an instant in the present or in the future. |
||
The annotated element must be a valid host name. |
||
The annotated element must be a valid IP address: |
||
The annotated element must be a valid latitude coordinate. |
||
The annotated element must be a string with latin alphabet letters only. |
||
The annotated element must have the expected string length. |
||
The annotated element must be a valid longitude coordinate. |
||
The annotated element must a lowercase string. |
||
The annotated element must be a double whose value must be lower to the specified maximum. |
||
The annotated element must be a |
||
The annotated element must have a string length whose value must be lower or equal to the specified maximum. |
||
The annotated element must be a number whose value must be lower or equal to the specified maximum. |
||
The annotated element must have a list size whose value must be lower or equal to the specified maximum. |
||
The annotated element must be a double whose value must be higher or equal to the specified minimum. |
||
The annotated element must be a |
||
The annotated element must have a string length whose value must be higher or equal to the specified minimum. |
||
The annotated element must be a number whose value must be higher or equal to the specified minimum. |
||
The annotated element must have a list size whose value must be higher or equal to the specified minimum. |
||
|
The annotated element may be optional, i.e. |
|
The annotated array element may be optional, i.e. |
||
The annotated element must be a decimal within accepted range (scale and precision). |
||
The annotated element must be an instant in the past. |
||
The annotated element must be an instant in the past or in the present. |
||
The annotated (See |
||
The annotated element must be a valid phone number. |
||
The annotated element must have the expected list size. |
||
The annotated element must be a valid |
||
The annotated element value must start with the provided prefix. |
||
The annotated element must be an enumeration with predefined sub sequence. |
||
The annotated element must be a valid |
||
The annotated element must be an instant with truncated time value. |
||
The annotated element must contain unique items. |
||
The annotated element must an uppercase string. |
||
The annotated element must be a valid |
||
The annotated element must be a valid URL encoded value. |
||
The annotated element must be a valid |
||
The annotated element must be a valid |
The RxMicro framework analyzes built-in constraints when drawing up the project documentation. Therefore, a properly selected annotation, in addition to its main purpose, makes it possible to automatically generate more accurate project documentation! |
3. HTTP Requests Server Validation
After activation of the rxmicro.validation
module in the module-info.java
descriptor
module examples.validation.server.basic {
requires rxmicro.rest.server.netty;
requires rxmicro.rest.server.exchange.json;
requires rxmicro.validation; (1)
}
1 | rxmicro.validation is a module for request and response validation. |
the developer can use built-in constraints to validate HTTP request parameters:
final class MicroService {
@PUT(value = "/", httpBody = false)
void consume(final @Email String email) { (1)
System.out.println("Email: " + email);
}
}
1 | The email parameter is annotated by the @Email annotation. |
Due to this annotation the RxMicro Annotation Processor
will automatically generate a validator for email
HTTP parameter:
@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {
private BlockingHttpClient blockingHttpClient;
private SystemOut systemOut;
@ParameterizedTest
@CsvSource({
"/, Parameter \"email\" is required!", (1)
"/?email=, Parameter \"email\" is required!",
"/?email=rxmicro, Invalid parameter \"email\": Expected a valid email format!",
"/?email=rxmicro.io, Invalid parameter \"email\": Expected a valid email format!",
"/?email=@rxmicro.io, Invalid parameter \"email\": Expected a valid email format!",
"/?email=rxmicro.io@, Invalid parameter \"email\": Expected a valid email format!",
"/?email=@.rxmicro.io, Invalid parameter \"email\": Expected a valid email format!",
"/?email=rxmicro.io@., Invalid parameter \"email\": Expected a valid domain name!"
})
void Should_return_invalid_request_status(final String path,
final String expectedErrorMessage) {
final ClientHttpResponse response = blockingHttpClient.put(path);
assertEquals(jsonErrorObject(expectedErrorMessage), response.getBody()); (2)
assertEquals(400, response.getStatusCode()); (2)
assertTrue(systemOut.isEmpty(), "System.out is not empty: " + systemOut.asString()); (3)
}
@Test
void Should_handle_request() {
final ClientHttpResponse response = blockingHttpClient.put("/?email=welcome@rxmicro.io");
assertEquals("Email: welcome@rxmicro.io", systemOut.asString()); (4)
assertEquals(200, response.getStatusCode()); (4)
}
}
1 | When activating the rxmicro.validation module, all query parameters are automatically considered as required.(Therefore the RxMicro Annotation Processor automatically adds a required validator for each parameter.
If the parameter should be optional , the model field should be annotated with the
@Nullable annotation.) |
2 | If request parameters are invalid, HTTP server automatically returns a status code 400 and JSON object of standard structure with detailed error description. |
3 | If an HTTP request validation error occurs, the request handler isn’t invoked from the REST controller. |
4 | If the request parameters are valid, control is transferred to the request handler from the REST controller. |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
4. HTTP Responses Server Validation
In addition to validating HTTP requests, the RxMicro framework also provides the option to validate HTTP responses.
(HTTP response validation can be useful for identifying errors in business task implementation algorithms. For example, instead of returning an incorrect response model to a client, the microservice will throw an error. This approach increases the speed of error search and debugging of the source code that performs the business task.)
If current implementation of the business task doesn’t contain any errors, then HTTP response validation will consume computing resources senselessly. In this case, HTTP response validation must be disabled! By default the response validators are generated but not invoked! To activate the validation of responses it is necessary to set
or
or using any other supported config types |
The HTTP response model class can contain any built-in or custom constraint annotations:
public final class Response {
final String message; (1)
public Response(final String message) {
this.message = message;
}
}
1 | In this example, the message field doesn’t explicitly contain any constraint annotations.
But since to the module-info.java descriptor was added the rxmicro.validation module, then all model fields not marked with the
@Nullable annotation are automatically required.
(In other words, such fields are implicitly marked by a virtual constraint annotation @Required .) |
To emulate an incorrect business value, null
value is passed in the request handler to the message
field:
final class MicroService {
@GET("/")
CompletableFuture<Response> get() {
return CompletableFuture.completedFuture(new Response(null));
}
}
In case of invalid HTTP response, the RxMicro framework returns HTTP response with a status 500
and standard error model:
@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {
private BlockingHttpClient blockingHttpClient;
@Test
void Should_produce_internal_error() {
final ClientHttpResponse response = blockingHttpClient.get("/");
assertEquals(
jsonErrorObject("Response is invalid: Parameter \"message\" is required!"),
response.getBody()
);
assertEquals(500, response.getStatusCode());
}
}
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
5. HTTP Responses Client Validation
After activation of the rxmicro.validation
module in the module-info.java
descriptor
module examples.validation.client.basic {
requires rxmicro.rest.client;
requires rxmicro.rest.client.exchange.json;
requires rxmicro.validation; (1)
}
1 | rxmicro.validation is a module for request and response validation. |
the developer can use built-in constraints to validate HTTP request parameters:
public final class Response {
(1)
@Email
String email;
public String getEmail() {
return email;
}
}
1 | The email parameter is annotated by the @Email annotation. |
After setting up the model class, this model must be used in the REST client:
@RestClient
public interface RESTClient {
@GET("/")
CompletableFuture<Response> get();
}
When converting the content of an HTTP response to the Response
class object, the validator will be invoked automatically:
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
private static final HttpRequestMock HTTP_REQUEST_MOCK = new HttpRequestMock.Builder()
.setMethod(HttpMethod.GET)
.setPath("/")
.build();
private RESTClient restClient;
@Alternative
@Mock
private HttpClientFactory httpClientFactory;
private void prepareValidResponse() {
prepareHttpClientMock(
httpClientFactory,
HTTP_REQUEST_MOCK,
jsonObject("email", "welcome@rxmicro.io") (1)
);
}
@Test
@BeforeThisTest(method = "prepareValidResponse")
void Should_return_received_email() {
final Response response = restClient.get().join();
assertEquals("welcome@rxmicro.io", response.getEmail()); (1)
}
private void prepareInvalidResponse() {
prepareHttpClientMock(
httpClientFactory,
HTTP_REQUEST_MOCK,
jsonObject("email", "rxmicro.io") (2)
);
}
@Test
@BeforeThisTest(method = "prepareInvalidResponse")
void Should_throw_UnexpectedResponseException() {
final Throwable throwable =
(4)
getRealThrowable(assertThrows(RuntimeException.class, () -> restClient.get().join())); (2)
assertEquals(UnexpectedResponseException.class, throwable.getClass()); (3)
assertEquals(
"Response is invalid: " +
"Invalid parameter \"email\": Expected a valid email format!", (3)
throwable.getMessage()
);
}
}
1 | If the REST client receives a valid HTTP response, no errors will occur. |
2 | If the email field is incorrect in the HTTP response, the REST client will return an error signal. |
3 | If the HTTP response is incorrect, the UnexpectedResponseException class exception is returned with a detailed text message. |
4 | Since CompletableFuture when receiving an error signal ALWAYS returns the CompletionException class exception, to get the original exception class, which was thrown during the lazy evaluation, it is necessary to use the
Exceptions.getRealThrowable(Throwable) utility method! |
Check out the following example to find out the features of the RxMicro framework for HTTP request validation in REST clients: |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
6. HTTP Requests Client Validation
In addition to validating HTTP responses, the RxMicro framework also provides the option to validate HTTP requests.
(HTTP request validation can be useful for identifying errors in business task implementation algorithms. For example, instead of returning an incorrect request model to a server, the REST client will throw an error. This approach increases the speed of error search and debugging of the source code that performs the business task.)
If current implementation of the business task doesn’t contain any errors, then HTTP request validation will consume computing resources senselessly. In this case, HTTP request validation must be disabled! By default the request validators are generated but not invoked!
To activate the validation of requests it is necessary to set
or
or using any other supported config types |
7. Creating Custom Constraints
If built-in constraints are not enough, the developer can create custom constraint. To do so, the following steps must be taken:
-
Create a constraint annotation.
-
Implement a validator.
A validation annotation is an annotation that meets the following requirements:
(1)
@Retention(CLASS)
(2)
@Target({FIELD, METHOD, PARAMETER})
(3)
@ConstraintRule(
supportedTypes = {
BigDecimal.class (4)
},
validatorClass = {
ExpectedZeroConstraintValidator.class (5)
}
)
public @interface ExpectedZero {
boolean off() default false; (6)
}
1 | The annotation is only available at the compilation level. |
2 | This annotation allows validating class fields, class methods (setters and getters ) and method parameters. |
3 | The
@ConstraintRule annotation is used to indicate: |
4 | data type; |
5 | validator class. |
6 | Each constraint annotation requires a required boolean off() default false; parameter, that allows You to disable the validator.(This feature is useful for model inheritance when a parameter from a child class should not be validated and a parameter from a parent class should be validated!) |
Validator is a class that meets the following requirements:
public final class ExpectedZeroConstraintValidator
implements ConstraintValidator<BigDecimal> { (1)
@Override
public void validateNonNull(final BigDecimal value,
final HttpModelType httpModelType,
final String modelName) throws ValidationException {
if (value.compareTo(BigDecimal.ZERO) != 0) { (2)
throw new ValidationException(
"Invalid ? \"?\": Expected a zero value!",
httpModelType, modelName
);
}
}
}
1 | The validator class must implement the
ConstraintValidator interface parameterized by the data type.(If constraint annotation can be applied to different data types, a separate validator class must be created for each data type.) |
2 | If the parameter is incorrect, the
ValidationException exception with a clear error message must be thrown. |
Using a custom validator is no different from using a predefined validator:
final class MicroService {
@PATCH("/")
void consume(final @ExpectedZero BigDecimal value) {
System.out.println(value);
}
}
@ParameterizedTest
@CsvSource({
"/, Parameter \"value\" is required!",
"/?value=, Parameter \"value\" is required!",
"/?value=1.23, Invalid parameter \"value\": Expected a zero value!",
"/?value=-3.45, Invalid parameter \"value\": Expected a zero value!"
})
void Should_return_invalid_request_status(final String path,
final String expectedErrorMessage) {
final ClientHttpResponse response = blockingHttpClient.patch(path);
assertEquals(jsonErrorObject(expectedErrorMessage), response.getBody());
assertEquals(400, response.getStatusCode());
assertEquals("", systemOut.asString());
}
@ParameterizedTest
@ValueSource(strings = {"0", "0.0", "0.000", "-0.0"})
void Should_handle_request(final String value) {
final ClientHttpResponse response = blockingHttpClient.patch("/?value=" + value);
assertEquals(new BigDecimal(value).toString(), systemOut.asString());
assertEquals(200, response.getStatusCode());
}
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
8. Disabling Validation
To disable the generation of validators, You must perform one of the following steps:
-
Delete the
rxmicro.validation
module from themodule-info.java
descriptor. -
Use
GenerateOption.DISABLED
options to disable specific categories of validators. -
Use the
@DisableValidation
annotation to disable validators of a selected group of classes in the project.
8.1. Removing the rxmicro.validation
Module
The easiest and fastest way to disable the generation of validators for all classes in the current module is to remove the rxmicro.validation
module from the module-info.java
descriptor.
After deleting the |
8.2. Using GenerateOption.DISABLED
Option
To disable the generation of validators by category, it is necessary to use annotations:
-
@RestServerGeneratorConfig
(to set up the REST controllers). -
@RestClientGeneratorConfig
(to set up the REST clients).
@RestServerGeneratorConfig(
generateRequestValidators = GenerateOption.DISABLED, (1)
generateResponseValidators = GenerateOption.DISABLED (2)
)
@RestClientGeneratorConfig(
generateRequestValidators = GenerateOption.DISABLED, (3)
generateResponseValidators = GenerateOption.DISABLED (4)
)
module examples.validation {
requires rxmicro.rest.server;
requires rxmicro.rest.client;
requires rxmicro.validation;
}
1 | Validators for all models of HTTP requests in the current project won’t be generated. |
2 | Validators for all models of HTTP responses in the current project won’t be generated. |
3 | Validators for all models of HTTP requests in the current project won’t be generated. |
4 | Validators for all models of HTTP responses in the current project won’t be generated. |
Upon activation of the All other categories of validators must be manually activated using the |
After changing the settings using the |
8.3. Using @DisableValidation
Annotation
The @DisableValidation
annotation provides an opportunity to disable the generation of validators for the selected group of classes in the project:
-
If a model class is annotated by this annotation, then only for this model class the validator won’t be generated.
-
If this annotation annotates the
package-info.java
class, then for all classes from the specified package and all its subpackages no validators will be generated. -
If this annotation annotates the
module-info.java
descriptor, then for all classes in the current module no validators will be generated.
(This behavior is similar to the removal of therxmicro.validation
module from themodule-info.java
descriptor.)
After adding the |