Skip to content

Commit

Permalink
Merge pull request #66 from sashirestela/65-add-inspector-for-body-re…
Browse files Browse the repository at this point in the history
…quest-parameter

Add inspector for Body request parameter
  • Loading branch information
sashirestela authored Mar 27, 2024
2 parents 94b2506 + 7c82c02 commit 3a55d1f
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 19 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,16 @@ Take in account that you need to use **Java 11 or greater**.

We have the following attributes to create a CleverClient object:

| Attribute | Description | Required |
| -------------------|--------------------------------------------------|-----------|
| baseUrl | Api's url | mandatory |
| headers | Map of headers (name/value) | optional |
| header | Single header as a name and a value | optional |
| httpClient | Java HttpClient object | optional |
| requestInterceptor | Function to modify the request once is built | optional |
| endsOfStream | List of texts used to mark the end of streams | optional |
| endOfStream | Text used to mark the end of streams | optional |
| Attribute | Description | Required |
| -------------------|---------------------------------------------------|-----------|
| baseUrl | Api's url | mandatory |
| headers | Map of headers (name/value) | optional |
| header | Single header as a name and a value | optional |
| httpClient | Java HttpClient object | optional |
| requestInterceptor | Function to modify the request once is built | optional |
| bodyInspector | Function to inspect the `@Body` request parameter | optional |
| endsOfStream | List of texts used to mark the end of streams | optional |
| endOfStream | Text used to mark the end of streams | optional |

The attribute ```end(s)OfStream``` is required when you have endpoints sending back streams of data (Server Sent Events - SSE).

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.sashirestela</groupId>
<artifactId>cleverclient</artifactId>
<version>1.3.3</version>
<version>1.4.0</version>
<packaging>jar</packaging>

<name>cleverclient</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

