© 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. |
REST Client is an interface that contains at least one declarative HTTP request handler.
To create REST clients, the RxMicro framework provides the following modules:
-
The
rxmicro.rest
is a basic module that defines basicRxMicro Annotations
, required when using theREST
architecture of building program systems; -
The
rxmicro.rest.client
is a basic module used to create and run REST clients; -
The
rxmicro.rest.client.jdk
is an HTTP client implementation module based onJava HTTP Client
; -
The
rxmicro.rest.client.exchange.json
is a module for converting Java models toJSON
format and vice versa;
Due to transit dependencies only two modules usually need to be added to a project:
|
1. REST Client Implementation Requirements
1.1. REST Client Interface Requirements
REST Client is a Java interface that annotated by the
@RestClient
annotation:
import java.util.concurrent.CompletableFuture;
import io.rxmicro.rest.client.RestClient;
import io.rxmicro.rest.method.GET;
@RestClient
public interface RESTClient {
@GET("/")
CompletableFuture<Model> getContent();
}
that must meet the following requirements:
-
The interface must be a
public
one. -
The interface must be annotated by the required
@RestClient
annotation. -
The interface couldn’t extend any other interfaces.
-
The interface couldn’t be a nested one.
1.2. HTTP Request Handler Requirements
HTTP request handler is a method, that must meet the following requirements:
-
The method couldn’t be a
private
one. -
The method couldn’t be an
abstract
one. -
The method couldn’t be a
static
one. -
The method must be annotated by only one of the following annotations:
-
The method must return one of the following reactive types:
-
If the method returns a reactive type, that this type must be parametrized by a HTTP response model type. (The additional types such as
java.lang.Void
andjava.util.Optional
are supported also. (Read more: core.html)).
A REST client can contain |
The
For such cases the HTTP protocol provides the special |
The RxMicro framework supports the following parameter types for the HTTP request handler:
-
Handler without parameters.
(This type is recommended for the simpleststateless
REST clients without parameters.) -
List of primitive parameters.
(This type is recommended for REST clients, the behavior of which depends on 1-2 parameters.) -
Custom class modeling an HTTP request.
(This type is recommended for REST clients, the behavior of which depends on 3 or more parameters.)
When using the Project Reactor and RxJava reactive libraries, You need:
Adding dependencies to
Adding modules to
|
2. RxMicro Annotations
The RxMicro framework supports the following RxMicro Annotations
, which are used to create and configure REST Clients.
Annotation | Description |
---|---|
Denotes that an interface is a dynamic generated REST client. |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a base URL path for the all request handlers at the REST client. |
|
Denotes a version of the REST client. |
|
Denotes that a field of Java model class is a HTTP header. |
|
Declares a strategy of header name formation based on Java model field name analysis. (By default, the
|
|
Denotes a static HTTP header that must be added to the request, created by REST client implementation. |
|
Denotes a static HTTP header that must be set to the request, created by REST client implementation. |
|
Denotes the header, which name needs to be repeated for each element in the list. (This annotation applies only to fields with the |
|
Denotes that a field of Java model class is a HTTP parameter. |
|
Declares a strategy of parameter name formation based on Java model field name analysis. (By default, the
|
|
Denotes a static query parameter that must be added to the request, created by REST client implementation. |
|
Denotes a static query parameter that must be set to the request, created by REST client implementation. |
|
Denotes the query parameter, which name needs to be repeated for each element in the list. (This annotation applies only to fields with the |
|
Denotes that a field of Java model class is a |
|
Declares the Java model field as a field, that the RxMicro framework must used as a unique request ID and sent it to the server. |
|
Indicates to the RxMicro framework that the value of the Java model field should be used as a status code that received from the server. |
|
Indicates to the RxMicro framework that the value of the Java model field should be used as a body that received from the server. |
|
Denotes an abstract class that contains a partial implementation of the annotated by this annotation REST client interface. |
|
Allows to configure the process of code generation by the |
3. HTTP Request Handler Return Types
The HTTP request handler supports two categories of returned results:
-
HTTP response without body;
-
HTTP response with body;
3.1. Supported Return Result Types for HTTP Response without Body
The RxMicro framework supports the following return result types for an HTTP response without body:
@RestClient
public interface RestClientWithoutBody {
(1)
@GET("/jse/completedFuture1")
CompletableFuture<Void> completedFuture1();
@GET("/jse/completedFuture2")
CompletableFuture<Void> completedFuture2(final Request request);
@GET("/jse/completedFuture3")
CompletableFuture<Void> completedFuture3(final String requestParameter);
(2)
@GET("/jse/completionStage1")
CompletionStage<Void> completionStage1();
@GET("/jse/completionStage2")
CompletionStage<Void> completionStage2(final Request request);
@GET("/jse/completionStage3")
CompletionStage<Void> completionStage3(final String requestParameter);
(3)
@GET("/spring-reactor/mono1")
Mono<Void> mono1();
@GET("/spring-reactor/mono2")
Mono<Void> mono2(final Request request);
@GET("/spring-reactor/mono3")
Mono<Void> mono3(final String requestParameter);
(4)
@GET("/rxjava3/completable1")
Completable completable1();
@GET("/rxjava3/completable2")
Completable completable2(final Request request);
@GET("/rxjava3/completable3")
Completable completable3(final String requestParameter);
}
1 | The CompletableFuture<Void>
type is recommended when using the java.util.concurrent library. |
2 | Instead of the CompletableFuture<Void> type, can also be used the
CompletionStage<Void> type. |
3 | When using the Project Reactor library, only the
Mono<Void> type can be used. |
4 | When using the RxJava library, only the
Completable type can be used. |
All the above mentioned HTTP request handlers shouldn’t throw any exceptions:
@InitMocks
@RxMicroComponentTest(RestClientWithoutBody.class)
final class RestClientWithoutBodyTest {
private RestClientWithoutBody restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
static Stream<Consumer<RestClientWithoutBody>> clientMethodsProvider() {
return Stream.of(
client -> client.completedFuture1().join(),
client -> client.completedFuture2(new Request("param")).join(),
client -> client.completedFuture3("param").join(),
client -> client.completionStage1().toCompletableFuture().join(),
client -> client.completionStage2(
new Request("param")).toCompletableFuture().join(),
client -> client.completionStage3("param").toCompletableFuture().join(),
client -> client.mono1().block(),
client -> client.mono2(new Request("param")).block(),
client -> client.mono3("param").block(),
client -> client.completable1().blockingAwait(),
client -> client.completable2(new Request("param")).blockingAwait(),
client -> client.completable3("param").blockingAwait()
);
}
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder().setAnyRequest().build(),
true
);
}
@ParameterizedTest
@MethodSource("clientMethodsProvider")
@BeforeThisTest(method = "prepare")
void Should_be_invoked_successfully(final Consumer<RestClientWithoutBody> clientMethod) {
assertDoesNotThrow(() -> clientMethod.accept(restClient));
}
}
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 |
3.2. Supported Return Result Types for HTTP Response with Body
The RxMicro framework supports the following return result types for an HTTP response with body:
@RestClient
public interface RestClientWithBody {
(1)
@GET("/jse/completedFuture1")
CompletableFuture<Response> completedFuture1();
@GET("/jse/completedFuture2")
CompletableFuture<Response> completedFuture2(final Request request);
@GET("/jse/completedFuture3")
CompletableFuture<Response> completedFuture3(final String requestParameter);
(2)
@GET("/jse/completionStage1")
CompletionStage<Response> completionStage1();
@GET("/jse/completionStage2")
CompletionStage<Response> completionStage2(final Request request);
@GET("/jse/completionStage3")
CompletionStage<Response> completionStage3(final String requestParameter);
(3)
@GET("/spring-reactor/mono1")
Mono<Response> mono1();
@GET("/spring-reactor/mono2")
Mono<Response> mono2(final Request request);
@GET("/spring-reactor/mono3")
Mono<Response> mono3(final String requestParameter);
(4)
@GET("/rxjava3/single1")
Single<Response> single1();
@GET("/rxjava3/single2")
Single<Response> single2(final Request request);
@GET("/rxjava3/single3")
Single<Response> single3(final String requestParameter);
}
1 | The CompletableFuture<MODEL>
type is recommended when using the java.util.concurrent library. |
2 | Instead of the CompletableFuture<MODEL> type, can also be used the
CompletionStage<MODEL> type. |
3 | When using the Project Reactor library, only the
Mono<MODEL> type can be used. |
4 | When using the RxJava library, only the
Single<MODEL> type can be used. |
Note that the reactive types must be parameterized by the HTTP response model class! |
All of the above mentioned handlers return the Response
class instance with the Hello World!
message:
@InitMocks
@RxMicroComponentTest(RestClientWithBody.class)
final class RestClientWithBodyTest {
private RestClientWithBody restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
static Stream<Function<RestClientWithBody, Response>> clientMethodsProvider() {
return Stream.of(
client -> client.completedFuture1().join(),
client -> client.completedFuture2(new Request("param")).join(),
client -> client.completedFuture3("param").join(),
client -> client.completionStage1().toCompletableFuture().join(),
client -> client.completionStage2(
new Request("param")).toCompletableFuture().join(),
client -> client.completionStage3("param").toCompletableFuture().join(),
client -> client.mono1().block(),
client -> client.mono2(new Request("param")).block(),
client -> client.mono3("param").block(),
client -> client.single1().blockingGet(),
client -> client.single2(new Request("param")).blockingGet(),
client -> client.single3("param").blockingGet()
);
}
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder().setAnyRequest().build(),
jsonObject("message", "Hello World!"),
true
);
}
@ParameterizedTest
@MethodSource("clientMethodsProvider")
@BeforeThisTest(method = "prepare")
void Should_return_message_Hello_World(final Function<RestClientWithBody, Response> clientMethod) {
final Response response = assertDoesNotThrow(() -> clientMethod.apply(restClient));
assertEquals("Hello World!", response.getMessage());
}
}
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 Headers Handling
4.1. Basic Rules
The RxMicro framework supports HTTP headers in request and response models:
(1)
@HeaderMappingStrategy
public final class Request {
(2)
@Header
String endpointVersion;
(3)
@Header("UseProxy")
Boolean useProxy;
public String getEndpointVersion() {
return endpointVersion;
}
public Boolean getUseProxy() {
return useProxy;
}
}
(1)
@HeaderMappingStrategy
public final class Response {
(2)
@Header
final String endpointVersion;
(3)
@Header("UseProxy")
final Boolean useProxy;
public Response(final Request request) {
this.endpointVersion = request.getEndpointVersion();
this.useProxy = request.getUseProxy();
}
public Response(final String endpointVersion,
final Boolean useProxy) {
this.endpointVersion = endpointVersion;
this.useProxy = useProxy;
}
}
1 | The @HeaderMappingStrategy
annotation allows setting common conversion rules for all header names from HTTP to Java format and vice versa in the current model class.(By default, the CAPITALIZE_WITH_HYPHEN strategy is used.
The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP header name is generated.) |
2 | In order to declare a model field as the HTTP header field, it is necessary to use the
@Header annotation. |
3 | Using the @Header
annotation, it is possible to specify the HTTP header name, which does not correspond to the used strategy declared by the
@HeaderMappingStrategy annotation. |
The RxMicro framework uses the following algorithm to define the HTTP header name for the specified model field:
-
If the field is annotated by the
@Header
annotation with an explicit indication of the HTTP header name, the specified name is used; -
If no HTTP header name is specified in the
@Header
annotation, the RxMicro framework checks for the@HeaderMappingStrategy
annotation above the model class; -
If the model class is annotated by the
@HeaderMappingStrategy
annotation, then the specified naming strategy is used.
(The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP header name is generated.) -
If the
@HeaderMappingStrategy
annotation is missing, the model class field name is used as the HTTP header name.
After creating model classes, it is necessary to create a request handler that uses the following models:
@RestClient
public interface SimpleUsageRestClient {
@GET("/get1")
CompletableFuture<Response> get1(final Request request); (1)
(2)
@GET("/get2")
(3)
@HeaderMappingStrategy
CompletableFuture<Response> get2(final @Header String endpointVersion, (4)
final @Header("UseProxy") Boolean useProxy); (5)
}
1 | If a separate model class has been created for an HTTP request, then this class must be passed by the method parameter that handles the HTTP request. |
2 | Besides supporting HTTP request model classes, the RxMicro framework supports request handlers that accept HTTP headers as method parameters. |
3 | To define a common naming strategy for all HTTP headers which are passed as method parameters, the request handler must be annotated by the @HeaderMappingStrategy annotation. |
4 | To declare a method parameter as an HTTP header field, use the @Header annotation. |
5 | Using the @Header annotation, it is possible to specify an HTTP header name that does not correspond to the strategy used, declared by the @HeaderMappingStrategy annotation. |
The RxMicro framework recommends for request handlers that depend on 3 or more HTTP headers to create separate classes of request model. Upon implementation of HTTP headers directly into the handler, the code becomes hard to read! |
Despite the different approaches to HTTP header handling support, as to the message generated by the HTTP protocol, the two above-mentioned handlers are absolutely equal:
@InitMocks
@RxMicroComponentTest(SimpleUsageRestClient.class)
final class SimpleUsageRestClientTest {
private static final String ENDPOINT_VERSION = "v1";
private static final Boolean USE_PROXY = true;
private SimpleUsageRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
static Stream<Function<SimpleUsageRestClient, Response>> restClientExecutableProvider() {
return Stream.of(
client -> client.get1(new Request(ENDPOINT_VERSION, USE_PROXY)).join(),
client -> client.get2(ENDPOINT_VERSION, USE_PROXY).join()
);
}
private void prepare() {
final HttpHeaders headers = HttpHeaders.of(
"Endpoint-Version", ENDPOINT_VERSION, (2)
"UseProxy", USE_PROXY (3)
);
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setAnyPath()
.setHeaders(headers)
.build(),
new HttpResponseMock.Builder()
.setHeaders(headers)
.build(),
true
);
}
@ParameterizedTest
@MethodSource("restClientExecutableProvider")
@BeforeThisTest(method = "prepare")
void Should_process_HTTP_headers(
final Function<SimpleUsageRestClient, Response> clientMethod) {
final Response response = assertDoesNotThrow(() ->
clientMethod.apply(restClient)); (1)
assertEquals(ENDPOINT_VERSION, response.getEndpointVersion()); (4)
assertEquals(USE_PROXY, response.getUseProxy()); (4)
}
}
1 | When performing a request to different URL Paths, the result is the same. |
2 | The Endpoint-Version name corresponds to the endpointVersion field of the request model.(This correspondence is formed basing on the default strategy use, which is defined by the @HeaderMappingStrategy annotation.) |
3 | The UseProxy name corresponds to the useProxy field of the request model, since this name is specified in the @Header annotation. |
4 | After executing the request in the resulting HTTP response, the Endpoint-Version and UseProxy headers are equal to v1 and true respectively.
(The handler returns the same header values it received from an HTTP request.) |
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.2. Supported Data Types
The RxMicro framework supports the following Java types, which can be HTTP request model headers:
-
? extends Enum<?>
; -
java.lang.Boolean
; -
java.lang.Byte
; -
java.lang.Short
; -
java.lang.Integer
; -
java.lang.Long
; -
java.math.BigInteger
; -
java.lang.Float
; -
java.lang.Double
; -
java.math.BigDecimal
; -
java.lang.Character
; -
java.lang.String
; -
java.time.Instant
;
For floating point numbers, it is suggested to use the Using the |
and also the java.util.List<T>
, java.util.Set<T>
and the java.util.Map<String, T>
type is parameterized by any of the above mentioned primitive types.
If the |
4.3. Static HTTP Headers
For static header installation, the RxMicro framework provides the
@AddHeader
and
@SetHeader
annotations:
@RestClient
(1)
@SetHeader(name = "Mode", value = "Demo")
public interface StaticHeadersRestClient {
@GET("/get1")
CompletableFuture<Void> get1();
@GET("/get2")
(2)
@SetHeader(name = "Debug", value = "true")
CompletableFuture<Void> get2();
}
1 | If the HTTP header is set for the REST client, it is added to the HTTP requests for each handler. |
2 | If the HTTP header is set for the handler, it is added to the HTTP requests only for that handler. |
In terms of the HTTP protocol, static headers do not differ from any others:
@InitMocks
@RxMicroComponentTest(StaticHeadersRestClient.class)
final class StaticHeadersRestClientTest {
private StaticHeadersRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepareParentHeaderOnly() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/get1")
.setHeaders(HttpHeaders.of(
"Mode", "Demo"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareParentHeaderOnly")
void Should_use_parent_header_only() {
assertDoesNotThrow(() -> restClient.get1().join());
}
private void prepareParentAndChildHeaders() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/get2")
.setHeaders(HttpHeaders.of(
"Mode", "Demo",
"Debug", "true"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareParentAndChildHeaders")
void Should_use_parent_and_child_headers() {
assertDoesNotThrow(() -> restClient.get2().join());
}
}
In order to understand the differences between the |
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.4. Repeating HTTP Headers
If the HTTP header of an HTTP request is a list of values, the list elements are transferred by default via the HTTP protocol as a string separated by the |
symbol.
If You want the HTTP header to be repeated for each value, You need to use the
@RepeatHeader
annotation:
@RestClient
public interface RepeatingHeadersRestClient {
@PUT("/")
@HeaderMappingStrategy
CompletableFuture<Void> put(@Header List<Status> singleHeader,
@Header @RepeatHeader List<Status> repeatingHeader);
}
The use of the For response models it makes no sense, because the RxMicro framework converts any of the supported formats into a list of values. |
As a result of converting the Java model to HTTP response, the result will be as follows:
@InitMocks
@RxMicroComponentTest(RepeatingHeadersRestClient.class)
final class RepeatingHeadersRestClientTest {
private RepeatingHeadersRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(PUT)
.setPath("/")
.setHeaders(HttpHeaders.of(
"Single-Header", "created|approved|rejected", (1)
"Repeating-Header", "created",
"Repeating-Header", "approved", (2)
"Repeating-Header", "rejected"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_repeating_headers() {
final List<Status> headers = List.of(created, approved, rejected);
assertDoesNotThrow(() -> restClient.put(headers, headers).join());
}
}
1 | By default, HTTP header list elements are transferred via the HTTP protocol as a string separated by the | symbol. |
2 | If the field is annotated by the @RepeatHeader annotation, then the header is repeated for each element of the list. |
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 Parameters Handling
5.1. Basic Rules
The RxMicro framework supports HTTP parameters in request and response models:
(1)
@ParameterMappingStrategy
public final class Request {
(2)
String endpointVersion;
(3)
@Parameter("use-Proxy")
Boolean useProxy;
public String getEndpointVersion() {
return endpointVersion;
}
public Boolean getUseProxy() {
return useProxy;
}
}
(1)
@ParameterMappingStrategy
public final class Response {
(2)
final String endpointVersion;
(3)
@Parameter("use-Proxy")
final Boolean useProxy;
public Response(final Request request) {
this.endpointVersion = request.getEndpointVersion();
this.useProxy = request.getUseProxy();
}
public Response(final String endpointVersion,
final Boolean useProxy) {
this.endpointVersion = endpointVersion;
this.useProxy = useProxy;
}
}
1 | The @ParameterMappingStrategy
annotation allows setting common conversion rules for all parameter names from HTTP to Java format and vice versa in the current model class.
(By default, the
LOWERCASE_WITH_UNDERSCORED strategy is used.
The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP parameter name is generated.) |
2 | In order to declare a model field as the HTTP parameter field, it is necessary to use the
@Parameter annotation.
(Unlike the @Header annotation, the @Parameter annotation is optional.
Thus, if the model field is not annotated by any annotation, then by default it is assumed that there is the @Parameter annotation above the model field.) |
3 | Using the @Parameter
annotation, it is possible to specify the HTTP parameter name, which does not correspond to the used strategy declared by the
@ParameterMappingStrategy annotation. |
The RxMicro framework uses the following algorithm to define the HTTP parameter name for the specified model field:
-
If the field is annotated by the
@Parameter
annotation with an explicit indication of the HTTP parameter name, the specified name is used; -
If no HTTP parameter name is specified in the
@Parameter
annotation, the RxMicro framework checks for the@ParameterMappingStrategy
annotation above the model class; -
If the model class is annotated by the
@ParameterMappingStrategy
annotation, then the specified naming strategy is used. (The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP parameter name is generated.) -
If the
@ParameterMappingStrategy
annotation is missing, the model class field name is used as the HTTP parameter name.
Unlike the Thus, if the model field is not annotated by any annotation, then by default it is assumed that there is the |
After creating model classes, it is necessary to create an HTTP request handler that uses the following models:
@RestClient
public interface SimpleUsageRestClient {
@GET("/get1")
CompletableFuture<Response> get1(final Request request); (1)
(2)
@GET("/get2")
(3)
@ParameterMappingStrategy
CompletableFuture<Response> get2(final String endpointVersion, (4)
final @Parameter("use-Proxy") Boolean useProxy); (5)
}
1 | If a separate model class has been created for an HTTP request, then this class must be passed by the method parameter that handles the HTTP request. |
2 | Besides supporting HTTP request model classes, the RxMicro framework supports request handlers that accept HTTP request parameters as method parameters. |
3 | To define a common naming strategy for all HTTP parameters which are passed as method parameters, the request handler must be annotated by the @ParameterMappingStrategy annotation. |
4 | To declare a method parameter as an HTTP parameter field, You do not need to use any additional annotations. |
5 | Using the @Parameter annotation, it is possible to specify an HTTP parameter name that does not correspond to the strategy used, declared by the @ParameterMappingStrategy annotation. |
Note that the RxMicro framework does not distinguish whether HTTP request parameters will be transferred to the handler using the start line or HTTP body! |
The RxMicro framework recommends for request handlers that depend on 3 or more HTTP parameters to create separate classes of request model. Upon implementation of HTTP parameters directly into the handler, the code becomes hard to read! |
Despite the different approaches to HTTP parameter handling support, as to the message generated by the HTTP protocol, the two above-mentioned handlers are absolutely equal:
@InitMocks
@RxMicroComponentTest(SimpleUsageRestClient.class)
final class SimpleUsageRestClientTest {
private static final String ENDPOINT_VERSION = "v1";
private static final Boolean USE_PROXY = true;
private static final QueryParams QUERY_PARAMETERS = QueryParams.of(
"endpoint_version", ENDPOINT_VERSION,
"use-Proxy", USE_PROXY
);
private static final Object BODY = jsonObject(
"endpoint_version", ENDPOINT_VERSION,
"use-Proxy", USE_PROXY
);
private SimpleUsageRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare1() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.GET)
.setPath("/get1")
.setQueryParameters(QUERY_PARAMETERS)
.build(),
BODY,
true
);
}
@Test
@BeforeThisTest(method = "prepare1")
void Should_use_handler_with_request_class() {
final Response response =
assertDoesNotThrow(() ->
restClient.get1(new Request(ENDPOINT_VERSION, USE_PROXY)).join());
assertEquals(ENDPOINT_VERSION, response.getEndpointVersion());
assertEquals(USE_PROXY, response.getUseProxy());
}
private void prepare2() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.GET)
.setPath("/get2")
.setQueryParameters(QUERY_PARAMETERS)
.build(),
BODY,
true
);
}
@Test
@BeforeThisTest(method = "prepare2")
void Should_use_handler_with_request_params() {
final Response response =
assertDoesNotThrow(() ->
restClient.get2(ENDPOINT_VERSION, USE_PROXY).join());
assertEquals(ENDPOINT_VERSION, response.getEndpointVersion());
assertEquals(USE_PROXY, response.getUseProxy());
}
}
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.2. Supported Data Types
The RxMicro framework supports the following Java types, which can be HTTP request model parameters:
-
? extends Enum<?>
; -
java.lang.Boolean
; -
java.lang.Byte
; -
java.lang.Short
; -
java.lang.Integer
; -
java.lang.Long
; -
java.math.BigInteger
; -
java.lang.Float
; -
java.lang.Double
; -
java.math.BigDecimal
; -
java.lang.Character
; -
java.lang.String
; -
java.time.Instant
; -
? extends Object
; -
java.util.List<? extends Object>
; -
java.util.Set<? extends Object>
; -
java.util.Map<java.lang.String, ? extends Object>
;
For floating point numbers, it is suggested to use the Using the |
and also the java.util.List<T>
, java.util.Set<T>
and the java.util.Map<String, T>
type is parameterized by any of the above mentioned primitive types.
If the |
5.3. Complex Model Support
Unlike HTTP headers, HTTP parameters can be transferred in the request body.
Therefore, besides primitives the RxMicro framework also supports nested JSON objects and arrays.
Therefore, the list of supported types includes the following types:
|
Nested Model Example:
@ParameterMappingStrategy
public final class NestedModel {
String stringParameter;
BigDecimal bigDecimalParameter;
Instant instantParameter;
public NestedModel(final String stringParameter,
final BigDecimal bigDecimalParameter,
final Instant instantParameter) {
this.stringParameter = stringParameter;
this.bigDecimalParameter = bigDecimalParameter;
this.instantParameter = instantParameter;
}
public NestedModel() {
}
@Override
public int hashCode() {
return Objects.hash(stringParameter, bigDecimalParameter, instantParameter);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final NestedModel that = (NestedModel) o;
return stringParameter.equals(that.stringParameter) &&
bigDecimalParameter.equals(that.bigDecimalParameter) &&
instantParameter.equals(that.instantParameter);
}
}
As well as examples of more complex Java models that use a nested model:
@ParameterMappingStrategy
public final class ComplexRequest {
final Integer integerParameter; (1)
final Status enumParameter; (1)
final List<Status> enumsParameter; (2)
final NestedModel nestedModelParameter; (3)
final List<NestedModel> nestedModelsParameter; (4)
public ComplexRequest(final Integer integerParameter,
final Status enumParameter,
final List<Status> enumsParameter,
final NestedModel nestedModelParameter,
final List<NestedModel> nestedModelsParameter) {
this.integerParameter = integerParameter;
this.enumParameter = enumParameter;
this.enumsParameter = enumsParameter;
this.nestedModelParameter = nestedModelParameter;
this.nestedModelsParameter = nestedModelsParameter;
}
}
@ParameterMappingStrategy
public final class ComplexResponse {
Integer integerParameter; (1)
Status enumParameter; (1)
List<Status> enumsParameter; (2)
NestedModel nestedModelParameter; (3)
List<NestedModel> nestedModelsParameter; (4)
public Integer getIntegerParameter() {
return integerParameter;
}
public Status getEnumParameter() {
return enumParameter;
}
public List<Status> getEnumsParameter() {
return enumsParameter;
}
public NestedModel getNestedModelParameter() {
return nestedModelParameter;
}
public List<NestedModel> getNestedModelsParameter() {
return nestedModelsParameter;
}
}
1 | Primitive JSON type field. |
2 | Primitive JSON array type. |
3 | Nested JSON object. |
4 | JSON array of JSON nested objects. |
After creating model classes, it is necessary to create a request handler that uses the following models:
@RestClient
public interface ComplexModelRestClient {
@POST("/")
CompletableFuture<ComplexResponse> post(ComplexRequest request);
}
Since the request is transferred in an HTTP body, the sent and received JSON objects must be the same:
@InitMocks
@RxMicroComponentTest(ComplexModelRestClient.class)
final class ComplexModelRestClientTest {
private final Integer integerParameter = 1;
private final Status enumParameter = created;
private final List<Status> enumsParameter = List.of(created, approved);
private final NestedModel nestedModelParameter =
new NestedModel("test", new BigDecimal("3.14"), Instant.now());
private final List<NestedModel> nestedModelsParameter = List.of(
new NestedModel("test1", new BigDecimal("1.1"), Instant.now()),
new NestedModel("test2", new BigDecimal("1.2"), Instant.now())
);
private ComplexModelRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(POST)
.setPath("/")
.setAnyBody()
.build(),
new HttpResponseMock.Builder()
.setReturnRequestBody()
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_complex_requests_and_responses() {
final ComplexResponse response = assertDoesNotThrow(() ->
restClient.post(new ComplexRequest(
integerParameter,
enumParameter,
enumsParameter,
nestedModelParameter,
nestedModelsParameter
)).join());
assertEquals(integerParameter, response.getIntegerParameter());
assertEquals(enumParameter, response.getEnumParameter());
assertEquals(enumsParameter, response.getEnumsParameter());
assertEquals(nestedModelParameter, response.getNestedModelParameter());
assertEquals(nestedModelsParameter, response.getNestedModelsParameter());
}
}
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.4. Static Query Parameters
For setting static query parameters, the RxMicro framework provides the
@AddQueryParameter
and
@SetQueryParameter
annotations:
@RestClient
(1)
@SetQueryParameter(name = "mode", value = "demo")
public interface StaticQueryParametersRestClient {
@GET("/get1")
CompletableFuture<Void> get1();
@GET("/get2")
(2)
@SetQueryParameter(name = "debug", value = "true")
CompletableFuture<Void> get2();
}
1 | If the static query parameter is set for the REST client, it is added to the HTTP requests for each handler. |
2 | If the static query parameter is set for the handler, it is added to the HTTP requests only for that handler. |
In terms of the HTTP protocol, static query parameters do not differ from any standard parameter:
@InitMocks
@RxMicroComponentTest(StaticQueryParametersRestClient.class)
final class StaticQueryParametersRestClientTest {
private StaticQueryParametersRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepareParentParamOnly() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/get1")
.setQueryParameters(QueryParams.of(
"mode", "demo"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareParentParamOnly")
void Should_use_parent_param_only() {
assertDoesNotThrow(() -> restClient.get1().join());
}
private void prepareParentAndChildParams() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/get2")
.setQueryParameters(QueryParams.of(
"debug", true,
"mode", "demo"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareParentAndChildParams")
void Should_use_parent_and_child_params() {
assertDoesNotThrow(() -> restClient.get2().join());
}
}
In order to understand the differences between the |
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.5. Repeating Query Parameters
If the static query parameter of an HTTP request is a list of values, the list elements are transferred by default via the HTTP protocol as a string separated by the |
symbol.
If You want the static query parameter to be repeated for each value, You need to use the
@RepeatQueryParameter
annotation:
@RestClient
public interface RepeatingQueryParamsRestClient {
@PUT(value = "/", httpBody = false)
@ParameterMappingStrategy
CompletableFuture<Void> put(List<Status> singleHeader,
@RepeatQueryParameter List<Status> repeatingHeader);
}
As a result of converting the Java model to HTTP response, the result will be as follows:
@InitMocks
@RxMicroComponentTest(RepeatingQueryParamsRestClient.class)
final class RepeatingQueryParamsRestClientTest {
private RepeatingQueryParamsRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(PUT)
.setPath("/")
.setQueryParameters(QueryParams.of(
"single_header", "created|approved|rejected", (1)
"repeating_header", "created",
"repeating_header", "approved", (2)
"repeating_header", "rejected"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_repeating_headers() {
final List<Status> headers = List.of(created, approved, rejected);
assertDoesNotThrow(() -> restClient.put(headers, headers).join());
}
}
1 | By default, static query parameter list elements are transferred via the HTTP protocol as a string separated by the | symbol. |
2 | If the field is annotated by the @RepeatQueryParameter annotation, then the static query parameter is repeated for each element of the list. |
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. The path variables
Support
6.1. Basic Rules
The RxMicro framework supports path variables
in request models:
public final class Request {
(1)
@PathVariable
String category;
(2)
@PathVariable("class")
String type;
@PathVariable
String subType;
public String getCategory() {
return category;
}
public String getType() {
return type;
}
public String getSubType() {
return subType;
}
}
1 | In order to declare a model field as the path variable , it is necessary to use the
@PathVariable annotation. |
2 | Using the @PathVariable annotation, it is possible to specify the path variable name.(If no name is specified, the model field name is used as the path variable name.) |
Unlike HTTP headers and parameters that are available also on the client side, So for simplicity it is recommended to always use the model field name as the |
After creating model classes, it is necessary to create an HTTP request handler that uses the following model:
@RestClient
public interface RESTClient {
(1)
@GET("/${category}/${class}-${subType}")
CompletableFuture<Void> consume(final Request request);
(1)
@GET("/${category}/${class}-${subType}")
CompletableFuture<Void> consume(@PathVariable String category, (2)
@PathVariable("class") String type, (3)
@PathVariable String subType);
}
1 | When using path variables in request models, be sure to use all path variables in the URL Path.
(Based on the analysis of the URL Path, and considering all path variables , the RxMicro framework generates the final URL to which the HTTP request will be sent.) |
2 | In order to declare a method parameter as path variable , You must use the @PathVariable annotation. |
3 | Using the @PathVariable annotation it is possible to specify the path variable name, which does not match the name of the method parameter. |
The RxMicro framework recommends for request handlers that depend on 3 or more
Upon implementation of
|
Despite the different approaches to path variables
handling support, in terms of the final URL the two above-mentioned handlers are absolutely equal:
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
private RESTClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
static Stream<Consumer<RESTClient>> clientMethodsProvider() {
return Stream.of(
client -> client.consume(new Request("CATEGORY", "TYPE", "SUB-TYPE")).join(),
client -> client.consume("CATEGORY", "TYPE", "SUB-TYPE").join()
);
}
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.GET)
.setPath("/CATEGORY/TYPE-SUB-TYPE") (1)
.build(),
true
);
}
@ParameterizedTest
@MethodSource("clientMethodsProvider")
@BeforeThisTest(method = "prepare")
void Should_return_message_Hello_World(final Consumer<RESTClient> clientMethod) {
assertDoesNotThrow(() -> clientMethod.accept(restClient));
}
}
1 | As a result of the HTTP request handler execution, the RxMicro framework generates the same URL: /CATEGORY/TYPE-SUB-TYPE |
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.2. Supported Data Types
The RxMicro framework supports the following Java types, which can be path variables
of the request model:
-
? extends Enum<?>
; -
java.lang.Boolean
; -
java.lang.Byte
; -
java.lang.Short
; -
java.lang.Integer
; -
java.lang.Long
; -
java.math.BigInteger
; -
java.lang.Float
; -
java.lang.Double
; -
java.math.BigDecimal
; -
java.lang.Character
; -
java.lang.String
; -
java.time.Instant
;
For floating point numbers, it is suggested to use the Using the |
7. Support of Internal Data Types
7.1. Basic Rules
The RxMicro framework works with internal data. Due to this, the developer can extract additional information about the HTTP response.
To work with internal data, it is necessary to create a response model:
public final class Response {
(1)
@ResponseStatusCode
Integer status;
(2)
HttpVersion version;
(3)
HttpHeaders headers;
(4)
@ResponseBody
byte[] body;
public Integer getStatus() {
return status;
}
public HttpVersion getVersion() {
return version;
}
public HttpHeaders getHeaders() {
return headers;
}
public byte[] getBody() {
return body;
}
}
1 | To get the HTTP status code, it is necessary to use the
@ResponseStatusCode annotation. |
2 | To get the HTTP protocol version, it is necessary to use the
HttpVersion type.(Currently only 1.0 and 1.1 HTTP versions are supported.) |
3 | To get all HTTP headers, it is necessary to use the
HttpHeaders type. |
4 | To get the response body content as a byte array, it is necessary to use the
@ResponseBody annotation. |
After creating model classes, it is necessary to create a REST client method that uses this model:
@RestClient
public interface RESTClient {
@GET("/")
CompletableFuture<Response> get();
}
After creating the REST client, let’s check that everything works:
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
private RESTClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
@Mock
private HttpHeaders headers;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/")
.build(),
new HttpResponseMock.Builder()
.setStatus(201)
.setVersion(HTTP_1_0)
.setHeaders(headers)
.setBody("<BODY>")
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_internals() {
final Response response = assertDoesNotThrow(() -> restClient.get().join());
assertEquals(201, response.getStatus());
assertEquals(HTTP_1_0, response.getVersion());
assertSame(headers, response.getHeaders());
assertArrayEquals("<BODY>".getBytes(UTF_8), response.getBody());
}
}
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 |
7.2. Supported Internal Data Types
Java type | RxMicro Annotation | Is the annotation required? | Description |
---|---|---|---|
None |
- |
HTTP protocol version |
|
None |
- |
HTTP header model |
|
Yes |
HTTP response status code |
||
|
Yes |
HTTP response body content |
Currently only |
8. Versioning of REST Clients
The RxMicro framework supports versioning of REST Clients using two strategies:
-
Versioning based on HTTP header analysis with the
Api-Version
name. -
Versioning based on URL Path fragment analysis.
8.1. Versioning Based on HTTP Header Analysis
The RxMicro framework allows creating identical REST clients that differ only in version:
@RestClient
@Version(value = "v1", strategy = Version.Strategy.HEADER) (1)
public interface OldRestClient {
@PATCH("/patch")
CompletableFuture<Void> update();
}
1 | REST client of the old v1 version, using the
Version.Strategy.HEADER strategy; |
@RestClient
@Version(value = "v2", strategy = Version.Strategy.HEADER) (1)
public interface NewRestClient {
@PATCH("/patch")
CompletableFuture<Void> update();
}
1 | REST client of the new v2 version, using the
Version.Strategy.HEADER strategy; |
The correctness of HTTP request generation for these REST clients can be checked with the following tests:
@InitMocks
@RxMicroComponentTest(OldRestClient.class)
final class OldRestClientTest {
private OldRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.PATCH)
.setPath("/patch")
.setHeaders(HttpHeaders.of(
API_VERSION, "v1"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_add_version_url_path() {
assertDoesNotThrow(() -> restClient.update().join());
}
}
@InitMocks
@RxMicroComponentTest(NewRestClient.class)
final class NewRestClientTest {
private NewRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.PATCH)
.setPath("/patch")
.setHeaders(HttpHeaders.of(
API_VERSION, "v2"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_add_version_url_path() {
assertDoesNotThrow(() -> restClient.update().join());
}
}
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.2. Versioning Based on URL Path Fragment Analysis
The RxMicro framework allows creating identical REST clients that differ only in version:
@RestClient
@Version(value = "v1", strategy = Version.Strategy.URL_PATH) (1)
public interface OldRestClient {
@PATCH("/patch")
CompletableFuture<Void> update();
}
1 | REST client of the old v1 version, using the
Version.Strategy.URL_PATH strategy; |
@RestClient
@Version(value = "v2", strategy = Version.Strategy.URL_PATH) (1)
public interface NewRestClient {
@PATCH("/patch")
CompletableFuture<Void> update();
}
1 | REST client of the new v2 version, using the
Version.Strategy.URL_PATH strategy; |
The correctness of HTTP request generation for these REST clients can be checked with the following tests:
@InitMocks
@RxMicroComponentTest(OldRestClient.class)
final class OldRestClientTest {
private OldRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.PATCH)
.setPath("/v1/patch")
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_add_version_url_path() {
assertDoesNotThrow(() -> restClient.update().join());
}
}
@InitMocks
@RxMicroComponentTest(NewRestClient.class)
final class NewRestClientTest {
private NewRestClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.PATCH)
.setPath("/v2/patch")
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_add_version_url_path() {
assertDoesNotThrow(() -> restClient.update().join());
}
}
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 |
9. Base URL Path for All Handlers
To configure a base URL Path for all methods sending an HTTP request, the RxMicro framework provides the
@BaseUrlPath
annotation:
@RestClient
@BaseUrlPath("base-url-path")
public interface RESTClient {
@GET("/path")
CompletableFuture<Void> path();
}
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
private RESTClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.GET)
.setPath("/base-url-path/path")
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_base_url() {
assertDoesNotThrow(() -> restClient.path().join());
}
}
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 |
10. Expressions
The RxMicro framework supports expressions for REST clients.
Expressions can be useful to send configuration parameters to the server.
To use expressions You need to create a configuration class:
public final class CustomRestClientConfig extends RestClientConfig {
private boolean useProxy = true;
private Mode mode = Mode.PRODUCTION;
public boolean isUseProxy() {
return useProxy;
}
public CustomRestClientConfig setUseProxy(final boolean useProxy) {
this.useProxy = useProxy;
return this;
}
public Mode getMode() {
return mode;
}
public CustomRestClientConfig setMode(final Mode mode) {
this.mode = mode;
return this;
}
@Override
public CustomRestClientConfig setSchema(final ProtocolSchema schema) {
return (CustomRestClientConfig) super.setSchema(schema);
}
@Override
public CustomRestClientConfig setHost(final String host) {
return (CustomRestClientConfig) super.setHost(host);
}
@Override
public CustomRestClientConfig setPort(final int port) {
return (CustomRestClientConfig) super.setPort(port);
}
public enum Mode {
PRODUCTION,
TEST
}
}
, which must meet the following requirements:
-
The class must be public.
-
The class must contain a public constructor without parameters.
-
The class must extend the
RestClientConfig
class. -
To set property values, the class must contain
setters
.
(Only those fields, that will containsetters
, can be initialized!)
To attach this configuration class to a REST client, You must specify it in the
@RestClient
annotation parameter:
@RestClient(
configClass = CustomRestClientConfig.class, (1)
configNameSpace = "custom" (2)
)
@AddHeader(name = "Use-Proxy", value = "${useProxy}") (3)
public interface RESTClient {
@PUT("/")
@AddHeader(name = "Debug", value = "Use-Proxy=${useProxy}, Mode=${mode}") (3)
@AddHeader(name = "Endpoint", value = "Schema=${schema}, Host=${host}, Port=${port}") (3)
CompletableFuture<Void> put();
}
1 | Attaching the configuration class to a REST client. |
2 | The name space specification for this configuration file.(For more information on name space , refer to the core.html section) |
3 | After attaching the configuration class, its properties can be used in expressions. |
So dont’t forget to add the following
|
The functionality of expressions can be demonstrated through the test:
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
@WithConfig("custom")
private static final CustomRestClientConfig config =
new CustomRestClientConfig()
.setHost("rxmicro.io")
.setPort(8443)
.setSchema(ProtocolSchema.HTTPS)
.setUseProxy(false)
.setMode(CustomRestClientConfig.Mode.TEST);
private RESTClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepare() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(HttpMethod.PUT)
.setPath("/")
.setHeaders(HttpHeaders.of(
"Use-Proxy", "false",
"Debug", "Use-Proxy=false, Mode=TEST",
"Endpoint", "Schema=HTTPS, Host=rxmicro.io, Port=8443"
))
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepare")
void Should_support_expressions() {
assertDoesNotThrow(() -> restClient.put().join());
}
}
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 |
11. Request ID
The Request Id feature described at Monitoring section.
12. Partial Implementation
If the REST client generated by the RxMicro Annotation Processor
contains errors, incorrect or non-optimized logic, the developer can use the Partial Implementation
feature.
This feature allows You to implement HTTP request handlers for the REST client on Your own, instead of generating them by the RxMicro framework.
To activate this feature, You need to use the
@PartialImplementation
annotation, and specify an abstract class that contains a partial implementation of HTTP request handler(s) for REST client:
@RestClient
(1)
@PartialImplementation(AbstractRESTClient.class)
public interface RESTClient {
@GET("/")
CompletableFuture<Void> generatedMethod();
CompletableFuture<Void> userDefinedMethod();
}
1 | Using the
@PartialImplementation
annotation, the AbstractRESTClient class is specified. |
An AbstractRESTClient
contains the following content:
abstract class AbstractRESTClient extends AbstractRestClient implements RESTClient {
@Override
public final CompletableFuture<Void> userDefinedMethod() {
return CompletableFuture.completedFuture(null);
}
}
An abstract class that contains a partial implementation must meet the following requirements:
-
The class must be an
abstract
one. -
The class must extend the
AbstractRestClient
one. -
The class must implement the REST client interface.
-
The class must contain an implementation of all methods that are not generated automatically.
In terms of infrastructure, the HTTP request handlers generated and defined by the developer for REST client do not differ:
@InitMocks
@RxMicroComponentTest(RESTClient.class)
final class RESTClientTest {
private RESTClient restClient;
@Mock(answer = RETURNS_DEEP_STUBS)
@Alternative
private HttpClientFactory httpClientFactory;
private void prepareGeneratedMethod() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setMethod(GET)
.setPath("/")
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareGeneratedMethod")
void Should_invoke_generated_method() {
assertDoesNotThrow(() -> restClient.generatedMethod().join());
}
private void prepareUserDefinedMethod() {
prepareHttpClientMock(
httpClientFactory,
new HttpRequestMock.Builder()
.setAnyRequest()
.build(),
true
);
}
@Test
@BeforeThisTest(method = "prepareUserDefinedMethod")
void Should_invoke_user_defined_method() {
assertDoesNotThrow(() -> restClient.userDefinedMethod().join());
final HttpClient httpClient = getPreparedHttpClientMock();
verify(httpClient, never()).sendAsync(anyString(), anyString(), any());
verify(httpClient, never()).sendAsync(anyString(), anyString(), any(), any());
}
}
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 |
13. Configuring the Code Generation Process
The RxMicro framework provides an option to configure the code generation process for REST client.
For this purpose, it is necessary to use the
@RestClientGeneratorConfig
annotation, that annotates the module-info.java
module descriptor:
import io.rxmicro.rest.client.RestClientGeneratorConfig;
import io.rxmicro.rest.model.ClientExchangeFormatModule;
import io.rxmicro.rest.model.GenerateOption;
@RestClientGeneratorConfig(
exchangeFormat = ClientExchangeFormatModule.AUTO_DETECT, (1)
generateRequestValidators = GenerateOption.DISABLED, (2)
generateResponseValidators = GenerateOption.DISABLED, (3)
requestValidationMode =
RestClientGeneratorConfig.RequestValidationMode.RETURN_ERROR_SIGNAL (4)
)
module rest.client.generator {
requires rxmicro.rest.client;
}
1 | The exchangeFormat parameter allows You to specify a format for message exchange with a server.(By default, it is used the message exchange format which added to the module-info.java descriptor.
If several modules supporting the message exchange format are added to the module-info.java descriptor, then using the exchangeFormat , You need to specify which module should be used for REST clients.) |
2 | The generateRequestValidators parameter allows enabling/disabling the option of generating HTTP request validators for all REST client methods in the project.(The DISABLED value means that validators won’t be generated by the RxMicro Annotation Processor .) |
3 | The generateResponseValidators parameter allows enabling/disabling the option of generating HTTP response validators for all REST client methods in the project.(The AUTO_DETECT value means that validators will be generated only if the developer adds the rxmicro.validation module to the module-info.java descriptor.) |
4 | The requestValidationMode parameter specifies how the HTTP request parameters should be checked. |
If the requestValidationMode
parameter is equal to the RETURN_ERROR_SIGNAL
, then error handling should be performed in reactive style:
restClient.put("not_valid_email")
.exceptionally(throwable -> {
// do something with ValidationException
return null;
});
If the requestValidationMode
parameter is equal to the THROW_EXCEPTION
, then it is necessary to catch the exception
try {
restClient.put("not_valid_email");
} catch (final ValidationException e) {
// do something with ValidationException
}
The RxMicro team strong recommends using the (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.) FYI: 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 Thus the RxMicro team recommends the following approach:
|