diff --git a/src/example/java/io/github/sashirestela/cleverclient/example/StreamExample.java b/src/example/java/io/github/sashirestela/cleverclient/example/StreamExample.java
index b9770ce..2d4e40a 100644
--- a/src/example/java/io/github/sashirestela/cleverclient/example/StreamExample.java
+++ b/src/example/java/io/github/sashirestela/cleverclient/example/StreamExample.java
@@ -5,6 +5,7 @@
import io.github.sashirestela.cleverclient.example.openai.ChatResponse;
import io.github.sashirestela.cleverclient.example.openai.ChatService;
import io.github.sashirestela.cleverclient.example.openai.Message;
+import io.github.sashirestela.cleverclient.support.CleverClientException;
import java.util.Arrays;
@@ -12,46 +13,50 @@
* Before running this example you must have an OpenAI account and keep your Api Key in an
* environment variable called OPENAI_API_KEY.
*
- * @see OpenAI
+ * @see OpenAI
* Authentication
*/
public class StreamExample {
public static void main(String[] args) {
- final var BASE_URL = "https://api.openai.com";
- final var AUTHORIZATION_HEADER = "Authorization";
- final var BEARER_AUTHORIZATION = "Bearer " + System.getenv("OPENAI_API_KEY");
- final var END_OF_STREAM = "[DONE]";
+ try {
+ final var BASE_URL = "https://api.openai.com";
+ final var AUTHORIZATION_HEADER = "Authorization";
+ final var BEARER_AUTHORIZATION = "Bearer " + System.getenv("OPENAI_API_KEY");
+ final var END_OF_STREAM = "[DONE]";
- var cleverClient = CleverClient.builder()
- .baseUrl(BASE_URL)
- .header(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION)
- .endOfStream(END_OF_STREAM)
- .build();
- var chatService = cleverClient.create(ChatService.class);
+ var cleverClient = CleverClient.builder()
+ .baseUrl(BASE_URL)
+ .header(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION)
+ .endOfStream(END_OF_STREAM)
+ .build();
+ var chatService = cleverClient.create(ChatService.class);
- var chatRequest = ChatRequest.builder()
- .model("gpt-3.5-turbo")
- .messages(Arrays.asList(
- new Message("user", "Write an article about AI, no more than 100 words.")))
- .temperature(0.7)
- .build();
+ var chatRequest = ChatRequest.builder()
+ .model("gpt-3.5-turbo")
+ .messages(Arrays.asList(
+ new Message("user", "Write an article about AI, no more than 100 words.")))
+ .temperature(0.7)
+ .build();
- showTitle("Example Create Synchronous Stream");
- var chatResponseSync = chatService.createSyncStream(chatRequest);
- chatResponseSync
- .filter(chatResp -> chatResp.firstContent() != null)
- .map(ChatResponse::firstContent)
- .forEach(System.out::print);
- System.out.println();
+ showTitle("Example Create Synchronous Stream");
+ var chatResponseSync = chatService.createSyncStream(chatRequest);
+ chatResponseSync
+ .filter(chatResp -> chatResp.firstContent() != null)
+ .map(ChatResponse::firstContent)
+ .forEach(System.out::print);
+ System.out.println();
- showTitle("Example Create Asynchronous Stream");
- var chatResponseAsync = chatService.createAsyncStream(chatRequest).join();
- chatResponseAsync
- .filter(chatResp -> chatResp.firstContent() != null)
- .map(ChatResponse::firstContent)
- .forEach(System.out::print);
- System.out.println();
+ showTitle("Example Create Asynchronous Stream");
+ var chatResponseAsync = chatService.createAsyncStream(chatRequest).join();
+ chatResponseAsync
+ .filter(chatResp -> chatResp.firstContent() != null)
+ .map(ChatResponse::firstContent)
+ .forEach(System.out::print);
+ System.out.println();
+ } catch (Exception e) {
+ handleException(e);
+ }
}
private static void showTitle(String title) {
@@ -61,4 +66,19 @@ private static void showTitle(String title) {
System.out.println("-".repeat(times));
}
+ private static void handleException(Exception e) {
+ System.out.println(e.getMessage());
+ CleverClientException cce = null;
+ if (e instanceof CleverClientException) {
+ cce = (CleverClientException) e;
+ } else if (e.getCause() instanceof CleverClientException) {
+ cce = (CleverClientException) e.getCause();
+ }
+ if (cce != null) {
+ cce.responseInfo().ifPresentOrElse(System.out::println, cce::printStackTrace);
+ } else {
+ e.printStackTrace();
+ }
+ }
+
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSender.java
index 4758954..e50ff67 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSender.java
@@ -1,6 +1,8 @@
package io.github.sashirestela.cleverclient.sender;
import io.github.sashirestela.cleverclient.support.CleverClientException;
+import io.github.sashirestela.cleverclient.support.CleverClientException.HttpResponseInfo;
+import io.github.sashirestela.cleverclient.support.CleverClientException.HttpResponseInfo.HttpRequestInfo;
import io.github.sashirestela.cleverclient.support.ReturnType;
import io.github.sashirestela.cleverclient.util.CommonUtil;
import org.slf4j.Logger;
@@ -59,8 +61,22 @@ protected void throwExceptionIfErrorIsPresent(HttpResponse> response, Class>
data = (String) response.body();
}
logger.error("Response : {}", data);
- throw new CleverClientException("ERROR : {0}", data, null);
+ throw new CleverClientException(fillResponseInfo(response, data));
}
}
+ private HttpResponseInfo fillResponseInfo(HttpResponse> response, String data) {
+ var request = response.request();
+ return HttpResponseInfo.builder()
+ .statusCode(response.statusCode())
+ .data(data)
+ .headers(response.headers().map())
+ .request(HttpRequestInfo.builder()
+ .httpMethod(request.method())
+ .url(request.uri().toString())
+ .headers(request.headers().map())
+ .build())
+ .build();
+ }
+
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncBinarySender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncBinarySender.java
index c10e9db..875d1ef 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncBinarySender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncBinarySender.java
@@ -27,7 +27,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncCustomSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncCustomSender.java
index 2f125cb..960e0b8 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncCustomSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncCustomSender.java
@@ -27,7 +27,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncGenericSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncGenericSender.java
index e5c9a72..6789470 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncGenericSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncGenericSender.java
@@ -28,7 +28,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncListSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncListSender.java
index 420928c..3ec33b1 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncListSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncListSender.java
@@ -27,7 +27,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncPlainTextSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncPlainTextSender.java
index 6c24481..3bd0adf 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncPlainTextSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncPlainTextSender.java
@@ -26,7 +26,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamEventSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamEventSender.java
index af55b07..5b69710 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamEventSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamEventSender.java
@@ -41,7 +41,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamSender.java b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamSender.java
index f8bd923..2e2146f 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamSender.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/sender/HttpSyncStreamSender.java
@@ -35,7 +35,7 @@ public Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Return
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
- throw new CleverClientException(e.getMessage(), null, e);
+ throw new CleverClientException(e);
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/support/CleverClientException.java b/src/main/java/io/github/sashirestela/cleverclient/support/CleverClientException.java
index 7f4999b..ebd5753 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/support/CleverClientException.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/support/CleverClientException.java
@@ -1,17 +1,64 @@
package io.github.sashirestela.cleverclient.support;
+import io.github.sashirestela.cleverclient.util.Constant;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
public class CleverClientException extends RuntimeException {
+ private final HttpResponseInfo responseInfo;
+
public CleverClientException(String message) {
super(message);
+ this.responseInfo = null;
+ }
+
+ public CleverClientException(Throwable cause) {
+ super(cause);
+ this.responseInfo = null;
}
public CleverClientException(String message, Object... parameters) {
super(MessageFormat.format(message, Arrays.copyOfRange(parameters, 0, parameters.length - 1)),
(Throwable) parameters[parameters.length - 1]);
+ this.responseInfo = null;
+ }
+
+ public CleverClientException(HttpResponseInfo responseInfo) {
+ super(MessageFormat.format(Constant.HTTP_ERROR_MESSAGE, responseInfo.getStatusCode()), null);
+ this.responseInfo = responseInfo;
+ }
+
+ public Optional responseInfo() {
+ return Optional.ofNullable(responseInfo);
+ }
+
+ @Data
+ @Builder
+ public static class HttpResponseInfo implements Serializable {
+
+ private int statusCode;
+ private String data;
+ private Map> headers;
+ private HttpRequestInfo request;
+
+ @Data
+ @Builder
+ public static class HttpRequestInfo implements Serializable {
+
+ private String httpMethod;
+ private String url;
+ private Map> headers;
+
+ }
+
}
}
diff --git a/src/main/java/io/github/sashirestela/cleverclient/util/Constant.java b/src/main/java/io/github/sashirestela/cleverclient/util/Constant.java
index f589cae..f3c9cbd 100644
--- a/src/main/java/io/github/sashirestela/cleverclient/util/Constant.java
+++ b/src/main/java/io/github/sashirestela/cleverclient/util/Constant.java
@@ -12,4 +12,6 @@ private Constant() {
public static final String REGEX_PATH_PARAM_URL = "\\{(.*?)\\}";
+ public static final String HTTP_ERROR_MESSAGE = "HTTP interaction failed: server returned a {0} response status.";
+
}
diff --git a/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java b/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java
index acc9907..221afaf 100644
--- a/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java
+++ b/src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java
@@ -14,16 +14,21 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +44,8 @@ class HttpProcessorTest {
HttpResponse httpResponse = mock(HttpResponse.class);
HttpResponse> httpResponseStream = mock(HttpResponse.class);
HttpResponse httpResponseBinary = mock(HttpResponse.class);
+ HttpRequest httpRequest = mock(HttpRequest.class);
+ HttpHeaders httpHeaders = mock(HttpHeaders.class);
@BeforeAll
static void setup() {
@@ -359,49 +366,76 @@ void shouldReturnAnObjectWhenMethodIsAnnotatedWithMultipart() {
}
@Test
- void shouldThrownExceptionWhenCallingNoStreamingMethodAndServerRespondsWithError() {
+ void shouldThrownExceptionWhenCallingNoStreamingMethodAndServerRespondsWithError() throws URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponse));
+ when(httpHeaders.map()).thenReturn(Map.of());
+ when(httpRequest.method()).thenReturn("GET");
+ when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
+ when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpResponse.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponse.body()).thenReturn(
"{\"error\": {\"message\": \"The resource does not exist\", \"type\": \"T\", \"param\": \"P\", \"code\": \"C\"}}");
+ when(httpResponse.headers()).thenReturn(httpHeaders);
+ when(httpResponse.request()).thenReturn(httpRequest);
var service = httpProcessor.createProxy(ITest.AsyncService.class);
var futureService = service.getDemo(100);
- Exception exception = assertThrows(CompletionException.class,
- () -> futureService.join());
- assertTrue(exception.getMessage().contains("The resource does not exist"));
+
+ Exception exception = assertThrows(CompletionException.class, () -> futureService.join());
+ CleverClientException nestedException = (CleverClientException) exception.getCause();
+ assertNotNull(nestedException);
+ assertEquals(CleverClientException.class, nestedException.getClass());
+ assertTrue(nestedException.responseInfo().get().getData().contains("The resource does not exist"));
}
@Test
- void shouldThrownExceptionWhenCallingStreamingMethodAndServerRespondsWithError() {
+ void shouldThrownExceptionWhenCallingStreamingMethodAndServerRespondsWithError() throws URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofLines().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponseStream));
+ when(httpHeaders.map()).thenReturn(Map.of());
+ when(httpRequest.method()).thenReturn("GET");
+ when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
+ when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpResponseStream.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponseStream.body()).thenReturn(Stream.of(
"{\"error\": {\"message\": \"The resource does not exist\", \"type\": \"T\", \"param\": \"P\", \"code\": \"C\"}}"));
+ when(httpResponseStream.headers()).thenReturn(httpHeaders);
+ when(httpResponseStream.request()).thenReturn(httpRequest);
var service = httpProcessor.createProxy(ITest.AsyncService.class);
var futureService = service.getDemoStream(new ITest.RequestDemo("Descr", null));
- Exception exception = assertThrows(CompletionException.class,
- () -> futureService.join());
- assertTrue(exception.getMessage().contains("The resource does not exist"));
+
+ Exception exception = assertThrows(CompletionException.class, () -> futureService.join());
+ CleverClientException nestedException = (CleverClientException) exception.getCause();
+ assertNotNull(nestedException);
+ assertEquals(CleverClientException.class, nestedException.getClass());
+ assertTrue(nestedException.responseInfo().get().getData().contains("The resource does not exist"));
}
@Test
- void shouldThrownExceptionWhenCallingBinaryMethodAndServerRespondsWithError() {
+ void shouldThrownExceptionWhenCallingBinaryMethodAndServerRespondsWithError() throws URISyntaxException {
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofInputStream().getClass())))
.thenReturn(CompletableFuture.completedFuture(httpResponseBinary));
+ when(httpHeaders.map()).thenReturn(Map.of());
+ when(httpRequest.method()).thenReturn("GET");
+ when(httpRequest.uri()).thenReturn(new URI("https://api.com"));
+ when(httpRequest.headers()).thenReturn(httpHeaders);
when(httpResponseBinary.statusCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
when(httpResponseBinary.body()).thenReturn(new ByteArrayInputStream(
"{\"error\": {\"message\": \"The resource does not exist\", \"type\": \"T\", \"param\": \"P\", \"code\": \"C\"}}"
.getBytes()));
+ when(httpResponseBinary.headers()).thenReturn(httpHeaders);
+ when(httpResponseBinary.request()).thenReturn(httpRequest);
var service = httpProcessor.createProxy(ITest.AsyncService.class);
var futureService = service.getDemoBinary(100);
- Exception exception = assertThrows(CompletionException.class,
- () -> futureService.join());
- assertTrue(exception.getMessage().contains("The resource does not exist"));
+
+ Exception exception = assertThrows(CompletionException.class, () -> futureService.join());
+ CleverClientException nestedException = (CleverClientException) exception.getCause();
+ assertNotNull(nestedException);
+ assertEquals(CleverClientException.class, nestedException.getClass());
+ assertTrue(nestedException.responseInfo().get().getData().contains("The resource does not exist"));
}
@Test