Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.ErrorDetails;
import com.google.cloud.grpc.BaseGrpcServiceException;
import com.google.common.base.Preconditions;
import com.google.protobuf.util.Durations;
Expand All @@ -24,6 +26,7 @@
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.protobuf.ProtoUtils;
import java.util.Map;
import javax.annotation.Nullable;

/** Base exception type for all exceptions produced by the Cloud Spanner service. */
Expand Down Expand Up @@ -51,6 +54,7 @@ public String getResourceName() {
ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());

private final ErrorCode code;
private final ApiException apiException;

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
SpannerException(
Expand All @@ -59,10 +63,23 @@ public String getResourceName() {
boolean retryable,
@Nullable String message,
@Nullable Throwable cause) {
super(message, cause, code.getCode(), retryable);
super(
message,
// If the cause is instance of APIException then using its cause to avoid the breaking
// change, because earlier we were passing APIException's cause to constructor.
cause instanceof ApiException ? cause.getCause() : cause,
code.getCode(),
retryable);

if (token != DoNotConstructDirectly.ALLOWED) {
throw new AssertionError("Do not construct directly: use SpannerExceptionFactory");
}

if (cause instanceof ApiException) {
this.apiException = (ApiException) cause;
} else {
this.apiException = null;
}
this.code = Preconditions.checkNotNull(code);
}

Expand Down Expand Up @@ -95,4 +112,67 @@ static long extractRetryDelay(Throwable cause) {
}
return -1L;
}

/**
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
* reason otherwise null.
*
* @see <a
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L117">Reason</a>
* @return the reason of an error.
*/
public String getReason() {
if (this.apiException != null) {
return this.apiException.getReason();
}
return null;
}

/**
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
* specific domain otherwise null.
*
* @see <a
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L125">Domain</a>
* @return the logical grouping to which the "reason" belongs.
*/
public String getDomain() {
if (this.apiException != null) {
return this.apiException.getDomain();
}
return null;
}

/**
* Checks the underlying reason of the exception and if it's {@link ApiException} then return a
* map of key-value pairs otherwise null.
*
* @see <a
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L135">Metadata</a>
* @return the map of additional structured details about an error.
*/
public Map<String, String> getMetadata() {
if (this.apiException != null) {
return this.apiException.getMetadata();
}
return null;
}

/**
* Checks the underlying reason of the exception and if it's {@link ApiException} then return the
* ErrorDetails otherwise null.
*
* @see <a
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto">Status</a>
* @see <a
* href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto">Error
* Details</a>
* @return An object containing getters for structured objects from error_details.proto.
*/
public ErrorDetails getErrorDetails() {
if (this.apiException != null) {
return this.apiException.getErrorDetails();
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,8 @@ private static SpannerException fromApiException(ApiException exception) {
code = Status.Code.UNKNOWN;
}
ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code));
if (exception.getCause() != null) {
return SpannerExceptionFactory.newSpannerException(
errorCode, exception.getMessage(), exception.getCause());
} else {
return SpannerExceptionFactory.newSpannerException(errorCode, exception.getMessage());
}
return SpannerExceptionFactory.newSpannerException(
errorCode, exception.getMessage(), exception);
}

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