Skip to content
Open
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
3 changes: 3 additions & 0 deletions client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ dependencies {

compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")

testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
testImplementation("org.assertj:assertj-core:3.26.0")
}

tasks.withType<JavaCompile> {
Expand Down
37 changes: 37 additions & 0 deletions client/src/main/java/ai/fal/client/exception/FalException.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,60 @@ public class FalException extends RuntimeException {
@Nullable
private final String requestId;

private final int statusCode;

@Nullable
private final String body;

public FalException(@Nonnull String message, @Nullable String requestId) {
super(requireNonNull(message));
this.requestId = requestId;
this.statusCode = -1;
this.body = null;
}

public FalException(
@Nonnull String message,
@Nullable String requestId,
int statusCode,
@Nullable String body) {
super(requireNonNull(message));
this.requestId = requestId;
this.statusCode = statusCode;
this.body = body;
}

public FalException(@Nonnull String message, @Nonnull Throwable cause, @Nullable String requestId) {
super(requireNonNull(message), cause);
this.requestId = requestId;
this.statusCode = -1;
this.body = null;
}

public FalException(Throwable cause) {
super(cause);
this.requestId = null;
this.statusCode = -1;
this.body = null;
}

@Nullable
public String getRequestId() {
return this.requestId;
}

/**
* Returns the HTTP status code of the failed response, or -1 if not available.
*/
public int getStatusCode() {
return this.statusCode;
}

/**
* Returns the raw response body of the failed response, or null if not available.
*/
@Nullable
public String getBody() {
return this.body;
}
}
66 changes: 64 additions & 2 deletions client/src/main/java/ai/fal/client/http/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.google.gson.JsonElement;
import jakarta.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -101,15 +102,76 @@ public <T> T handleResponse(Response response, Class<T> resultType) {

public FalException responseToException(Response response) {
final var requestId = response.header(HEADER_REQUEST_ID);
final var statusCode = response.code();
final var contentType = response.header("content-type");
String bodyString = null;

if (contentType != null && contentType.contains("application/json")) {
final var body = response.body();
if (body != null) {
final var json = gson.fromJson(body.charStream(), JsonElement.class);
try {
bodyString = body.string();
} catch (IOException ignored) {
}
}
}

var message = "Request failed with code: " + statusCode;
if (bodyString != null) {
final var detail = extractDetailMessage(bodyString);
if (detail != null) {
message += ": " + detail;
}
}

return new FalException("Request failed with code: " + response.code(), requestId);
return new FalException(message, requestId, statusCode, bodyString);
}

private String extractDetailMessage(String bodyString) {
try {
final var json = gson.fromJson(bodyString, JsonElement.class);
if (json == null || !json.isJsonObject()) {
return null;
}
final var jsonObject = json.getAsJsonObject();

// Handle {"detail": [{"msg": "...", ...}, ...]} format (validation errors)
if (jsonObject.has("detail")) {
final var detail = jsonObject.get("detail");
if (detail.isJsonArray()) {
final var messages = new ArrayList<String>();
for (final var item : detail.getAsJsonArray()) {
if (item.isJsonObject()) {
final var itemObj = item.getAsJsonObject();
if (itemObj.has("msg")) {
messages.add(itemObj.get("msg").getAsString());
}
}
}
if (!messages.isEmpty()) {
return String.join("; ", messages);
}
}
if (detail.isJsonPrimitive() && detail.getAsJsonPrimitive().isString()) {
return detail.getAsString();
}
}

// Handle {"message": "..."} format
if (jsonObject.has("message")) {
return jsonObject.get("message").getAsString();
}

// Handle {"error": "..."} format
if (jsonObject.has("error")) {
final var error = jsonObject.get("error");
if (error.isJsonPrimitive()) {
return error.getAsString();
}
}
} catch (Exception ignored) {
}
return null;
}

public <T> Output<T> wrapInResult(Response response, Class<T> resultType) {
Expand Down
Loading