Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public static String inOneLine(Map<String, List<String>> headers) {
* @param statusCode status code of the http response.
* @param body The error response body as a String.
* @param contentType The content type of the error response.
* @return An instance of WatsonxError parsed from the body.
* @return An instance of WatsonxError parsed from the body, or an unclassified error carrying the status code when the content type is neither
* JSON nor XML.
*/
public static WatsonxError parseErrorBody(int statusCode, String body, String contentType) {
if (isNull(contentType))
Expand Down Expand Up @@ -160,7 +161,7 @@ public static WatsonxError parseErrorBody(int statusCode, String body, String co
if (contentType.contains("application/xml"))
return parseXmlError(body);

throw new RuntimeException(body);
return new WatsonxError(statusCode, "", List.of(new Error(Code.UNCLASSIFIED.value(), body, null)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,23 @@ void should_parse_model_gateway_error_body_correctly_from_json() {
}

@Test
void should_throw_runtime_exception_when_parsing_error_body_with_unsupported_content_type() {
assertThrows(RuntimeException.class, () -> {
HttpUtils.parseErrorBody(500, "test body", "text/plain");
});
void should_parse_error_body_as_unclassified_when_content_type_is_not_supported() {
WatsonxError result = HttpUtils.parseErrorBody(500, "test body", "text/plain");
assertEquals(500, result.statusCode());
assertEquals(1, result.errors().size());
assertEquals(WatsonxError.Code.UNCLASSIFIED.value(), result.errors().get(0).code());
assertEquals("test body", result.errors().get(0).message());
assertEquals(null, result.errors().get(0).moreInfo());
}

@Test
void should_keep_status_code_when_gateway_returns_plain_text_error() {
WatsonxError result = HttpUtils.parseErrorBody(502, "error code: 502", "text/plain");
assertEquals(502, result.statusCode());
assertEquals("error code: 502", result.errors().get(0).message());

var ex = HttpUtils.mapWatsonxException(new WatsonxException("error code: 502", 502, result));
assertEquals(502, ex.statusCode());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,39 @@ public <T> HttpResponse<T> intercept(HttpRequest request, BodyHandler<T> bodyHan
client.send(httpRequest, bodyHandler);
verify(httpClient, times(4)).send(any(), eq(bodyHandler));
}

@Test
@SuppressWarnings("unchecked")
void should_retry_when_plain_text_response_has_retryable_status_code() throws Exception {

SyncHttpClient client = SyncHttpClient.builder()
.httpClient(httpClient)
.interceptor(RetryInterceptor.ON_RETRYABLE_STATUS_CODES)
.interceptor(mockInterceptor)
.build();

when(mockInterceptor.intercept(any(), eq(bodyHandler), anyInt(), any()))
.thenAnswer(CHAIN_MOCK);

var badGatewayResponse = mock(HttpResponse.class);
when(badGatewayResponse.statusCode()).thenReturn(502);
when(badGatewayResponse.headers())
.thenReturn(HttpHeaders.of(Map.of("Content-Type", List.of("text/plain")), (t, u) -> true));
when(badGatewayResponse.body()).thenReturn("error code: 502");

when(httpResponse.statusCode())
.thenReturn(200);

when(httpClient.send(any(), eq(bodyHandler)))
.thenReturn(badGatewayResponse)
.thenReturn(httpResponse);

var result = client.send(httpRequest, bodyHandler);
assertEquals(httpResponse, result);
verify(httpClient, times(2)).send(any(), eq(bodyHandler));
verify(mockInterceptor, times(2)).intercept(any(), eq(bodyHandler), anyInt(), any());
}

}

@Nested
Expand Down Expand Up @@ -625,6 +658,49 @@ public <T> CompletableFuture<HttpResponse<T>> intercept(HttpRequest request, Bod
client.send(httpRequest, bodyHandler).join();
verify(httpClient, times(4)).sendAsync(any(), any(BodyHandler.class));
}

@Test
@SuppressWarnings("unchecked")
void should_retry_when_plain_text_response_has_retryable_status_code() throws Exception {

AsyncHttpClient client = AsyncHttpClient.builder()
.httpClient(httpClient)
.interceptor(RetryInterceptor.ON_RETRYABLE_STATUS_CODES)
.interceptor(mockInterceptor)
.build();

when(mockInterceptor.intercept(any(), eq(bodyHandler), anyInt(), any()))
.thenAnswer(CHAIN_MOCK);

when(httpClient.sendAsync(any(), any(BodyHandler.class)))
.thenAnswer(invocation -> {
BodyHandler<String> handler = invocation.getArgument(1);
var responseInfo = mock(HttpResponse.ResponseInfo.class);
when(responseInfo.statusCode()).thenReturn(502);
when(responseInfo.headers())
.thenReturn(HttpHeaders.of(Map.of("Content-Type", List.of("text/plain")), (t, u) -> true));

var subscriber = handler.apply(responseInfo);

subscriber.onSubscribe(mock(Flow.Subscription.class));
subscriber.onNext(List.of(ByteBuffer.wrap("error code: 502".getBytes(StandardCharsets.UTF_8))));
subscriber.onComplete();

return subscriber.getBody().handle((body, throwable) -> {
if (throwable != null) {
return CompletableFuture.failedFuture(throwable);
}
return CompletableFuture.failedFuture(new IllegalStateException("Exception was expected"));
}).thenCompose(cf -> cf);
})
.thenReturn(completedFuture(httpResponse));

var result = client.send(httpRequest, bodyHandler).get();
assertEquals(httpResponse, result);
verify(httpClient, times(2)).sendAsync(any(), any(BodyHandler.class));
verify(mockInterceptor, times(2)).intercept(any(), eq(bodyHandler), anyInt(), any());
}

}

@Test
Expand Down