Skip to content

Commit e591ed8

Browse files
committed
feat: parse error msg
1 parent 6ba51ad commit e591ed8

File tree

2 files changed

+419
-6
lines changed

2 files changed

+419
-6
lines changed

src/main/java/dev/openfga/sdk/errors/FgaError.java

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static dev.openfga.sdk.errors.HttpStatusCode.*;
44

5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
57
import dev.openfga.sdk.api.configuration.Configuration;
68
import dev.openfga.sdk.api.configuration.CredentialsMethod;
79
import dev.openfga.sdk.constants.FgaConstants;
@@ -11,6 +13,8 @@
1113
import java.util.Optional;
1214

1315
public class FgaError extends ApiException {
16+
private static final ObjectMapper ERROR_MAPPER = new ObjectMapper();
17+
1418
private String method = null;
1519
private String requestUrl = null;
1620
private String clientId = null;
@@ -28,6 +32,62 @@ public FgaError(String message, int code, HttpHeaders responseHeaders, String re
2832
super(message, code, responseHeaders, responseBody);
2933
}
3034

35+
/**
36+
* Parse the API error response body to extract the error message and code.
37+
* @param methodName The API method name that was called
38+
* @param responseBody The response body JSON string
39+
* @return A descriptive error message
40+
*/
41+
private static String parseErrorMessage(String methodName, String responseBody) {
42+
if (responseBody == null || responseBody.trim().isEmpty()) {
43+
return methodName;
44+
}
45+
46+
try {
47+
JsonNode jsonNode = ERROR_MAPPER.readTree(responseBody);
48+
49+
// Try to extract message field
50+
JsonNode messageNode = jsonNode.get("message");
51+
String message = (messageNode != null && !messageNode.isNull()) ? messageNode.asText() : null;
52+
53+
// If we have a message, return it, otherwise fall back to method name
54+
if (message != null && !message.trim().isEmpty()) {
55+
return message;
56+
}
57+
} catch (Exception e) {
58+
// If parsing fails, fall back to the method name
59+
// This is intentional to ensure errors are still reported even if the response format is unexpected
60+
}
61+
62+
return methodName;
63+
}
64+
65+
/**
66+
* Extract the API error code from the response body.
67+
* @param responseBody The response body JSON string
68+
* @return The error code, or null if not found
69+
*/
70+
private static String extractErrorCode(String responseBody) {
71+
if (responseBody == null || responseBody.trim().isEmpty()) {
72+
return null;
73+
}
74+
75+
try {
76+
JsonNode jsonNode = ERROR_MAPPER.readTree(responseBody);
77+
78+
// Try to extract code field
79+
JsonNode codeNode = jsonNode.get("code");
80+
if (codeNode != null && !codeNode.isNull()) {
81+
return codeNode.asText();
82+
}
83+
} catch (Exception e) {
84+
// If parsing fails, return null
85+
// This is intentional - we still want to report the error even if we can't extract the code
86+
}
87+
88+
return null;
89+
}
90+
3191
public static Optional<FgaError> getError(
3292
String name,
3393
HttpRequest request,
@@ -43,25 +103,43 @@ public static Optional<FgaError> getError(
43103

44104
final String body = response.body();
45105
final var headers = response.headers();
106+
107+
// Parse the error message from the response body
108+
final String errorMessage = parseErrorMessage(name, body);
46109
final FgaError error;
47110

48111
if (status == BAD_REQUEST || status == UNPROCESSABLE_ENTITY) {
49-
error = new FgaApiValidationError(name, previousError, status, headers, body);
112+
error = new FgaApiValidationError(errorMessage, previousError, status, headers, body);
50113
} else if (status == UNAUTHORIZED || status == FORBIDDEN) {
51-
error = new FgaApiAuthenticationError(name, previousError, status, headers, body);
114+
error = new FgaApiAuthenticationError(errorMessage, previousError, status, headers, body);
52115
} else if (status == NOT_FOUND) {
53-
error = new FgaApiNotFoundError(name, previousError, status, headers, body);
116+
error = new FgaApiNotFoundError(errorMessage, previousError, status, headers, body);
54117
} else if (status == TOO_MANY_REQUESTS) {
55-
error = new FgaApiRateLimitExceededError(name, previousError, status, headers, body);
118+
error = new FgaApiRateLimitExceededError(errorMessage, previousError, status, headers, body);
56119
} else if (isServerError(status)) {
57-
error = new FgaApiInternalError(name, previousError, status, headers, body);
120+
error = new FgaApiInternalError(errorMessage, previousError, status, headers, body);
58121
} else {
59-
error = new FgaError(name, previousError, status, headers, body);
122+
error = new FgaError(errorMessage, previousError, status, headers, body);
60123
}
61124

62125
error.setMethod(request.method());
63126
error.setRequestUrl(configuration.getApiUrl());
64127

128+
// Extract and set API error code from response body
129+
String apiErrorCode = extractErrorCode(body);
130+
if (apiErrorCode != null) {
131+
error.setApiErrorCode(apiErrorCode);
132+
}
133+
134+
// Extract and set request ID from response headers if present
135+
// Common request ID header names
136+
Optional<String> requestId = headers.firstValue("X-Request-Id")
137+
.or(() -> headers.firstValue("x-request-id"))
138+
.or(() -> headers.firstValue("Request-Id"));
139+
if (requestId.isPresent()) {
140+
error.setRequestId(requestId.get());
141+
}
142+
65143
// Extract and set Retry-After header if present
66144
Optional<String> retryAfter = headers.firstValue(FgaConstants.RETRY_AFTER_HEADER_NAME);
67145
if (retryAfter.isPresent()) {
@@ -135,6 +213,15 @@ public String getApiErrorCode() {
135213
return apiErrorCode;
136214
}
137215

216+
/**
217+
* Get the API error code.
218+
* This is an alias for getApiErrorCode() for convenience.
219+
* @return The API error code from the response
220+
*/
221+
public String getCode() {
222+
return apiErrorCode;
223+
}
224+
138225
public void setRetryAfterHeader(String retryAfterHeader) {
139226
this.retryAfterHeader = retryAfterHeader;
140227
}

0 commit comments

Comments
 (0)