Skip to content

Commit c8a2184

Browse files
feat: Error Details Improvement (#1929)
feat: Error Details Improvement
1 parent b685f47 commit c8a2184

7 files changed

+164
-20
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbortedException.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
1920
import javax.annotation.Nullable;
2021

2122
/**
@@ -34,6 +35,15 @@ public class AbortedException extends SpannerException {
3435
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3536
AbortedException(
3637
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
37-
super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause);
38+
this(token, message, cause, null);
39+
}
40+
41+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
42+
AbortedException(
43+
DoNotConstructDirectly token,
44+
@Nullable String message,
45+
@Nullable Throwable cause,
46+
@Nullable ApiException apiException) {
47+
super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause, apiException);
3848
}
3949
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
1920
import javax.annotation.Nullable;
2021

2122
/**
@@ -31,6 +32,15 @@ public class AdminRequestsPerMinuteExceededException extends SpannerException {
3132
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3233
AdminRequestsPerMinuteExceededException(
3334
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
34-
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause);
35+
this(token, message, cause, null);
36+
}
37+
38+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
39+
AdminRequestsPerMinuteExceededException(
40+
DoNotConstructDirectly token,
41+
@Nullable String message,
42+
@Nullable Throwable cause,
43+
@Nullable ApiException apiException) {
44+
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause, apiException);
3545
}
3646
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
1920
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
2021
import com.google.rpc.ResourceInfo;
2122
import javax.annotation.Nullable;
@@ -34,6 +35,16 @@ public class DatabaseNotFoundException extends ResourceNotFoundException {
3435
@Nullable String message,
3536
ResourceInfo resourceInfo,
3637
@Nullable Throwable cause) {
37-
super(token, message, resourceInfo, cause);
38+
this(token, message, resourceInfo, cause, null);
39+
}
40+
41+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
42+
DatabaseNotFoundException(
43+
DoNotConstructDirectly token,
44+
@Nullable String message,
45+
ResourceInfo resourceInfo,
46+
@Nullable Throwable cause,
47+
@Nullable ApiException apiException) {
48+
super(token, message, resourceInfo, cause, apiException);
3849
}
3950
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
1920
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
2021
import com.google.rpc.ResourceInfo;
2122
import javax.annotation.Nullable;
@@ -34,6 +35,15 @@ public class InstanceNotFoundException extends ResourceNotFoundException {
3435
@Nullable String message,
3536
ResourceInfo resourceInfo,
3637
@Nullable Throwable cause) {
37-
super(token, message, resourceInfo, cause);
38+
this(token, message, resourceInfo, cause, null);
39+
}
40+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
41+
InstanceNotFoundException(
42+
DoNotConstructDirectly token,
43+
@Nullable String message,
44+
ResourceInfo resourceInfo,
45+
@Nullable Throwable cause,
46+
@Nullable ApiException apiException) {
47+
super(token, message, resourceInfo, cause, apiException);
3848
}
3949
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
1920
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
2021
import com.google.rpc.ResourceInfo;
2122
import javax.annotation.Nullable;
@@ -34,6 +35,16 @@ public class SessionNotFoundException extends ResourceNotFoundException {
3435
@Nullable String message,
3536
ResourceInfo resourceInfo,
3637
@Nullable Throwable cause) {
37-
super(token, message, resourceInfo, cause);
38+
this(token, message, resourceInfo, cause, null);
39+
}
40+
41+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
42+
SessionNotFoundException(
43+
DoNotConstructDirectly token,
44+
@Nullable String message,
45+
ResourceInfo resourceInfo,
46+
@Nullable Throwable cause,
47+
@Nullable ApiException apiException) {
48+
super(token, message, resourceInfo, cause, apiException);
3849
}
3950
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java

+82-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import com.google.api.gax.rpc.ApiException;
20+
import com.google.api.gax.rpc.ErrorDetails;
1921
import com.google.cloud.grpc.BaseGrpcServiceException;
2022
import com.google.common.base.Preconditions;
2123
import com.google.protobuf.util.Durations;
@@ -24,6 +26,7 @@
2426
import io.grpc.Metadata;
2527
import io.grpc.Status;
2628
import io.grpc.protobuf.ProtoUtils;
29+
import java.util.Map;
2730
import javax.annotation.Nullable;
2831

2932
/** Base exception type for all exceptions produced by the Cloud Spanner service. */
@@ -36,8 +39,9 @@ public abstract static class ResourceNotFoundException extends SpannerException
3639
DoNotConstructDirectly token,
3740
@Nullable String message,
3841
ResourceInfo resourceInfo,
39-
@Nullable Throwable cause) {
40-
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause);
42+
@Nullable Throwable cause,
43+
@Nullable ApiException apiException) {
44+
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause, apiException);
4145
this.resourceInfo = resourceInfo;
4246
}
4347

