Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for error handling #81

Merged
merged 5 commits into from
Dec 14, 2024
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
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Library that makes it easy to use the Java HttpClient to perform http operations
- [Interface Annotations](#interface-annotations)
- [Supported Response Types](#supported-response-types)
- [Interface Default Methods](#interface-default-methods)
- [Exception Handling](#exception-handling) **NEW**
- [Examples](#-examples)
- [Contributing](#-contributing)
- [License](#-license)
Expand Down Expand Up @@ -242,6 +243,127 @@ interface Completions {

Note that we have named the annotated methods with the suffix "Basic" just to indicate that we should not call them directly but should call the default ones (those without the suffix).

### Exception Handling

The `CleverClientException` provides robust error handling mechanisms for HTTP client interactions, allowing you to capture and process different types of errors comprehensively.

#### Key Exception Handling Scenarios

##### 1. Basic Exception Catching

```java
try {
// Your HTTP client method call
Response response = client.someMethod();
} catch (CleverClientException e) {
// Handle the specific CleverClient exception
System.err.println("An error occurred: " + e.getMessage());
}
```

##### 2. Accessing HTTP Response Details

When an exception occurs during an HTTP request, you can retrieve detailed response information:

```java
try {
// HTTP client method call
client.makeRequest();
} catch (CleverClientException e) {
// Check if response info is available
e.responseInfo().ifPresent(responseInfo -> {
// Access status code
int statusCode = responseInfo.getStatusCode();
System.err.println("HTTP Status Code: " + statusCode);

// Access response headers
Map<String, List<String>> headers = responseInfo.getHeaders();
headers.forEach((key, value) ->
System.err.println(key + ": " + value)
);

// Access response data
String responseData = responseInfo.getData();
System.err.println("Response Body: " + responseData);

// Access request details
CleverClientException.HttpResponseInfo.HttpRequestInfo requestInfo =
responseInfo.getRequest();
System.err.println("Request Method: " + requestInfo.getHttpMethod());
System.err.println("Request URL: " + requestInfo.getUrl());
});
}
```
##### 3. Extracting CleverClientException from Other Exceptions

The library provides a utility method to extract `CleverClientException` from other exception types. This is the case when exceptions occurs in `CompletableFuture` processing, where a CompletionException is the outer exception and CleverClientException is the cause one:

```java
try {
// Some method that might throw an exception
performOperation();
} catch (Throwable e) {
// Try to extract CleverClientException
Optional<CleverClientException> cleverException =
CleverClientException.getFrom(e);

cleverException.ifPresent(exception -> {
// Handle the CleverClientException specifically
System.err.println("Detailed HTTP Error: " + exception.getMessage());

// Optionally access response details
exception.responseInfo().ifPresent(responseInfo -> {
// Handle response info
});
});
}
```
#### Best Practices

- Always handle exceptions at the appropriate level of your application.
- Log detailed error information for troubleshooting.
- Use the `responseInfo()` method to get additional context about HTTP errors.
- Consider creating custom error handling logic based on specific status codes or error conditions.

#### Example of Complex Error Handling

```java
public void robustHttpCall() {
try {
// HTTP client method
client.executeRequest();
} catch (CleverClientException e) {
// Comprehensive error handling
if (e.responseInfo().isPresent()) {
HttpResponseInfo info = e.responseInfo().get();
switch (info.getStatusCode()) {
case 400:
handleBadRequest(info);
break;
case 401:
handleUnauthorized(info);
break;
case 500:
handleServerError(info);
break;
default:
handleGenericError(e);
}
} else {
// Handle exceptions without response info
handleGenericError(e);
}
}
}
```

#### Recommendations

- Always use try-catch blocks when making HTTP calls
- Log errors for monitoring and debugging
- Implement specific handling for different HTTP status codes
- Use the rich error information provided by `CleverClientException`

## ✳ Examples

Some examples have been created in the folder [example](https://github.com/sashirestela/cleverclient/tree/main/src/example/java/io/github/sashirestela/cleverclient/example) and you can follow the next steps to execute them:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,71 @@
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.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
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 {
redirectSystemErr();

var cleverClient = CleverClient.builder()
.baseUrl(BASE_URL)
.header(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION)
.endOfStream(END_OF_STREAM)
.build();
var chatService = cleverClient.create(ChatService.class);
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 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 cleverClient = CleverClient.builder()
.baseUrl(BASE_URL)
.header(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION)
.endOfStream(END_OF_STREAM)
.build();
var chatService = cleverClient.create(ChatService.class);

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();
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 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 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();
} catch (Exception e) {
handleException(e);
}
}

private static void redirectSystemErr() throws FileNotFoundException {
File file = new File("error.log");
FileOutputStream fos = new FileOutputStream(file);
PrintStream ps = new PrintStream(fos);
System.setErr(ps);
}

private static void showTitle(String title) {
Expand All @@ -61,4 +79,15 @@ private static void showTitle(String title) {
System.out.println("-".repeat(times));
}

private static void handleException(Exception e) {
System.out.println(e.getMessage());
CleverClientException.getFrom(e)
.ifPresentOrElse(
cce -> cce.responseInfo()
.ifPresentOrElse(
System.out::println,
cce::printStackTrace),
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
Loading
Loading