Skip to content

Commit b42c0bd

Browse files
committed
Error handling revamp - A prototype
1 parent 87ce0ed commit b42c0bd

17 files changed

+1265
-111
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2019 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase;
18+
19+
public enum ErrorCode {
20+
21+
INVALID_ARGUMENT,
22+
23+
FAILED_PRECONDITION,
24+
25+
OUT_OF_RANGE,
26+
27+
UNAUTHENTICATED,
28+
29+
PERMISSION_DENIED,
30+
31+
NOT_FOUND,
32+
33+
CONFLICT,
34+
35+
ABORTED,
36+
37+
ALREADY_EXISTS,
38+
39+
RESOURCE_EXHAUSTED,
40+
41+
CANCELLED,
42+
43+
DATA_LOSS,
44+
45+
UNKNOWN,
46+
47+
INTERNAL,
48+
49+
UNAVAILABLE,
50+
51+
DEADLINE_EXCEEDED,
52+
}

src/main/java/com/google/firebase/FirebaseException.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,42 @@
1717
package com.google.firebase;
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
20+
import static com.google.common.base.Preconditions.checkNotNull;
2021

2122
import com.google.common.base.Strings;
2223
import com.google.firebase.internal.NonNull;
24+
import com.google.firebase.internal.Nullable;
2325

2426
/** Base class for all Firebase exceptions. */
2527
public class FirebaseException extends Exception {
2628

27-
// TODO(b/27677218): Exceptions should have non-empty messages.
28-
@Deprecated
29-
protected FirebaseException() {}
29+
private final ErrorCode errorCode;
30+
private final FirebaseHttpResponse response;
3031

3132
public FirebaseException(@NonNull String detailMessage) {
32-
super(detailMessage);
33-
checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
33+
this(detailMessage, null);
3434
}
3535

3636
public FirebaseException(@NonNull String detailMessage, Throwable cause) {
37-
super(detailMessage, cause);
38-
checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
37+
this(ErrorCode.UNKNOWN, detailMessage, null, cause);
38+
}
39+
40+
public FirebaseException(
41+
@NonNull ErrorCode errorCode,
42+
@NonNull String message,
43+
@Nullable FirebaseHttpResponse response,
44+
@Nullable Throwable cause) {
45+
super(message, cause);
46+
checkArgument(!Strings.isNullOrEmpty(message), "Message must not be null or empty");
47+
this.errorCode = checkNotNull(errorCode);
48+
this.response = response;
49+
}
50+
51+
public ErrorCode getPlatformErrorCode() {
52+
return errorCode;
53+
}
54+
55+
@Nullable public FirebaseHttpResponse getHttpResponse() {
56+
return response;
3957
}
4058
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2019 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase;
18+
19+
import com.google.api.client.http.HttpRequest;
20+
import com.google.api.client.http.HttpResponse;
21+
import com.google.api.client.http.HttpResponseException;
22+
import com.google.common.collect.ImmutableMap;
23+
import java.util.Map;
24+
25+
public final class FirebaseHttpResponse {
26+
27+
private final int statusCode;
28+
private final String content;
29+
private final Map<String, Object> headers;
30+
private final HttpRequest request;
31+
32+
public FirebaseHttpResponse(HttpResponse response, String content) {
33+
this.statusCode = response.getStatusCode();
34+
this.content = content;
35+
this.headers = ImmutableMap.copyOf(response.getHeaders());
36+
this.request = response.getRequest();
37+
}
38+
39+
public FirebaseHttpResponse(HttpResponseException e, HttpRequest request) {
40+
this.statusCode = e.getStatusCode();
41+
this.content = e.getContent();
42+
this.headers = ImmutableMap.copyOf(e.getHeaders());
43+
this.request = request;
44+
}
45+
46+
public int getStatusCode() {
47+
return this.statusCode;
48+
}
49+
50+
public String getContent() {
51+
return this.content;
52+
}
53+
54+
public Map<String, Object> getHeaders() {
55+
return this.headers;
56+
}
57+
58+
public HttpRequest getRequest() {
59+
return request;
60+
}
61+
}

src/main/java/com/google/firebase/internal/ApiClientUtils.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@
2020
import com.google.api.client.http.HttpResponse;
2121
import com.google.api.client.http.HttpTransport;
2222
import com.google.common.collect.ImmutableList;
23+
import com.google.firebase.ErrorCode;
2324
import com.google.firebase.FirebaseApp;
2425

26+
import com.google.firebase.FirebaseException;
2527
import java.io.IOException;
28+
import java.net.ConnectException;
29+
import java.net.NoRouteToHostException;
30+
import java.net.SocketException;
31+
import java.net.SocketTimeoutException;
32+
import java.net.UnknownHostException;
33+
import java.util.HashSet;
34+
import java.util.Set;
2635

2736
/**
2837
* A set of shared utilities for using the Google API client.
@@ -43,9 +52,22 @@ public class ApiClientUtils {
4352
* @return A new {@code HttpRequestFactory} instance.
4453
*/
4554
public static HttpRequestFactory newAuthorizedRequestFactory(FirebaseApp app) {
55+
return newAuthorizedRequestFactory(app, DEFAULT_RETRY_CONFIG);
56+
}
57+
58+
/**
59+
* Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and
60+
* automatic retries.
61+
*
62+
* @param app {@link FirebaseApp} from which to obtain authorization credentials.
63+
* @param retryConfig {@link RetryConfig} which specifies how and when to retry errors.
64+
* @return A new {@code HttpRequestFactory} instance.
65+
*/
66+
public static HttpRequestFactory newAuthorizedRequestFactory(
67+
FirebaseApp app, @Nullable RetryConfig retryConfig) {
4668
HttpTransport transport = app.getOptions().getHttpTransport();
4769
return transport.createRequestFactory(
48-
new FirebaseRequestInitializer(app, DEFAULT_RETRY_CONFIG));
70+
new FirebaseRequestInitializer(app, retryConfig));
4971
}
5072