@@ -51,6 +55,7 @@ public String getResourceName() {
5155
ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
5256

5357
private final ErrorCode code;
58+
private final ApiException apiException;
5459

5560
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
5661
SpannerException(
@@ -59,11 +64,23 @@ public String getResourceName() {
5964
boolean retryable,
6065
@Nullable String message,
6166
@Nullable Throwable cause) {
67+
this(token, code, retryable, message, cause, null);
68+
}
69+
70+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
71+
SpannerException(
72+
DoNotConstructDirectly token,
73+
ErrorCode code,
74+
boolean retryable,
75+
@Nullable String message,
76+
@Nullable Throwable cause,
77+
@Nullable ApiException apiException) {
6278
super(message, cause, code.getCode(), retryable);
6379
if (token != DoNotConstructDirectly.ALLOWED) {
6480
throw new AssertionError("Do not construct directly: use SpannerExceptionFactory");
6581
}
6682
this.code = Preconditions.checkNotNull(code);
83+
this.apiException = apiException;
6784
}
6885

6986
/** Returns the error code associated with this exception. */
@@ -95,4 +112,67 @@ static long extractRetryDelay(Throwable cause) {
95112
}
96113
return -1L;
97114
}
115+
116+
/**
117+
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
118+
* reason otherwise null.
119+
*
120+
* @see <a
121+
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L117">Reason</a>
122+
* @return the reason of an error.
123+
*/
124+
public String getReason() {
125+
if (this.apiException != null) {
126+
return this.apiException.getReason();
127+
}
128+
return null;
129+
}
130+
131+
/**
132+
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
133+
* specific domain otherwise null.
134+
*
135+
* @see <a
136+
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L125">Domain</a>
137+
* @return the logical grouping to which the "reason" belongs.
138+
*/
139+
public String getDomain() {
140+
if (this.apiException != null) {
141+
return this.apiException.getDomain();
142+
}
143+
return null;
144+
}
145+
146+
/**
147+
* Checks the underlying reason of the exception and if it's {@link ApiException} then return a
148+
* map of key-value pairs otherwise null.
149+
*
150+
* @see <a
151+
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L135">Metadata</a>
152+
* @return the map of additional structured details about an error.
153+
*/
154+
public Map<String, String> getMetadata() {
155+
if (this.apiException != null) {
156+
return this.apiException.getMetadata();
157+
}
158+
return null;
159+
}
160+
161+
/**
162+
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
163+
* ErrorDetails otherwise null.
164+
*
165+
* @see <a
166+
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto">Status</a>
167+
* @see <a
168+
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto">Error
169+
* Details</a>
170+
* @return An object containing getters for structured objects from error_details.proto.
171+
*/
172+
public ErrorDetails getErrorDetails() {
173+
if (this.apiException != null) {
174+
return this.apiException.getErrorDetails();
175+
}
176+
return null;
177+
}
98178
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,15 @@ static StatusRuntimeException createAbortedExceptionWithRetryDelay(
251251
}
252252

253253
static SpannerException newSpannerExceptionPreformatted(
254-
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
254+
ErrorCode code,
255+
@Nullable String message,
256+
@Nullable Throwable cause,
257+
@Nullable ApiException apiException) {
255258
// This is the one place in the codebase that is allowed to call constructors directly.
256259
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
257260
switch (code) {
258261
case ABORTED:
259-
return new AbortedException(token, message, cause);
262+
return new AbortedException(token, message, cause, apiException);
260263
case RESOURCE_EXHAUSTED:
261264
ErrorInfo info = extractErrorInfo(cause);
262265
if (info != null
@@ -265,26 +268,35 @@ static SpannerException newSpannerExceptionPreformatted(
265268
&& AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_VALUE.equals(
266269
info.getMetadataMap()
267270
.get(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY))) {
268-
return new AdminRequestsPerMinuteExceededException(token, message, cause);
271+
return new AdminRequestsPerMinuteExceededException(token, message, cause, apiException);
269272
}
270273
case NOT_FOUND:
271274
ResourceInfo resourceInfo = extractResourceInfo(cause);
272275
if (resourceInfo != null) {
273276
switch (resourceInfo.getResourceType()) {
274277
case SESSION_RESOURCE_TYPE:
275-
return new SessionNotFoundException(token, message, resourceInfo, cause);
278+
return new SessionNotFoundException(
279+
token, message, resourceInfo, cause, apiException);
276280
case DATABASE_RESOURCE_TYPE:
277-
return new DatabaseNotFoundException(token, message, resourceInfo, cause);
281+
return new DatabaseNotFoundException(
282+
token, message, resourceInfo, cause, apiException);
278283
case INSTANCE_RESOURCE_TYPE:
279-
return new InstanceNotFoundException(token, message, resourceInfo, cause);
284+
return new InstanceNotFoundException(
285+
token, message, resourceInfo, cause, apiException);
280286
}
281287
}
282288
// Fall through to the default.
283289
default:
284-
return new SpannerException(token, code, isRetryable(code, cause), message, cause);
290+
return new SpannerException(
291+
token, code, isRetryable(code, cause), message, cause, apiException);
285292
}
286293
}
287294

295+
static SpannerException newSpannerExceptionPreformatted(
296+
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
297+
return newSpannerExceptionPreformatted(code, message, cause, null);
298+
}
299+
288300
private static SpannerException fromApiException(ApiException exception) {
289301
Status.Code code;
290302
if (exception.getStatusCode() instanceof GrpcStatusCode) {
@@ -295,12 +307,12 @@ private static SpannerException fromApiException(ApiException exception) {
295307
code = Status.Code.UNKNOWN;
296308
}
297309
ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code));
298-
if (exception.getCause() != null) {
299-
return SpannerExceptionFactory.newSpannerException(
300-
errorCode, exception.getMessage(), exception.getCause());
301-
} else {
302-
return SpannerExceptionFactory.newSpannerException(errorCode, exception.getMessage());
303-
}
310+
311+
return SpannerExceptionFactory.newSpannerExceptionPreformatted(
312+
errorCode,
313+
formatMessage(errorCode, exception.getMessage()),
314+
exception.getCause(),
315+
exception);
304316
}
305317

306318
private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) {

0 commit comments

Comments
 (0)