Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Error Details Improvement #1929

Merged
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import javax.annotation.Nullable;

/**
Expand All @@ -34,6 +35,15 @@ public class AbortedException extends SpannerException {
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AbortedException(
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause);
this(token, message, cause, null);
}

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AbortedException(
DoNotConstructDirectly token,
@Nullable String message,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause, apiException);
}
}
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import javax.annotation.Nullable;

/**
Expand All @@ -31,6 +32,15 @@ public class AdminRequestsPerMinuteExceededException extends SpannerException {
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AdminRequestsPerMinuteExceededException(
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause);
this(token, message, cause, null);
}

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AdminRequestsPerMinuteExceededException(
DoNotConstructDirectly token,
@Nullable String message,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause, apiException);
}
}
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
Expand All @@ -34,6 +35,16 @@ public class DatabaseNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
super(token, message, resourceInfo, cause);
this(token, message, resourceInfo, cause, null);
}

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
DatabaseNotFoundException(
DoNotConstructDirectly token,
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, message, resourceInfo, cause, apiException);
}
}
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
Expand All @@ -34,6 +35,15 @@ public class InstanceNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
super(token, message, resourceInfo, cause);
this(token, message, resourceInfo, cause, null);
}
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
InstanceNotFoundException(
DoNotConstructDirectly token,
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, message, resourceInfo, cause, apiException);
}
}
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.spanner;

import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
Expand All @@ -34,6 +35,16 @@ public class SessionNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
super(token, message, resourceInfo, cause);
this(token, message, resourceInfo, cause, null);
}

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
SessionNotFoundException(
DoNotConstructDirectly token,
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, message, resourceInfo, cause, apiException);
}
}
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 All @@ -36,8 +39,9 @@ public abstract static class ResourceNotFoundException extends SpannerException
DoNotConstructDirectly token,
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause);
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause, apiException);
this.resourceInfo = resourceInfo;
}

Expand All @@ -51,6 +55,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,11 +64,23 @@ public String getResourceName() {
boolean retryable,
@Nullable String message,
@Nullable Throwable cause) {
this(token, code, retryable, message, cause, null);
}

/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
SpannerException(
DoNotConstructDirectly token,
ErrorCode code,
boolean retryable,
@Nullable String message,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
super(message, cause, code.getCode(), retryable);
if (token != DoNotConstructDirectly.ALLOWED) {
throw new AssertionError("Do not construct directly: use SpannerExceptionFactory");
}
this.code = Preconditions.checkNotNull(code);
this.apiException = apiException;
}

/** Returns the error code associated with this exception. */
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() {
gauravpurohit06 marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
Expand Up @@ -251,12 +251,15 @@ static StatusRuntimeException createAbortedExceptionWithRetryDelay(
}

static SpannerException newSpannerExceptionPreformatted(
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
ErrorCode code,
@Nullable String message,
@Nullable Throwable cause,
@Nullable ApiException apiException) {
// This is the one place in the codebase that is allowed to call constructors directly.
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
switch (code) {
case ABORTED:
return new AbortedException(token, message, cause);
return new AbortedException(token, message, cause, apiException);
case RESOURCE_EXHAUSTED:
ErrorInfo info = extractErrorInfo(cause);
if (info != null
Expand All @@ -265,26 +268,35 @@ static SpannerException newSpannerExceptionPreformatted(
&& AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_VALUE.equals(
info.getMetadataMap()
.get(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY))) {
return new AdminRequestsPerMinuteExceededException(token, message, cause);
return new AdminRequestsPerMinuteExceededException(token, message, cause, apiException);
}
case NOT_FOUND:
ResourceInfo resourceInfo = extractResourceInfo(cause);
if (resourceInfo != null) {
switch (resourceInfo.getResourceType()) {
case SESSION_RESOURCE_TYPE:
return new SessionNotFoundException(token, message, resourceInfo, cause);
return new SessionNotFoundException(
token, message, resourceInfo, cause, apiException);
case DATABASE_RESOURCE_TYPE:
return new DatabaseNotFoundException(token, message, resourceInfo, cause);
return new DatabaseNotFoundException(
token, message, resourceInfo, cause, apiException);
case INSTANCE_RESOURCE_TYPE:
return new InstanceNotFoundException(token, message, resourceInfo, cause);
return new InstanceNotFoundException(
token, message, resourceInfo, cause, apiException);
}
}
// Fall through to the default.
default:
return new SpannerException(token, code, isRetryable(code, cause), message, cause);
return new SpannerException(
token, code, isRetryable(code, cause), message, cause, apiException);
}
}

static SpannerException newSpannerExceptionPreformatted(
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
return newSpannerExceptionPreformatted(code, message, cause, null);
}

private static SpannerException fromApiException(ApiException exception) {
Status.Code code;
if (exception.getStatusCode() instanceof GrpcStatusCode) {
Expand All @@ -295,12 +307,14 @@ private static SpannerException fromApiException(ApiException exception) {
code = Status.Code.UNKNOWN;
}
ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code));

Throwable exceptionCause = null;

if (exception.getCause() != null) {
return SpannerExceptionFactory.newSpannerException(
errorCode, exception.getMessage(), exception.getCause());
} else {
return SpannerExceptionFactory.newSpannerException(errorCode, exception.getMessage());
exceptionCause = exception.getCause();
}
return SpannerExceptionFactory.newSpannerExceptionPreformatted(
errorCode, formatMessage(errorCode, exception.getMessage()), exceptionCause, exception);
}

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