diff --git a/pom.xml b/pom.xml
index 324801b..c9854c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
dev.bannmann.restflow
restflow
- 0.10
+ 0.11
${project.groupId}:${project.artifactId}
Fluent API for Java 11 HTTP Client with JSON-B support
diff --git a/src/main/java/dev/bannmann/restflow/AbstractRequester.java b/src/main/java/dev/bannmann/restflow/AbstractRequester.java
index d97ef7e..826d531 100644
--- a/src/main/java/dev/bannmann/restflow/AbstractRequester.java
+++ b/src/main/java/dev/bannmann/restflow/AbstractRequester.java
@@ -93,7 +93,23 @@ private HttpResponse failOrPassThrough(HttpResponse response)
protected abstract void verifyNoErrors(HttpResponse response);
- protected abstract R extractValue(HttpResponse response);
+ private R extractValue(HttpResponse httpResponse)
+ {
+ try
+ {
+ return doExtractValue(httpResponse);
+ }
+ catch (RuntimeException e)
+ {
+ String message = String.format("Could not process response to %s %s:\n%s",
+ request.method(),
+ request.uri(),
+ httpResponse.body());
+ throw new ResponseBodyException(message, e, httpResponse, diagnosticsData, callerFrames);
+ }
+ }
+
+ protected abstract R doExtractValue(HttpResponse response);
protected boolean isFailure(int responseStatus)
{
@@ -105,7 +121,7 @@ protected boolean isSuccess(int responseStatus)
return responseStatus >= 200 && responseStatus < 300;
}
- protected RequestStatusException createException(HttpResponse response)
+ protected ResponseStatusException createException(HttpResponse response)
{
int status = response.statusCode();
String body = getStringBody(response);
@@ -115,12 +131,9 @@ protected RequestStatusException createException(HttpResponse response)
request.method(),
request.uri());
- return RequestStatusException.builder()
+ return ResponseStatusException.builder()
.message(message)
- .request(request)
.response(response)
- .status(status)
- .body(body)
.diagnosticsData(diagnosticsData)
.build();
}
diff --git a/src/main/java/dev/bannmann/restflow/InvalidResponseException.java b/src/main/java/dev/bannmann/restflow/InvalidResponseException.java
new file mode 100644
index 0000000..3c19ff8
--- /dev/null
+++ b/src/main/java/dev/bannmann/restflow/InvalidResponseException.java
@@ -0,0 +1,49 @@
+package dev.bannmann.restflow;
+
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Getter;
+
+/**
+ * Thrown when the response is invalid. Refer to subclasses for specific cases.
+ */
+@Getter
+public abstract class InvalidResponseException extends RequestException
+{
+ private final transient HttpResponse> response;
+
+ protected InvalidResponseException(
+ String message,
+ Throwable cause,
+ HttpResponse> response,
+ Map diagnosticsData,
+ List callerFrames)
+ {
+ super(message, cause, diagnosticsData, callerFrames);
+ this.response = response;
+ }
+
+ @Override
+ public HttpRequest getRequest()
+ {
+ return response.request();
+ }
+
+ public int getStatusCode()
+ {
+ return response.statusCode();
+ }
+
+ public String getRawBody()
+ {
+ Object body = response.body();
+ if (body == null)
+ {
+ return null;
+ }
+ return body.toString();
+ }
+}
diff --git a/src/main/java/dev/bannmann/restflow/OptionalRequester.java b/src/main/java/dev/bannmann/restflow/OptionalRequester.java
index a4b3087..3dcf59b 100644
--- a/src/main/java/dev/bannmann/restflow/OptionalRequester.java
+++ b/src/main/java/dev/bannmann/restflow/OptionalRequester.java
@@ -38,7 +38,7 @@ protected void verifyNoErrors(HttpResponse response)
}
@Override
- protected Optional extractValue(HttpResponse response)
+ protected Optional doExtractValue(HttpResponse response)
{
if (response.statusCode() == HttpStatus.NOT_FOUND)
{
diff --git a/src/main/java/dev/bannmann/restflow/RegularRequester.java b/src/main/java/dev/bannmann/restflow/RegularRequester.java
index 587fbf9..9dd6954 100644
--- a/src/main/java/dev/bannmann/restflow/RegularRequester.java
+++ b/src/main/java/dev/bannmann/restflow/RegularRequester.java
@@ -35,7 +35,7 @@ protected void verifyNoErrors(HttpResponse response)
}
@Override
- protected R extractValue(HttpResponse response)
+ protected R doExtractValue(HttpResponse response)
{
return spec.getResponseBodyConfig()
.getResponseConverter()
diff --git a/src/main/java/dev/bannmann/restflow/RequestException.java b/src/main/java/dev/bannmann/restflow/RequestException.java
index 5c9f020..8482069 100644
--- a/src/main/java/dev/bannmann/restflow/RequestException.java
+++ b/src/main/java/dev/bannmann/restflow/RequestException.java
@@ -7,26 +7,24 @@
import lombok.Getter;
/**
- * Thrown when the request could not be completed for some reason. Refer to {@link RequestStatusException} and
- * {@link RequestFailureException} for specific cases.
+ * Thrown when the request could not be completed for some reason. Refer to subclasses for specific cases.
*/
@Getter
public abstract class RequestException extends RuntimeException
{
- private final transient HttpRequest request;
private final transient Map diagnosticsData;
private final transient List callerFrames;
protected RequestException(
- HttpRequest request,
String message,
Throwable cause,
Map diagnosticsData,
List callerFrames)
{
super(message, cause);
- this.request = request;
this.diagnosticsData = diagnosticsData;
this.callerFrames = callerFrames;
}
+
+ public abstract HttpRequest getRequest();
}
diff --git a/src/main/java/dev/bannmann/restflow/RequestFailureException.java b/src/main/java/dev/bannmann/restflow/RequestFailureException.java
index c1863b2..90ba0d3 100644
--- a/src/main/java/dev/bannmann/restflow/RequestFailureException.java
+++ b/src/main/java/dev/bannmann/restflow/RequestFailureException.java
@@ -4,13 +4,16 @@
import java.util.List;
import java.util.Map;
+import lombok.Getter;
+
/**
* Thrown when the request failed without a response.
- *
- * @see RequestStatusException
*/
+@Getter
public class RequestFailureException extends RequestException
{
+ private final HttpRequest request;
+
public RequestFailureException(
HttpRequest request,
String message,
@@ -18,6 +21,7 @@ public RequestFailureException(
Map diagnosticsData,
List callerFrames)
{
- super(request, message, cause, diagnosticsData, callerFrames);
+ super(message, cause, diagnosticsData, callerFrames);
+ this.request = request;
}
}
diff --git a/src/main/java/dev/bannmann/restflow/RequestStatusException.java b/src/main/java/dev/bannmann/restflow/RequestStatusException.java
deleted file mode 100644
index b2cfad8..0000000
--- a/src/main/java/dev/bannmann/restflow/RequestStatusException.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package dev.bannmann.restflow;
-
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.util.List;
-import java.util.Map;
-
-import lombok.Builder;
-import lombok.Getter;
-
-/**
- * Thrown when the response to a request has a non-2xx status code.
- *
- * @see RequestFailureException
- */
-@Getter
-public class RequestStatusException extends RequestException
-{
- private final int status;
- private final transient HttpResponse> response;
- private final String body;
-
- @Builder
- public RequestStatusException(
- String message,
- Throwable cause,
- HttpRequest request,
- HttpResponse> response,
- int status,
- String body,
- Map diagnosticsData,
- List callerFrames)
- {
- super(request, message, cause, diagnosticsData, callerFrames);
-
- this.response = response;
- this.status = status;
- this.body = body;
- }
-}
diff --git a/src/main/java/dev/bannmann/restflow/ResponseBodyException.java b/src/main/java/dev/bannmann/restflow/ResponseBodyException.java
new file mode 100644
index 0000000..332a139
--- /dev/null
+++ b/src/main/java/dev/bannmann/restflow/ResponseBodyException.java
@@ -0,0 +1,24 @@
+package dev.bannmann.restflow;
+
+import java.net.http.HttpResponse;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Builder;
+
+/**
+ * Thrown when the response body could not be processed.
+ */
+public class ResponseBodyException extends InvalidResponseException
+{
+ @Builder
+ public ResponseBodyException(
+ String message,
+ Throwable cause,
+ HttpResponse> response,
+ Map diagnosticsData,
+ List callerFrames)
+ {
+ super(message, cause, response, diagnosticsData, callerFrames);
+ }
+}
diff --git a/src/main/java/dev/bannmann/restflow/ResponseStatusException.java b/src/main/java/dev/bannmann/restflow/ResponseStatusException.java
new file mode 100644
index 0000000..3f89679
--- /dev/null
+++ b/src/main/java/dev/bannmann/restflow/ResponseStatusException.java
@@ -0,0 +1,24 @@
+package dev.bannmann.restflow;
+
+import java.net.http.HttpResponse;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Builder;
+
+/**
+ * Thrown when the response to a request has a non-2xx status code.
+ */
+public class ResponseStatusException extends InvalidResponseException
+{
+ @Builder
+ public ResponseStatusException(
+ String message,
+ Throwable cause,
+ HttpResponse> response,
+ Map diagnosticsData,
+ List callerFrames)
+ {
+ super(message, cause, response, diagnosticsData, callerFrames);
+ }
+}
diff --git a/src/test/java/dev/bannmann/restflow/TestBasicRestClient.java b/src/test/java/dev/bannmann/restflow/TestBasicRestClient.java
index 6aa1ae1..df07d2b 100644
--- a/src/test/java/dev/bannmann/restflow/TestBasicRestClient.java
+++ b/src/test/java/dev/bannmann/restflow/TestBasicRestClient.java
@@ -21,7 +21,9 @@
import java.util.function.Function;
import javax.json.Json;
+import javax.json.JsonObject;
import javax.json.bind.JsonbBuilder;
+import javax.json.stream.JsonParsingException;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -185,6 +187,22 @@ public void testTryFetchServerError()
assertThrowsInternalServerError(responseFuture, "POST");
}
+ @Test(timeOut = METHOD_TIMEOUT)
+ public void testExecuteWithMalformedJsonResponse()
+ {
+ mockedServer.when(TestData.Requests.Incoming.POST)
+ .respond(TestData.Responses.HTML_ERROR_PAGE_WITH_SUCCESS_STATUS);
+
+ CompletableFuture> responseFuture = makeClient().make(TestData.Requests.Outgoing.POST)
+ .returningJsonObject()
+ .tryFetch();
+
+ assertThatThrownBy(responseFuture::get).isExactlyInstanceOf(ExecutionException.class)
+ .extracting(Throwable::getCause, as(InstanceOfAssertFactories.THROWABLE))
+ .isExactlyInstanceOf(ResponseBodyException.class)
+ .hasRootCauseExactlyInstanceOf(JsonParsingException.class);
+ }
+
private void assertThrowsInternalServerError(CompletableFuture> responseFuture, String method)
{
assertThrowsRequestStatusException(responseFuture,
@@ -197,13 +215,13 @@ private void assertThrowsInternalServerError(CompletableFuture> responseFuture
private void assertThrowsRequestStatusException(
CompletableFuture> responseFuture, int status, String path, String body, String method)
{
- String message = String.format("Got status %d with message '%s' for %s %s%s",
+ var message = String.format("Got status %d with message '%s' for %s %s%s",
status,
body,
method,
TestData.BASE_URL,
path);
- RequestStatusException expectedCause = RequestStatusException.builder()
+ var expectedCause = ResponseStatusException.builder()
.message(message)
.build();
diff --git a/src/test/java/dev/bannmann/restflow/TestData.java b/src/test/java/dev/bannmann/restflow/TestData.java
index f641fa7..8633368 100644
--- a/src/test/java/dev/bannmann/restflow/TestData.java
+++ b/src/test/java/dev/bannmann/restflow/TestData.java
@@ -72,6 +72,10 @@ public class Body
public final String
INTERNAL_SERVER_ERROR_BODY
= "Detected a slight field variance in the thera-magnetic caesium portal housing.";
+
+ public final String
+ HTML_UNEXPECTED_ERROR
+ = "Sorry, an unexpected error occurred. Please try again later.";
}
public final org.mockserver.model.HttpResponse NO_CONTENT = response().withStatusCode(204);
@@ -85,6 +89,10 @@ public class Body
public final org.mockserver.model.HttpResponse HELLO_WORLD_ARRAY = response().withStatusCode(200)
.withBody(Body.HELLO_WORLD_JSON_ARRAY);
+ public final org.mockserver.model.HttpResponse HTML_ERROR_PAGE_WITH_SUCCESS_STATUS = response().withStatusCode(
+ 200)
+ .withBody(Body.HTML_UNEXPECTED_ERROR);
+
public final org.mockserver.model.HttpResponse SERVER_BUSY = response().withStatusCode(429)
.withBody("Please try again later")
.withDelay(TimeUnit.MILLISECONDS, 500);