/**
Expand All @@ -31,6 +32,7 @@ public class CleverClient {
private final Map<String, String> headers;
private final HttpClient httpClient;
private final UnaryOperator<HttpRequestData> requestInterceptor;
private final Consumer<Object> bodyInspector;
private final HttpProcessor httpProcessor;

/**
Expand All @@ -41,21 +43,25 @@ public class CleverClient {
* @param httpClient Custom Java's HttpClient component. One is created by default if none
* is passed. Optional.
* @param requestInterceptor Function to modify the request once it has been built.
* @param bodyInspector Function to inspect the Body request parameter.
* @param endsOfStream Texts used to mark the final of streams when handling server sent
* events (SSE). Optional.
*/
@Builder
public CleverClient(@NonNull String baseUrl, @Singular Map<String, String> headers, HttpClient httpClient,
UnaryOperator<HttpRequestData> requestInterceptor, @Singular("endOfStream") List<String> endsOfStream) {
UnaryOperator<HttpRequestData> requestInterceptor, Consumer<Object> bodyInspector,
@Singular("endOfStream") List<String> endsOfStream) {
this.baseUrl = baseUrl;
this.headers = Optional.ofNullable(headers).orElse(Map.of());
this.httpClient = Optional.ofNullable(httpClient).orElse(HttpClient.newHttpClient());
this.requestInterceptor = requestInterceptor;
this.bodyInspector = bodyInspector;
this.httpProcessor = HttpProcessor.builder()
.baseUrl(this.baseUrl)
.headers(CommonUtil.mapToListOfString(this.headers))
.httpClient(this.httpClient)
.requestInterceptor(this.requestInterceptor)
.bodyInspector(bodyInspector)
.build();
Configurator.builder()
.endsOfStream(Optional.ofNullable(endsOfStream).orElse(Arrays.asList()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.net.http.HttpClient;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

/**
Expand All @@ -30,6 +31,7 @@ public class HttpProcessor implements InvocationHandler {
private final List<String> headers;
private final HttpClient httpClient;
private final UnaryOperator<HttpRequestData> requestInterceptor;
private final Consumer<Object> bodyInspector;

/**
* Creates a generic dynamic proxy with this HttpProcessor object acting as an InvocationHandler to
Expand Down Expand Up @@ -99,8 +101,9 @@ private Object resolve(Method method, Object[] arguments) {
var url = baseUrl + URLBuilder.one().build(urlMethod, methodMetadata, arguments);
var httpMethod = methodMetadata.getHttpAnnotationName();
var returnType = methodMetadata.getReturnType();
var bodyObject = calculateBodyObject(methodMetadata, arguments);
var contentType = methodMetadata.getContentType();
var body = getAndInspectBody(methodMetadata, arguments);
var bodyObject = getBodyObject(body, contentType);
var fullHeaders = new ArrayList<>(this.headers);
fullHeaders.addAll(calculateHeaderContentType(contentType));
fullHeaders.addAll(interfaceMetadata.getFullHeadersByMethod(methodMetadata));
Expand All @@ -117,14 +120,24 @@ private Object resolve(Method method, Object[] arguments) {
return httpConnector.sendRequest();
}

private Object calculateBodyObject(MethodMetadata methodMetadata, Object[] arguments) {
private Object getAndInspectBody(MethodMetadata methodMetadata, Object[] arguments) {
var bodyIndex = methodMetadata.getBodyIndex();
var bodyObject = bodyIndex >= 0 ? arguments[bodyIndex] : null;
if (bodyObject != null) {
if (methodMetadata.getContentType() == ContentType.MULTIPART_FORMDATA) {
bodyObject = JsonUtil.objectToMap(bodyObject);
} else if (methodMetadata.getContentType() == ContentType.APPLICATION_JSON) {
bodyObject = JsonUtil.objectToJson(bodyObject);
var body = bodyIndex >= 0 ? arguments[bodyIndex] : null;

if (body != null && bodyInspector != null) {
bodyInspector.accept(body);
}

return body;
}

private Object getBodyObject(Object body, ContentType contentType) {
Object bodyObject = null;
if (body != null) {
if (contentType == ContentType.MULTIPART_FORMDATA) {
bodyObject = JsonUtil.objectToMap(body);
} else if (contentType == ContentType.APPLICATION_JSON) {
bodyObject = JsonUtil.objectToJson(body);
}
}
return bodyObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand All @@ -41,6 +43,7 @@ void shouldSetPropertiesToDefaultValuesWhenBuilderIsCalledWithoutThoseProperties
assertNotNull(cleverClient.getBaseUrl());
assertNotNull(cleverClient.getHttpProcessor());
assertNull(cleverClient.getRequestInterceptor());
assertNull(cleverClient.getBodyInspector());
}

@Test
Expand Down Expand Up @@ -122,6 +125,44 @@ void shouldModifyRequestWhenPassingInterceptorFunction() {
assertEquals(expectedBody, actualBody);
}

@SuppressWarnings("unchecked")
@Test
void shouldNotThrownExceptionWhenBodyInspectorEndsSuccessfully() {
var httpClient = mock(HttpClient.class);
Consumer<Object> bodyInspector = body -> {
var sample = (Sample) body;
if (sample.getModel() == null) {
throw new IllegalArgumentException("The parameter model must not be null.");
}
};
var cleverClient = CleverClient.builder()
.baseUrl("https://test")
.bodyInspector(bodyInspector)
.httpClient(httpClient)
.build();
when(httpClient.sendAsync(any(), any()))
.thenReturn(CompletableFuture.completedFuture(mock(HttpResponse.class)));
var testService = cleverClient.create(TestCleverClient.class);
assertDoesNotThrow(() -> testService.getText(Sample.builder().model("abc").build(), "math"));
}

@Test
void shouldThrownExceptionWhenBodyInspectorFails() {
Consumer<Object> bodyInspector = body -> {
var sample = (Sample) body;
if (sample.getModel() == null) {
throw new IllegalArgumentException("The parameter model must not be null.");
}
};
var cleverClient = CleverClient.builder()
.baseUrl("https://test")
.bodyInspector(bodyInspector)
.build();
var testService = cleverClient.create(TestCleverClient.class);
var sample = Sample.builder().id("1").build();
assertThrows(IllegalArgumentException.class, () -> testService.getText(sample, "math"));
}

@Value
@Builder
static class Sample {
Expand Down

0 comments on commit 3a55d1f

Please sign in to comment.