Skip to content

Commit

Permalink
feat: Error Details Improvement (#1929)
Browse files Browse the repository at this point in the history
feat: Error Details Improvement
  • Loading branch information
gauravpurohit06 committed Jul 5, 2022
1 parent b685f47 commit c8a2184
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 20 deletions.
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() {
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,12 @@ 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.newSpannerExceptionPreformatted(
errorCode,
formatMessage(errorCode, exception.getMessage()),
exception.getCause(),
exception);
}

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

0 comments on commit c8a2184

Please sign in to comment.