Skip to content

Commit

Permalink
Adding support for error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
sashirestela committed Dec 14, 2024
1 parent 5bdd0d5 commit e494355
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,58 @@
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;

/**
* Before running this example you must have an OpenAI account and keep your Api Key in an
* environment variable called OPENAI_API_KEY.
*
* @see <a href="https://platform.openai.com/docs/api-reference/authentication">OpenAI
* @see <a href= "https://platform.openai.com/docs/api-reference/authentication">OpenAI
* Authentication</a>
*/
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) {
Expand All @@ -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();
}
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<HttpResponseInfo> responseInfo() {
return Optional.ofNullable(responseInfo);
}

@Data
@Builder
public static class HttpResponseInfo implements Serializable {

private int statusCode;
private String data;
private Map<String, List<String>> headers;
private HttpRequestInfo request;

@Data
@Builder
public static class HttpRequestInfo implements Serializable {

private String httpMethod;
private String url;
private Map<String, List<String>> headers;

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.";

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,6 +44,8 @@ class HttpProcessorTest {
HttpResponse<String> httpResponse = mock(HttpResponse.class);
HttpResponse<Stream<String>> httpResponseStream = mock(HttpResponse.class);
HttpResponse<InputStream> httpResponseBinary = mock(HttpResponse.class);
HttpRequest httpRequest = mock(HttpRequest.class);
HttpHeaders httpHeaders = mock(HttpHeaders.class);

@BeforeAll
static void setup() {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit e494355

Please sign in to comment.