Skip to content

Commit 1602a02

Browse files
committed
#24 - ENH: Add asPlainString() with 2XX range check (for Client API returning String without HttpResponse)
1 parent a13f8f4 commit 1602a02

File tree

5 files changed

+99
-8
lines changed

5 files changed

+99
-8
lines changed

http-client/client/src/main/java/io/avaje/http/client/DHttpClientContext.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.net.http.HttpHeaders;
77
import java.net.http.HttpRequest;
88
import java.net.http.HttpResponse;
9+
import java.nio.charset.StandardCharsets;
910
import java.time.Duration;
1011
import java.util.List;
1112
import java.util.Map;
@@ -106,18 +107,32 @@ void checkMaybeThrow(HttpResponse<byte[]> response) {
106107
}
107108
}
108109

110+
@SuppressWarnings("unchecked")
111+
public BodyContent readErrorContent(boolean responseAsBytes, HttpResponse<?> httpResponse) {
112+
if (responseAsBytes) {
113+
return readContent((HttpResponse<byte[]>) httpResponse);
114+
}
115+
final String contentType = getContentType(httpResponse);
116+
final Object body = httpResponse.body();
117+
if (body instanceof String) {
118+
return new BodyContent(contentType, ((String) body).getBytes(StandardCharsets.UTF_8));
119+
}
120+
String type = (body == null) ? "null" : body.getClass().toString();
121+
throw new IllegalStateException("Unable to translate response body to bytes? Maybe use HttpResponse directly instead? Response body type: " + type);
122+
}
123+
109124
@Override
110125
public BodyContent readContent(HttpResponse<byte[]> httpResponse) {
111126
byte[] bodyBytes = decodeContent(httpResponse);
112127
final String contentType = getContentType(httpResponse);
113128
return new BodyContent(contentType, bodyBytes);
114129
}
115130

