REST Client

Home

REST-based Microservice Documentation


© 2019-2020 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:

  1. Add the required dependencies to pom.xml.

  2. Add the rxmicro.validation module to module-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:

Table 1. Supported Built-in Constraints.
Annotation Supported Type Description

@AllowEmptyString

java.lang.String

The annotated element may be optional, i.e. empty string BUT must be not null!

@AssertFalse

java.lang.Boolean

The annotated element must be false.

@AssertTrue

java.lang.Boolean

The annotated element must be true.

@Base64URLEncoded

java.lang.String

The annotated element must be a valid Base64 string:

@CountryCode

java.lang.String

The annotated element must be a valid country code:

@DigitsOnly

java.lang.String

The annotated element must be a string value with digit characters only.

@Email

java.lang.String

The annotated element must be a well-formed email address.

@EndsWith

java.lang.String

The annotated element value must end with the provided suffix.

@Enumeration

The annotated element must be an element of the predefined enumeration.

This validation rule is useful when a Java enum type is not applicable. For example: if an enum item name equals to a Java keyword. To solve this issue use @Enumeration annotation, otherwise use a Java enum.

@Future

java.time.Instant

The annotated element must be an instant in the future.

@FutureOrPresent

java.time.Instant

The annotated element must be an instant in the present or in the future.

@HostName

java.lang.String

The annotated element must be a valid host name.

@IP

java.lang.String

The annotated element must be a valid IP address:

@Lat

java.math.BigDecimal

The annotated element must be a valid latitude coordinate.

@LatinAlphabetOnly

java.lang.String

The annotated element must be a string with latin alphabet letters only.

@Length

java.lang.String

The annotated element must have the expected string length.

@Lng

java.math.BigDecimal

The annotated element must be a valid longitude coordinate.

@Lowercase

java.lang.String

The annotated element must a lowercase string.

@MaxDouble

The annotated element must be a double whose value must be lower to the specified maximum.

@MaxInt

The annotated element must be a byte or short or integer or long whose value must be lower or equal to the specified maximum.

@MaxLength

java.lang.String

The annotated element must have a string length whose value must be lower or equal to the specified maximum.

@MaxNumber

The annotated element must be a number whose value must be lower or equal to the specified maximum.

@MaxSize

The annotated element must have a list size whose value must be lower or equal to the specified maximum.

@MinDouble

The annotated element must be a double whose value must be higher or equal to the specified minimum.

@MinInt

The annotated element must be a byte or short or integer or long whose value must be higher or equal to the specified minimum.

@MinLength

java.lang.String

The annotated element must have a string length whose value must be higher or equal to the specified minimum.

@MinNumber

The annotated element must be a number whose value must be higher or equal to the specified minimum.

@MinSize

The annotated element must have a list size whose value must be higher or equal to the specified minimum.

@Nullable

? extends java.lang.Object

The annotated element may be optional, i.e. null.

@NullableArrayItem

The annotated array element may be optional, i.e. null.

@Numeric

java.math.BigDecimal

The annotated element must be a decimal within accepted range (scale and precision).

@Past

java.time.Instant

The annotated element must be an instant in the past.

@PastOrPresent

java.time.Instant

The annotated element must be an instant in the past or in the present.

@Pattern

java.lang.String

The annotated String must match the specified regular expression. The regular expression follows the Java regular expression conventions.

(See java.util.regex.Pattern).

@Phone

java.lang.String

The annotated element must be a valid phone number.

@Size

The annotated element must have the expected list size.

@Skype

java.lang.String

The annotated element must be a valid skype number.

@StartsWith

java.lang.String

The annotated element value must start with the provided prefix.

@SubEnum

? extends java.lang.Enum

The annotated element must be an enumeration with predefined sub sequence.

@Telegram

java.lang.String

The annotated element must be a valid telegram number.

@TruncatedTime

java.time.Instant

The annotated element must be an instant with truncated time value.

@UniqueItems

java.util.List

The annotated element must contain unique items.

@Uppercase

java.lang.String

The annotated element must an uppercase string.

@URI

java.lang.String

The annotated element must be a valid java.net.URI address.

(See Uniform Resource Identifier)

@URLEncoded

java.lang.String

The annotated element must be a valid URL encoded value.

@Viber

java.lang.String

The annotated element must be a valid viber number.

@WhatsApp

java.lang.String

The annotated element must be a valid whatsapp number.

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 RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

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 enableAdditionalValidations property:

new Configs.Builder()
        .withConfigs(new RestServerConfig()
                .setEnableAdditionalValidations(true)) (1)
        .build();
1 Enables the response validators

or

export rest-server.enableAdditionalValidations=true (1)
1 Enables the response validators

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 RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

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 RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

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 enableAdditionalValidations property:

new Configs.Builder()
        .withConfigs(new RestClientConfig()
                .setEnableAdditionalValidations(true)) (1)
        .build();
1 Enables the requests validators

or

export rest-client.enableAdditionalValidations=true (1)
1 Enables the response validators

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:

  1. Create a constraint annotation.

  2. 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 validate(final BigDecimal value,
                         final HttpModelType httpModelType,
                         final String modelName) throws ValidationException {
        if (value != null) { (2)
            if (value.compareTo(BigDecimal.ZERO) != 0) { (3)
                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 Validator must ignore null values.
(To check for null there is a predefined validator.)
3 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 RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

8. Disabling Validation

To disable the generation of validators, You must perform one of the following steps:

  1. Delete the rxmicro.validation module from the module-info.java descriptor.

  2. Use GenerateOption.DISABLED options to disable specific categories of validators.

  3. 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 rxmicro.validation module DON’T FORGET to recompile the whole project for the changes to take effect: mvn clean compile!

8.2. Using GenerateOption.DISABLED Option

To disable the generation of validators by category, it is necessary to use annotations:

@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 rxmicro.validation module, by default the RxMicro framework generates only HTTP request validators for REST controllers and HTTP response validators for REST clients!

All other categories of validators must be manually activated using the @RestServerGeneratorConfig and @RestClientGeneratorConfig annotations!

After changing the settings using the @RestServerGeneratorConfig and @RestClientGeneratorConfig annotations DON’T FORGET to recompile the whole project for the changes to take effect: mvn clean compile!

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:

  1. If a model class is annotated by this annotation, then only for this model class the validator won’t be generated.

  2. 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.

  3. 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 the rxmicro.validation module from the module-info.java descriptor.)

After adding the @DisableValidation annotation DON’T FORGET to recompile the whole project for the changes to take effect: mvn clean compile!

REST Client

Home

REST-based Microservice Documentation