Skip to content

Commit 766753a

Browse files
committed
Add async().asVoid() option
1 parent d72a0ba commit 766753a

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed

client/src/main/java/io/avaje/http/client/DHttpAsync.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ public CompletableFuture<HttpResponse<Void>> asDiscarding() {
2525
return withHandler(HttpResponse.BodyHandlers.discarding());
2626
}
2727

28+
@Override
29+
public CompletableFuture<HttpResponse<Void>> asVoid() {
30+
return request
31+
.performSendAsync(true, HttpResponse.BodyHandlers.ofByteArray())
32+
.thenApply(request::asyncVoid);
33+
}
34+
2835
@Override
2936
public CompletableFuture<HttpResponse<String>> asString() {
3037
return request

client/src/main/java/io/avaje/http/client/DHttpClientRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ protected <T> CompletableFuture<HttpResponse<T>> performSendAsync(boolean loggab
404404
return context.sendAsync(httpRequest, responseHandler);
405405
}
406406

407+
protected HttpResponse<Void> asyncVoid(HttpResponse<byte[]> response) {
408+
afterAsyncEncoded(response);
409+
return new HttpVoidResponse(response);
410+
}
411+
407412
protected <E> E asyncBean(Class<E> type, HttpResponse<byte[]> response) {
408413
afterAsyncEncoded(response);
409414
return context.readBean(type, encodedResponseBody);

client/src/main/java/io/avaje/http/client/HttpAsyncResponse.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,57 @@
1010
*/
1111
public interface HttpAsyncResponse {
1212

13+
/**
14+
* Process the response with check for 200 range status code
15+
* returning as {@literal HttpResponse<Void>}.
16+
* <p>
17+
* Unlike {@link #asDiscarding()} this request will read any response
18+
* content as bytes with the view that the response content can be
19+
* an error message that could be read via for example
20+
* {@link HttpException#bean(Class)}.
21+
* <p>
22+
* Will throw an HttpException if the status code is in the
23+
* error range allowing the caller to access the error message
24+
* body via for example {@link HttpException#bean(Class)}
25+
* <p>
26+
* This is intended to be used for POST, PUT, DELETE requests
27+
* where the caller is only interested in the response body
28+
* when an error occurs (status code not in 200 range).
29+
*
30+
* <pre>{@code
31+
*
32+
* clientContext.request()
33+
* .path("hello/world")
34+
* .GET()
35+
* .async().asVoid()
36+
* .whenComplete((hres, throwable) -> {
37+
*
38+
* if (throwable != null) {
39+
*
40+
* // if throwable.getCause() is a HttpException for status code >= 300
41+
* HttpException httpException = (HttpException) throwable.getCause();
42+
* int status = httpException.getStatusCode();
43+
*
44+
* // convert json error response body to a bean
45+
* ErrorResponse errorResponse = httpException.bean(ErrorResponse.class);
46+
* ...
47+
* } else {
48+
* int statusCode = hres.statusCode();
49+
* ...
50+
* }
51+
* });
52+
*
53+
* }</pre>
54+
*/
55+
CompletableFuture<HttpResponse<Void>> asVoid();
56+
1357
/**
1458
* Process discarding response body as {@literal HttpResponse<Void>}.
59+
* <p>
60+
* Unlike {@link #asVoid()} this will discard any response body including
61+
* any error response body. We should instead use {@link #asVoid()} if we
62+
* might get an error response body that we want to read via
63+
* for example {@link HttpException#bean(Class)}.
1564
*
1665
* <pre>{@code
1766
*

client/src/test/java/io/avaje/http/client/HelloControllerTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,40 @@ void postForm_asVoid_invokesValidation_expect_badRequest_extractError() {
579579
}
580580
}
581581

582+
@Test
583+
void asyncAsVoid_extractError() throws InterruptedException {
584+
AtomicReference<HttpException> ref = new AtomicReference<>();
585+
586+
final CompletableFuture<HttpResponse<Void>> future =
587+
clientContext.request()
588+
.path("hello/saveform")
589+
.formParam("email", "user@foo.com")
590+
.formParam("url", "notAValidUrl")
591+
.POST()
592+
.async()
593+
.asVoid()
594+
.whenComplete((hres, throwable) -> {
595+
596+
final HttpException cause = (HttpException) throwable.getCause();
597+
ref.set(cause);
598+
599+
final HttpResponse<?> httpResponse = cause.getHttpResponse();
600+
assertNotNull(httpResponse);
601+
assertEquals(422, httpResponse.statusCode());
602+
603+
final ErrorResponse errorResponse = cause.bean(ErrorResponse.class);
604+
final Map<String, String> errorMap = errorResponse.getErrors();
605+
assertThat(errorMap.get("url")).isEqualTo("must be a valid URL");
606+
assertThat(errorMap.get("name")).isEqualTo("must not be null");
607+
});
608+
609+
try {
610+
future.get();
611+
} catch (ExecutionException e) {
612+
assertThat(ref.get()).isNotNull();
613+
}
614+
}
615+
582616
@Test
583617
void postForm_asBytes_validation_expect_badRequest_extractError() {
584618
try {

0 commit comments

Comments
 (0)