116-
String getContentType(HttpResponse<byte[]> httpResponse) {
131+
String getContentType(HttpResponse<?> httpResponse) {
117132
return firstHeader(httpResponse.headers(), "Content-Type", "content-type");
118133
}
119134

120-
String getContentEncoding(HttpResponse<byte[]> httpResponse) {
135+
String getContentEncoding(HttpResponse<?> httpResponse) {
121136
return firstHeader(httpResponse.headers(), "Content-Encoding", "content-encoding");
122137
}
123138

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,14 @@ public HttpResponse<String> asString() {
485485
return withHandler(HttpResponse.BodyHandlers.ofString());
486486
}
487487

488+
@Override
489+
public HttpResponse<String> asPlainString() {
490+
loggableResponseBody = true;
491+
final HttpResponse<String> hres = withHandler(HttpResponse.BodyHandlers.ofString());
492+
context.checkResponse(hres);
493+
return hres;
494+
}
495+
488496
@Override
489497
public HttpResponse<Void> asDiscarding() {
490498
return withHandler(discarding());

http-client/client/src/main/java/io/avaje/http/client/HttpClientResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ public interface HttpClientResponse {
113113
*/
114114
HttpResponse<String> asString();
115115

116+
/**
117+
* Return the content as string with check for 200 range status code.
118+
* <p>
119+
* If the status code is in the error range then a {@link HttpException}
120+
* is thrown.
121+
*/
122+
HttpResponse<String> asPlainString();
123+
116124
/**
117125
* Return the content as InputStream.
118126
*/

http-client/client/src/main/java/io/avaje/http/client/HttpException.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
public class HttpException extends RuntimeException {
4444

4545
private final int statusCode;
46+
private final boolean responseAsBytes;
4647
private DHttpClientContext context;
4748
private HttpResponse<?> httpResponse;
4849

@@ -52,6 +53,7 @@ public class HttpException extends RuntimeException {
5253
public HttpException(int statusCode, String message) {
5354
super(message);
5455
this.statusCode = statusCode;
56+
this.responseAsBytes = false;
5557
}
5658

5759
/**
@@ -60,6 +62,7 @@ public HttpException(int statusCode, String message) {
6062
public HttpException(int statusCode, String message, Throwable cause) {
6163
super(message, cause);
6264
this.statusCode = statusCode;
65+
this.responseAsBytes = false;
6366
}
6467

6568
/**
@@ -68,20 +71,23 @@ public HttpException(int statusCode, String message, Throwable cause) {
6871
public HttpException(int statusCode, Throwable cause) {
6972
super(cause);
7073
this.statusCode = statusCode;
74+
this.responseAsBytes = false;
7175
}
7276

7377
HttpException(HttpResponse<?> httpResponse, DHttpClientContext context) {
7478
super();
7579
this.httpResponse = httpResponse;
7680
this.statusCode = httpResponse.statusCode();
7781
this.context = context;
82+
this.responseAsBytes = false;
7883
}
7984

8085
HttpException(DHttpClientContext context, HttpResponse<byte[]> httpResponse) {
8186
super();
8287
this.httpResponse = httpResponse;
8388
this.statusCode = httpResponse.statusCode();
8489
this.context = context;
90+
this.responseAsBytes = true;
8591
}
8692

8793
/**
@@ -90,27 +96,24 @@ public HttpException(int statusCode, Throwable cause) {
9096
* @param cls The type of bean to convert the response to
9197
* @return The response as a bean
9298
*/
93-
@SuppressWarnings("unchecked")
9499
public <T> T bean(Class<T> cls) {
95-
final BodyContent body = context.readContent((HttpResponse<byte[]>) httpResponse);
100+
final BodyContent body = context.readErrorContent(responseAsBytes, httpResponse);
96101
return context.readBean(cls, body);
97102
}
98103

99104
/**
100105
* Return the response body content as a UTF8 string.
101106
*/
102-
@SuppressWarnings("unchecked")
103107
public String bodyAsString() {
104-
final BodyContent body = context.readContent((HttpResponse<byte[]>) httpResponse);
108+
final BodyContent body = context.readErrorContent(responseAsBytes, httpResponse);
105109
return new String(body.content(), StandardCharsets.UTF_8);
106110
}
107111

108112
/**
109113
* Return the response body content as raw bytes.
110114
*/
111-
@SuppressWarnings("unchecked")
112115
public byte[] bodyAsBytes() {
113-
final BodyContent body = context.readContent((HttpResponse<byte[]>) httpResponse);
116+
final BodyContent body = context.readErrorContent(responseAsBytes, httpResponse);
114117
return body.content();
115118
}
116119

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,63 @@ void get_notFound() {
375375
assertThat(hres.body()).contains("Not found");
376376
}
377377

378+
@Test
379+
void asString_200() {
380+
final HttpResponse<String> hres = clientContext.request()
381+
.path("hello").path("message")
382+
.GET().asString();
383+
384+
assertThat(hres.body()).contains("hello world");
385+
assertThat(hres.statusCode()).isEqualTo(200);
386+
}
387+
388+
@Test
389+
void asPlainString_200() {
390+
final HttpResponse<String> hres = clientContext.request()
391+
.path("hello").path("message")
392+
.GET().asPlainString();
393+
394+
assertThat(hres.body()).contains("hello world");
395+
assertThat(hres.statusCode()).isEqualTo(200);
396+
}
397+
398+
@Test
399+
void asPlainString_throwingHttpException() {
400+
final HttpException httpException = assertThrows(HttpException.class, () ->
401+
clientContext.request()
402+
.path("hello/saveform3")
403+
.formParam("name", "Bax")
404+
.formParam("email", "notValidEmail")
405+
.POST()
406+
.asPlainString());
407+
408+
assertThat(httpException.statusCode()).isEqualTo(422);
409+
410+
// convert json error response body to a bean
411+
final ErrorResponse errorResponse = httpException.bean(ErrorResponse.class);
412+
final Map<String, String> errorMap = errorResponse.getErrors();
413+
assertThat(errorMap.get("email")).isEqualTo("must be a well-formed email address");
414+
}
415+
416+
@Test
417+
void asString_readInvalidResponse() {
418+
final HttpResponse<String> hres = clientContext.request()
419+
.path("hello/saveform3")
420+
.formParam("name", "Bax")
421+
.formParam("email", "notValidEmail")
422+
.POST()
423+
.asString();
424+
425+
assertThat(hres.statusCode()).isEqualTo(422);
426+
427+
// convert json error response body to a bean
428+
final ErrorResponse errorResponse = clientContext.converters()
429+
.beanReader(ErrorResponse.class).readBody(hres.body());
430+
431+
final Map<String, String> errorMap = errorResponse.getErrors();
432+
assertThat(errorMap.get("email")).isEqualTo("must be a well-formed email address");
433+
}
434+
378435
@Test
379436
void headers() {
380437
final HttpClientRequest request = clientContext.request();

0 commit comments

Comments
 (0)