5173
public static HttpRequestFactory newUnauthorizedRequestFactory(FirebaseApp app) {
@@ -62,4 +84,38 @@ public static void disconnectQuietly(HttpResponse response) {
6284
}
6385
}
6486
}
87+
88+
public static FirebaseException newFirebaseException(IOException e) {
89+
ErrorCode code = ErrorCode.UNKNOWN;
90+
String message = "Unknown error while making a remote service call" ;
91+
if (isInstance(e, SocketTimeoutException.class)) {
92+
code = ErrorCode.DEADLINE_EXCEEDED;
93+
message = "Timed out while making an API call";
94+
}
95+
96+
if (isInstance(e, UnknownHostException.class) || isInstance(e, NoRouteToHostException.class)) {
97+
code = ErrorCode.UNAVAILABLE;
98+
message = "Failed to establish a connection";
99+
}
100+
101+
return new FirebaseException(code, message + ": " + e.getMessage(), null, e);
102+
}
103+
104+
private static <T> boolean isInstance(IOException t, Class<T> type) {
105+
Throwable current = t;
106+
Set<Throwable> chain = new HashSet<>();
107+
while (current != null) {
108+
if (!chain.add(current)) {
109+
break;
110+
}
111+
112+
if (type.isInstance(current)) {
113+
return true;
114+
}
115+
116+
current = current.getCause();
117+
}
118+
119+
return false;
120+
}
65121
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2019 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.internal;
18+
19+
import com.google.api.client.http.HttpResponseException;
20+
import com.google.api.client.http.HttpStatusCodes;
21+
import com.google.common.collect.ImmutableMap;
22+
import com.google.firebase.ErrorCode;
23+
import com.google.firebase.FirebaseException;
24+
import com.google.firebase.FirebaseHttpResponse;
25+
import java.util.Map;
26+
27+
public abstract class BaseHttpErrorHandler<T extends FirebaseException>
28+
implements HttpErrorHandler<T> {
29+
30+
private static final Map<Integer, ErrorCode> HTTP_ERROR_CODES =
31+
ImmutableMap.<Integer, ErrorCode>builder()
32+
.put(HttpStatusCodes.STATUS_CODE_BAD_REQUEST, ErrorCode.INVALID_ARGUMENT)
33+
.put(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, ErrorCode.UNAUTHENTICATED)
34+
.put(HttpStatusCodes.STATUS_CODE_FORBIDDEN, ErrorCode.PERMISSION_DENIED)
35+
.put(HttpStatusCodes.STATUS_CODE_NOT_FOUND, ErrorCode.NOT_FOUND)
36+
.put(HttpStatusCodes.STATUS_CODE_CONFLICT, ErrorCode.CONFLICT)
37+
.put(429, ErrorCode.RESOURCE_EXHAUSTED)
38+
.put(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, ErrorCode.INTERNAL)
39+
.put(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, ErrorCode.UNAVAILABLE)
40+
.build();
41+
42+
@Override
43+
public final T handleHttpResponseException(
44+
HttpResponseException e, FirebaseHttpResponse response) {
45+
ErrorParams params = this.getErrorParams(e, response);
46+
return this.createException(params);
47+
}
48+
49+
protected ErrorParams getErrorParams(HttpResponseException e, FirebaseHttpResponse response) {
50+
ErrorCode code = HTTP_ERROR_CODES.get(e.getStatusCode());
51+
if (code == null) {
52+
code = ErrorCode.UNKNOWN;
53+
}
54+
55+
String message = String.format("Unexpected HTTP response with status: %d\n%s",
56+
e.getStatusCode(), e.getContent());
57+
return new ErrorParams(code, message, e, response);
58+
}
59+
60+
protected abstract T createException(ErrorParams params);
61+
62+
public static final class ErrorParams {
63+
private final ErrorCode errorCode;
64+
private final String message;
65+
private final HttpResponseException exception;
66+
private final FirebaseHttpResponse response;
67+
68+
public ErrorParams(
69+
ErrorCode errorCode, String message,
70+
HttpResponseException e, FirebaseHttpResponse response) {
71+
this.errorCode = errorCode;
72+
this.message = message;
73+
this.exception = e;
74+
this.response = response;
75+
}
76+
77+
public ErrorCode getErrorCode() {
78+
return errorCode;
79+
}
80+
81+
public String getMessage() {
82+
return message;
83+
}
84+
85+
public HttpResponseException getException() {
86+
return exception;
87+
}
88+
89+
public FirebaseHttpResponse getResponse() {
90+
return response;
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)