22
33import static dev .openfga .sdk .errors .HttpStatusCode .*;
44
5+ import com .fasterxml .jackson .databind .JsonNode ;
6+ import com .fasterxml .jackson .databind .ObjectMapper ;
57import dev .openfga .sdk .api .configuration .Configuration ;
68import dev .openfga .sdk .api .configuration .CredentialsMethod ;
79import dev .openfga .sdk .constants .FgaConstants ;
1113import java .util .Optional ;
1214
1315public 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