Skip to content

Commit 29a34e1

Browse files
redo graphapierrorexception (#222)
* remove incorrect GraphApiError parsing * prevent exceptions during GraphApiErrorException creation * remove Response constructor from GraphApiErrorException * add JsonUtil for case-insensitive JSON lookup * use case-insensitive JSON parsing in GraphApiErrorException * add missing success check in OneDriveUploader * OneDrive cleanup * OneDrive cleanup
1 parent 8dd1a0b commit 29a34e1

3 files changed

Lines changed: 196 additions & 119 deletions

File tree

Lines changed: 51 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,89 @@
11
package ratismal.drivebackup.uploaders.onedrive;
22

3-
import okhttp3.Response;
43
import org.jetbrains.annotations.NotNull;
54
import org.jetbrains.annotations.Nullable;
6-
import org.json.JSONArray;
75
import org.json.JSONObject;
86
import org.json.JSONException;
97

10-
import java.io.IOException;
11-
import java.util.ArrayList;
12-
import java.util.List;
13-
import java.util.stream.Collectors;
8+
import static ratismal.drivebackup.util.JsonUtil.optJsonObjectIgnoreCase;
9+
import static ratismal.drivebackup.util.JsonUtil.optStringIgnoreCase;
1410

1511
/**
1612
* an exception representing a microsoft graph api error
1713
*/
1814
public class GraphApiErrorException extends Exception {
1915
private static final String ERROR_OBJ_KEY = "error";
2016
private static final String CODE_STR_KEY = "code";
21-
private static final String INNERERROR_OBJ_KEY = "innererror";
2217
private static final String MESSAGE_STR_KEY = "message";
23-
private static final String DETAILS_ARR_KEY = "details";
2418

25-
/** status code of the response or -1 if not available */
19+
/** status code of the response */
2620
public final int statusCode;
2721
/** an error code string for the error that occurred */
28-
public final String errorCode;
22+
public final @NotNull String errorCode;
2923
/** a developer ready message about the error that occurred. this shouldn't be displayed to the user directly */
30-
public final String errorMessage;
31-
/** optional list of additional error objects that might be more specific than the top-level error */
32-
public final List<String> innerErrors;
33-
/**
34-
* optional list of additional error objects that might provide a breakdown of multiple errors encountered
35-
* while processing the request
36-
*/
37-
public final List<GraphApiErrorException> details;
38-
39-
/**
40-
* create the exception from a response
41-
*
42-
* @param response to parse error from its body
43-
* @throws IOException if the body string could not be loaded
44-
* @throws NullPointerException if the body could not be loaded
45-
* @throws JSONException if the body does not contain the expected json values
46-
*/
47-
public GraphApiErrorException(@NotNull Response response) throws IOException {
48-
this(response.code(), new JSONObject(response.body().string()).getJSONObject(ERROR_OBJ_KEY));
49-
}
24+
public final @NotNull String errorMessage;
25+
/** the full error object */
26+
public final @Nullable JSONObject errorObject;
5027

5128
/**
5229
* create the exception from a status code and response body
5330
*
54-
* @param statusCode of the response
55-
* @param responseBody of the response
31+
* @param statusCode of the response
32+
* @param jsonResponse string of the response body
5633
* @throws JSONException if the body does not contain the expected json values
5734
*/
58-
public GraphApiErrorException(int statusCode, @NotNull String responseBody) {
59-
this(statusCode, new JSONObject(responseBody).getJSONObject(ERROR_OBJ_KEY));
35+
public GraphApiErrorException(int statusCode, @NotNull String jsonResponse) {
36+
this(statusCode, new ParsedError(jsonResponse));
6037
}
6138

62-
private static List<String> parseInnerErrors(@Nullable JSONObject innerErrors) {
63-
List<String> list = new ArrayList<>();
64-
while (innerErrors != null) {
65-
String errorCode = innerErrors.optString(CODE_STR_KEY);
66-
if (errorCode != null) {
67-
list.add(errorCode);
39+
/** parsing logic that needs to happen before calling this/super constructor */
40+
private static class ParsedError {
41+
public final @NotNull String errorCode;
42+
public final @NotNull String errorMessage;
43+
public final @Nullable JSONObject errorObject;
44+
45+
public ParsedError(@NotNull String responseBody) {
46+
JSONObject errorResponse;
47+
try {
48+
errorResponse = new JSONObject(responseBody);
49+
} catch (JSONException jsonException) {
50+
this.errorCode = "invalidErrorResponse";
51+
this.errorMessage = String.valueOf(jsonException.getMessage());
52+
this.errorObject = null;
53+
return;
54+
}
55+
JSONObject errorObject = optJsonObjectIgnoreCase(errorResponse, ERROR_OBJ_KEY);
56+
if (errorObject == null) {
57+
this.errorCode = "invalidErrorResponse";
58+
this.errorMessage = String.format("error response has no json object '%s'", ERROR_OBJ_KEY);
59+
this.errorObject = null;
60+
return;
6861
}
69-
innerErrors = innerErrors.optJSONObject(INNERERROR_OBJ_KEY);
70-
}
71-
return list;
72-
}
7362

74-
private static List<GraphApiErrorException> parseDetails(@Nullable JSONArray details) {
75-
if (details == null) {
76-
return new ArrayList<>();
63+
this.errorCode = optStringIgnoreCase(errorObject, CODE_STR_KEY, "null");
64+
this.errorMessage = optStringIgnoreCase(errorObject, MESSAGE_STR_KEY, "null");
65+
this.errorObject = errorObject;
7766
}
78-
List<GraphApiErrorException> list = new ArrayList<>(details.length());
79-
for (int detailIdx = 0; detailIdx < details.length(); detailIdx++) {
80-
list.add(new GraphApiErrorException(-1, details.getJSONObject(detailIdx).getJSONObject(ERROR_OBJ_KEY)));
81-
}
82-
return list;
83-
}
84-
85-
private GraphApiErrorException(int statusCode, @NotNull JSONObject error) {
86-
this(statusCode, error.getString(CODE_STR_KEY), error.getString(MESSAGE_STR_KEY),
87-
parseInnerErrors(error.optJSONObject(INNERERROR_OBJ_KEY)),
88-
parseDetails(error.optJSONArray(DETAILS_ARR_KEY)));
8967
}
9068

91-
private static String toMessage(int statusCode, String errorCode, String errorMessage, List<String> innerErrors,
92-
List<GraphApiErrorException> details) {
93-
String format = "%d %s : \"%s\"%s%s";
94-
String inner = String.join("\", \"", innerErrors);
95-
String detail = details.stream().map(GraphApiErrorException::getMessage).collect(Collectors.joining(" }, { "));
96-
if (!inner.isEmpty()) {
97-
inner = " inner:[ \"" + inner + "\" ]";
98-
}
99-
if (!detail.isEmpty()) {
100-
detail = " details:[ { " + detail + " } ]";
69+
/**
70+
* constructs a formatted error message string using the provided status code and error details.
71+
* if ParsedError.errorObject is non-null, its included as pretty-printed json.
72+
*/
73+
private static @NotNull String toMessage(int statusCode, @NotNull ParsedError error) {
74+
String format = "%d %s : \"%s\"";
75+
String common = String.format(format, statusCode, error.errorCode, error.errorMessage);
76+
if (error.errorObject == null) {
77+
return common;
10178
}
102-
return String.format(format, statusCode, errorCode, errorMessage, inner, detail);
79+
return common + '\n' + error.errorObject.toString(2);
10380
}
10481

105-
private GraphApiErrorException(int statusCode, String errorCode, String errorMessage, List<String> innerErrors,
106-
List<GraphApiErrorException> details) {
107-
super(toMessage(statusCode, errorCode, errorMessage, innerErrors, details));
82+
private GraphApiErrorException(int statusCode, @NotNull ParsedError error) {
83+
super(toMessage(statusCode, error));
10884
this.statusCode = statusCode;
109-
this.errorCode = errorCode;
110-
this.errorMessage = errorMessage;
111-
this.innerErrors = innerErrors;
112-
this.details = details;
85+
this.errorCode = error.errorCode;
86+
this.errorMessage = error.errorMessage;
87+
this.errorObject = error.errorObject;
11388
}
11489
}

0 commit comments

Comments
 (0)