diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1007b960e..c266cf9c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [2.15.0](https://github.com/googleapis/gax-java/compare/v2.14.0...v2.15.0) (2022-04-06)
+
+
+### Features
+
+* Error Details Improvements - GRPC ([#1634](https://github.com/googleapis/gax-java/issues/1634)) ([00c3b9d](https://github.com/googleapis/gax-java/commit/00c3b9ddae11736e1231e4aa45965deff7689f2a))
+* relocate native image properties from java-core to gax ([#1648](https://github.com/googleapis/gax-java/issues/1648)) ([609c2aa](https://github.com/googleapis/gax-java/commit/609c2aab94ff215c879932e60ae041309f20c2c7))
+
+
+### Dependencies
+
+* upgrade grpc to 1.45.1 and auth to 1.6.0 ([#1652](https://github.com/googleapis/gax-java/issues/1652)) ([8f8f625](https://github.com/googleapis/gax-java/commit/8f8f62572795c83b4459f6d3a0f33f31a1ecfb71))
+
## [2.14.0](https://github.com/googleapis/gax-java/compare/v2.13.0...v2.14.0) (2022-04-01)
diff --git a/build.gradle b/build.gradle
index 02dde1018..cfd2d9e36 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,7 +13,7 @@ plugins {
}
// TODO: Populate this from dependencies.properties version property (for proper Gradle-Bazel sync)
-project.version = "2.14.0" // {x-version-update:gax:current}
+project.version = "2.15.0" // {x-version-update:gax:current}
allprojects {
group = 'com.google.api'
diff --git a/dependencies.properties b/dependencies.properties
index 7e432daa3..d901df5c3 100644
--- a/dependencies.properties
+++ b/dependencies.properties
@@ -8,16 +8,16 @@
# Versions of oneself
# {x-version-update-start:gax:current}
-version.gax=2.14.0
+version.gax=2.15.0
# {x-version-update-end}
# {x-version-update-start:gax:current}
-version.gax_grpc=2.14.0
+version.gax_grpc=2.15.0
# {x-version-update-end}
# {x-version-update-start:gax:current}
-version.gax_bom=2.14.0
+version.gax_bom=2.15.0
# {x-version-update-end}
# {x-version-update-start:gax-httpjson:current}
-version.gax_httpjson=0.99.0
+version.gax_httpjson=0.100.0
# {x-version-update-end}
# Versions for dependencies which actual artifacts differ between Bazel and Gradle.
@@ -25,7 +25,7 @@ version.gax_httpjson=0.99.0
# with the sources.
version.com_google_protobuf=3.19.4
version.google_java_format=1.1
-version.io_grpc=1.45.0
+version.io_grpc=1.45.1
# Maven artifacts.
# Note, the actual name of each property matters (bazel build scripts depend on it).
@@ -34,8 +34,8 @@ version.io_grpc=1.45.0
# 2) Replace all characters which are neither alphabetic nor digits with the underscore ('_') character
maven.com_google_api_grpc_proto_google_common_protos=com.google.api.grpc:proto-google-common-protos:2.8.0
maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-google-common-protos:2.8.0
-maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.2.1
-maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.2.1
+maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.6.0
+maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.6.0
maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.28.0
maven.io_opencensus_opencensus_contrib_grpc_metrics=io.opencensus:opencensus-contrib-grpc-metrics:0.28.0
maven.io_opencensus_opencensus_contrib_http_util=io.opencensus:opencensus-contrib-http-util:0.28.0
diff --git a/gax-bom/build.gradle b/gax-bom/build.gradle
index 1dbcded2d..fcdf470e6 100644
--- a/gax-bom/build.gradle
+++ b/gax-bom/build.gradle
@@ -5,7 +5,7 @@ plugins {
archivesBaseName = 'gax-bom'
-project.version = "2.14.0" // {x-version-update:gax-bom:current}
+project.version = "2.15.0" // {x-version-update:gax-bom:current}
def mavenJavaDir = "$buildDir/publications/mavenJava"
def mavenJavaBomOutputFile = file(mavenJavaDir + '/pom-default.xml')
diff --git a/gax-bom/pom.xml b/gax-bom/pom.xml
index e861f615c..c00f88341 100644
--- a/gax-bom/pom.xml
+++ b/gax-bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.api
gax-bom
- 2.14.0
+ 2.15.0
pom
GAX (Google Api eXtensions) for Java
Google Api eXtensions for Java
@@ -33,34 +33,34 @@
com.google.api
gax
- 2.14.0
+ 2.15.0
com.google.api
gax
- 2.14.0
+ 2.15.0
testlib
com.google.api
gax-grpc
- 2.14.0
+ 2.15.0
com.google.api
gax-grpc
- 2.14.0
+ 2.15.0
testlib
com.google.api
gax-httpjson
- 0.99.0
+ 0.100.0
com.google.api
gax-httpjson
- 0.99.0
+ 0.100.0
testlib
diff --git a/gax-grpc/build.gradle b/gax-grpc/build.gradle
index a8d3df04b..6bdbe6f7c 100644
--- a/gax-grpc/build.gradle
+++ b/gax-grpc/build.gradle
@@ -1,7 +1,7 @@
archivesBaseName = 'gax-grpc'
// TODO: Populate this from dependencies.properties version property (for proper Gradle-Bazel sync)
-project.version = "2.14.0" // {x-version-update:gax-grpc:current}
+project.version = "2.15.0" // {x-version-update:gax-grpc:current}
dependencies {
api(project(':gax'),
diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcApiExceptionFactory.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcApiExceptionFactory.java
index 7e33946df..9c3dd0a84 100644
--- a/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcApiExceptionFactory.java
+++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcApiExceptionFactory.java
@@ -31,8 +31,12 @@
import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.ApiExceptionFactory;
+import com.google.api.gax.rpc.ErrorDetails;
import com.google.api.gax.rpc.StatusCode.Code;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
@@ -40,11 +44,13 @@
/**
* Core logic for transforming GRPC exceptions into {@link ApiException}s. This logic is shared
- * amongst all of the call types.
+ * amongst all the call types.
*
*
Package-private for internal use.
*/
class GrpcApiExceptionFactory {
+
+ @VisibleForTesting static final String ERROR_DETAIL_KEY = "grpc-status-details-bin";
private final ImmutableSet retryableCodes;
GrpcApiExceptionFactory(Set retryCodes) {
@@ -54,10 +60,10 @@ class GrpcApiExceptionFactory {
ApiException create(Throwable throwable) {
if (throwable instanceof StatusException) {
StatusException e = (StatusException) throwable;
- return create(throwable, e.getStatus().getCode());
+ return create(throwable, e.getStatus().getCode(), e.getTrailers());
} else if (throwable instanceof StatusRuntimeException) {
StatusRuntimeException e = (StatusRuntimeException) throwable;
- return create(throwable, e.getStatus().getCode());
+ return create(throwable, e.getStatus().getCode(), e.getTrailers());
} else if (throwable instanceof ApiException) {
return (ApiException) throwable;
} else {
@@ -67,8 +73,29 @@ ApiException create(Throwable throwable) {
}
}
- private ApiException create(Throwable throwable, Status.Code statusCode) {
+ private ApiException create(Throwable throwable, Status.Code statusCode, Metadata metadata) {
boolean canRetry = retryableCodes.contains(GrpcStatusCode.grpcCodeToStatusCode(statusCode));
- return ApiExceptionFactory.createException(throwable, GrpcStatusCode.of(statusCode), canRetry);
+ GrpcStatusCode grpcStatusCode = GrpcStatusCode.of(statusCode);
+
+ if (metadata == null) {
+ return ApiExceptionFactory.createException(throwable, grpcStatusCode, canRetry);
+ }
+
+ byte[] bytes = metadata.get(Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER));
+ if (bytes == null) {
+ return ApiExceptionFactory.createException(throwable, grpcStatusCode, canRetry);
+ }
+
+ com.google.rpc.Status status;
+ try {
+ status = com.google.rpc.Status.parseFrom(bytes);
+ } catch (InvalidProtocolBufferException e) {
+ return ApiExceptionFactory.createException(throwable, grpcStatusCode, canRetry);
+ }
+
+ ErrorDetails.Builder errorDetailsBuilder = ErrorDetails.builder();
+ errorDetailsBuilder.setRawErrorMessages(status.getDetailsList());
+ return ApiExceptionFactory.createException(
+ throwable, grpcStatusCode, canRetry, errorDetailsBuilder.build());
}
}
diff --git a/gax-grpc/src/main/resources/META-INF/native-image/com.google.api/gax-grpc/native-image.properties b/gax-grpc/src/main/resources/META-INF/native-image/com.google.api/gax-grpc/native-image.properties
new file mode 100644
index 000000000..ad3745380
--- /dev/null
+++ b/gax-grpc/src/main/resources/META-INF/native-image/com.google.api/gax-grpc/native-image.properties
@@ -0,0 +1,10 @@
+Args = --initialize-at-run-time=io.grpc.netty.shaded.io.netty.handler.ssl.OpenSsl,\
+ io.grpc.netty.shaded.io.netty.internal.tcnative.SSL,\
+ io.grpc.netty.shaded.io.netty.internal.tcnative.CertificateVerifier,\
+ io.grpc.netty.shaded.io.netty.internal.tcnative.SSLPrivateKeyMethod,\
+ io.grpc.netty.shaded.io.netty.internal.tcnative.AsyncSSLPrivateKeyMethod,\
+ io.grpc.netty.shaded.io.grpc.netty,\
+ io.grpc.netty.shaded.io.netty.channel.epoll,\
+ io.grpc.netty.shaded.io.netty.channel.unix,\
+ io.grpc.netty.shaded.io.netty.handler.ssl,\
+ io.grpc.internal.RetriableStream
\ No newline at end of file
diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcApiExceptionFactoryTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcApiExceptionFactoryTest.java
new file mode 100644
index 000000000..bb8febef5
--- /dev/null
+++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcApiExceptionFactoryTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.grpc;
+
+import static com.google.api.gax.grpc.GrpcApiExceptionFactory.ERROR_DETAIL_KEY;
+
+import com.google.api.gax.rpc.ApiException;
+import com.google.api.gax.rpc.ErrorDetails;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Truth;
+import com.google.protobuf.Any;
+import com.google.protobuf.Duration;
+import com.google.rpc.ErrorInfo;
+import com.google.rpc.RetryInfo;
+import com.google.rpc.Status;
+import io.grpc.Metadata;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class GrpcApiExceptionFactoryTest {
+
+ private static final ErrorInfo ERROR_INFO =
+ ErrorInfo.newBuilder()
+ .setDomain("googleapis.com")
+ .setReason("SERVICE_DISABLED")
+ .putAllMetadata(Collections.emptyMap())
+ .build();
+
+ private static final RetryInfo RETRY_INFO =
+ RetryInfo.newBuilder().setRetryDelay(Duration.newBuilder().setSeconds(213).build()).build();
+
+ private static final ImmutableList RAW_ERROR_MESSAGES =
+ ImmutableList.of(Any.pack(ERROR_INFO), Any.pack(RETRY_INFO));
+
+ private static final ErrorDetails ERROR_DETAILS =
+ ErrorDetails.builder().setRawErrorMessages(RAW_ERROR_MESSAGES).build();
+
+ private static final io.grpc.Status GRPC_STATUS = io.grpc.Status.CANCELLED;
+
+ private GrpcApiExceptionFactory factory;
+
+ @Before
+ public void setUp() throws Exception {
+ factory = new GrpcApiExceptionFactory(Collections.emptySet());
+ }
+
+ @Test
+ public void create_shouldCreateApiExceptionWithErrorDetailsForStatusException() {
+ Metadata trailers = new Metadata();
+ Status status = Status.newBuilder().addAllDetails(RAW_ERROR_MESSAGES).build();
+ trailers.put(
+ Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), status.toByteArray());
+ StatusException statusException = new StatusException(GRPC_STATUS, trailers);
+
+ ApiException actual = factory.create(statusException);
+
+ Truth.assertThat(actual.getErrorDetails()).isEqualTo(ERROR_DETAILS);
+ }
+
+ @Test
+ public void create_shouldCreateApiExceptionWithErrorDetailsForStatusRuntimeException() {
+ Metadata trailers = new Metadata();
+ Status status = Status.newBuilder().addAllDetails(RAW_ERROR_MESSAGES).build();
+ trailers.put(
+ Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), status.toByteArray());
+ StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, trailers);
+
+ ApiException actual = factory.create(statusException);
+
+ Truth.assertThat(actual.getErrorDetails()).isEqualTo(ERROR_DETAILS);
+ }
+
+ @Test
+ public void create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataIsNull() {
+ StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, null);
+
+ ApiException actual = factory.create(statusException);
+
+ Truth.assertThat(actual.getErrorDetails()).isNull();
+ }
+
+ @Test
+ public void create_shouldCreateApiExceptionWithNoErrorDetailsIfMetadataDoesNotHaveErrorDetails() {
+ StatusRuntimeException statusException =
+ new StatusRuntimeException(GRPC_STATUS, new Metadata());
+
+ ApiException actual = factory.create(statusException);
+
+ Truth.assertThat(actual.getErrorDetails()).isNull();
+ }
+
+ @Test
+ public void create_shouldCreateApiExceptionWithNoErrorDetailsIfStatusIsMalformed() {
+ Metadata trailers = new Metadata();
+ Status status = Status.newBuilder().addDetails(Any.pack(ERROR_INFO)).build();
+ byte[] bytes = status.toByteArray();
+ // manually manipulate status bytes array
+ bytes[0] = 123;
+ trailers.put(Metadata.Key.of(ERROR_DETAIL_KEY, Metadata.BINARY_BYTE_MARSHALLER), bytes);
+ StatusRuntimeException statusException = new StatusRuntimeException(GRPC_STATUS, trailers);
+
+ ApiException actual = factory.create(statusException);
+
+ Truth.assertThat(actual.getErrorDetails()).isNull();
+ }
+}
diff --git a/gax-httpjson/build.gradle b/gax-httpjson/build.gradle
index b52dee58f..d885140c5 100644
--- a/gax-httpjson/build.gradle
+++ b/gax-httpjson/build.gradle
@@ -1,7 +1,7 @@
archivesBaseName = 'gax-httpjson'
// TODO: Populate this from dependencies.properties version property (for proper Gradle-Bazel sync)
-project.version = "0.99.0" // {x-version-update:gax-httpjson:current}
+project.version = "0.100.0" // {x-version-update:gax-httpjson:current}
dependencies {
api(project(':gax'),
diff --git a/gax/BUILD.bazel b/gax/BUILD.bazel
index f35f3fc99..ede88dee4 100644
--- a/gax/BUILD.bazel
+++ b/gax/BUILD.bazel
@@ -9,6 +9,8 @@ _JAVA_COPTS = [
_COMPILE_DEPS = [
"@com_google_api_api_common//jar",
+ "@com_google_api_grpc_proto_google_common_protos//jar",
+ "@com_google_protobuf_java//jar",
"@com_google_auth_google_auth_library_credentials//jar",
"@com_google_auth_google_auth_library_oauth2_http//jar",
"@com_google_auto_value_auto_value//jar",
diff --git a/gax/build.gradle b/gax/build.gradle
index cdd5d63bd..1e2b4cd23 100644
--- a/gax/build.gradle
+++ b/gax/build.gradle
@@ -1,11 +1,12 @@
archivesBaseName = "gax"
// TODO: Populate this from dependencies.properties version property (for proper Gradle-Bazel sync)
-project.version = "2.14.0" // {x-version-update:gax:current}
+project.version = "2.15.0" // {x-version-update:gax:current}
dependencies {
api(libraries['maven.com_google_api_api_common'],
- libraries['maven.com_google_auth_google_auth_library_credentials'],
+ libraries['maven.com_google_api_grpc_proto_google_common_protos'],
+ libraries['maven.com_google_auth_google_auth_library_credentials'],
libraries['maven.org_threeten_threetenbp'])
implementation(libraries['maven.com_google_auth_google_auth_library_oauth2_http'],
diff --git a/gax/src/main/java/com/google/api/gax/rpc/AbortedException.java b/gax/src/main/java/com/google/api/gax/rpc/AbortedException.java
index 7549b8b6d..4cd2e31b6 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/AbortedException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/AbortedException.java
@@ -42,4 +42,9 @@ public AbortedException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public AbortedException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/AlreadyExistsException.java b/gax/src/main/java/com/google/api/gax/rpc/AlreadyExistsException.java
index 5a0c81c49..6ee118898 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/AlreadyExistsException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/AlreadyExistsException.java
@@ -42,4 +42,9 @@ public AlreadyExistsException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public AlreadyExistsException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiException.java b/gax/src/main/java/com/google/api/gax/rpc/ApiException.java
index a7d1f784a..3722b894e 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/ApiException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/ApiException.java
@@ -30,24 +30,34 @@
package com.google.api.gax.rpc;
import com.google.common.base.Preconditions;
+import java.util.Map;
/** Represents an exception thrown during an RPC call. */
public class ApiException extends RuntimeException {
+
private static final long serialVersionUID = -4375114339928877996L;
+ private final ErrorDetails errorDetails;
private final StatusCode statusCode;
private final boolean retryable;
public ApiException(Throwable cause, StatusCode statusCode, boolean retryable) {
- super(cause);
- this.statusCode = Preconditions.checkNotNull(statusCode);
- this.retryable = retryable;
+ this(cause, statusCode, retryable, null);
}
public ApiException(String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause);
this.statusCode = Preconditions.checkNotNull(statusCode);
this.retryable = retryable;
+ this.errorDetails = null;
+ }
+
+ public ApiException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause);
+ this.statusCode = Preconditions.checkNotNull(statusCode);
+ this.retryable = retryable;
+ this.errorDetails = errorDetails;
}
/** Returns whether the failed request can be retried. */
@@ -59,4 +69,43 @@ public boolean isRetryable() {
public StatusCode getStatusCode() {
return statusCode;
}
+
+ /**
+ * Returns the reason of the exception. This is a constant value that identifies the proximate
+ * cause of the error. e.g. SERVICE_DISABLED
+ */
+ public String getReason() {
+ if (isErrorInfoEmpty()) {
+ return null;
+ }
+ return errorDetails.getErrorInfo().getReason();
+ }
+
+ /**
+ * Returns the logical grouping to which the "reason" belongs. The error domain is typically the
+ * registered service name of the tool or product that generates the error. e.g. googleapis.com
+ */
+ public String getDomain() {
+ if (isErrorInfoEmpty()) {
+ return null;
+ }
+ return errorDetails.getErrorInfo().getDomain();
+ }
+
+ /** Returns additional structured details about this exception. */
+ public Map getMetadata() {
+ if (isErrorInfoEmpty()) {
+ return null;
+ }
+ return errorDetails.getErrorInfo().getMetadataMap();
+ }
+
+ /** Returns all standard error messages that server sends. */
+ public ErrorDetails getErrorDetails() {
+ return errorDetails;
+ }
+
+ private boolean isErrorInfoEmpty() {
+ return errorDetails == null || errorDetails.getErrorInfo() == null;
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiExceptionFactory.java b/gax/src/main/java/com/google/api/gax/rpc/ApiExceptionFactory.java
index 9820b751e..4ca5f41c7 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/ApiExceptionFactory.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/ApiExceptionFactory.java
@@ -38,81 +38,85 @@ private ApiExceptionFactory() {}
public static ApiException createException(
Throwable cause, StatusCode statusCode, boolean retryable) {
+ return createException(cause, statusCode, retryable, null);
+ }
+
+ public static ApiException createException(
+ String message, Throwable cause, StatusCode statusCode, boolean retryable) {
switch (statusCode.getCode()) {
case CANCELLED:
- return new CancelledException(cause, statusCode, retryable);
+ return new CancelledException(message, cause, statusCode, retryable);
case NOT_FOUND:
- return new NotFoundException(cause, statusCode, retryable);
+ return new NotFoundException(message, cause, statusCode, retryable);
case INVALID_ARGUMENT:
- return new InvalidArgumentException(cause, statusCode, retryable);
+ return new InvalidArgumentException(message, cause, statusCode, retryable);
case DEADLINE_EXCEEDED:
- return new DeadlineExceededException(cause, statusCode, retryable);
+ return new DeadlineExceededException(message, cause, statusCode, retryable);
case ALREADY_EXISTS:
- return new AlreadyExistsException(cause, statusCode, retryable);
+ return new AlreadyExistsException(message, cause, statusCode, retryable);
case PERMISSION_DENIED:
- return new PermissionDeniedException(cause, statusCode, retryable);
+ return new PermissionDeniedException(message, cause, statusCode, retryable);
case RESOURCE_EXHAUSTED:
- return new ResourceExhaustedException(cause, statusCode, retryable);
+ return new ResourceExhaustedException(message, cause, statusCode, retryable);
case FAILED_PRECONDITION:
- return new FailedPreconditionException(cause, statusCode, retryable);
+ return new FailedPreconditionException(message, cause, statusCode, retryable);
case ABORTED:
- return new AbortedException(cause, statusCode, retryable);
+ return new AbortedException(message, cause, statusCode, retryable);
case OUT_OF_RANGE:
- return new OutOfRangeException(cause, statusCode, retryable);
+ return new OutOfRangeException(message, cause, statusCode, retryable);
case UNIMPLEMENTED:
- return new UnimplementedException(cause, statusCode, retryable);
+ return new UnimplementedException(message, cause, statusCode, retryable);
case INTERNAL:
- return new InternalException(cause, statusCode, retryable);
+ return new InternalException(message, cause, statusCode, retryable);
case UNAVAILABLE:
- return new UnavailableException(cause, statusCode, retryable);
+ return new UnavailableException(message, cause, statusCode, retryable);
case DATA_LOSS:
- return new DataLossException(cause, statusCode, retryable);
+ return new DataLossException(message, cause, statusCode, retryable);
case UNAUTHENTICATED:
- return new UnauthenticatedException(cause, statusCode, retryable);
+ return new UnauthenticatedException(message, cause, statusCode, retryable);
case UNKNOWN: // Fall through.
default:
- return new UnknownException(cause, statusCode, retryable);
+ return new UnknownException(message, cause, statusCode, retryable);
}
}
public static ApiException createException(
- String message, Throwable cause, StatusCode statusCode, boolean retryable) {
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
switch (statusCode.getCode()) {
case CANCELLED:
- return new CancelledException(message, cause, statusCode, retryable);
+ return new CancelledException(cause, statusCode, retryable, errorDetails);
case NOT_FOUND:
- return new NotFoundException(message, cause, statusCode, retryable);
+ return new NotFoundException(cause, statusCode, retryable, errorDetails);
case INVALID_ARGUMENT:
- return new InvalidArgumentException(message, cause, statusCode, retryable);
+ return new InvalidArgumentException(cause, statusCode, retryable, errorDetails);
case DEADLINE_EXCEEDED:
- return new DeadlineExceededException(message, cause, statusCode, retryable);
+ return new DeadlineExceededException(cause, statusCode, retryable, errorDetails);
case ALREADY_EXISTS:
- return new AlreadyExistsException(message, cause, statusCode, retryable);
+ return new AlreadyExistsException(cause, statusCode, retryable, errorDetails);
case PERMISSION_DENIED:
- return new PermissionDeniedException(message, cause, statusCode, retryable);
+ return new PermissionDeniedException(cause, statusCode, retryable, errorDetails);
case RESOURCE_EXHAUSTED:
- return new ResourceExhaustedException(message, cause, statusCode, retryable);
+ return new ResourceExhaustedException(cause, statusCode, retryable, errorDetails);
case FAILED_PRECONDITION:
- return new FailedPreconditionException(message, cause, statusCode, retryable);
+ return new FailedPreconditionException(cause, statusCode, retryable, errorDetails);
case ABORTED:
- return new AbortedException(message, cause, statusCode, retryable);
+ return new AbortedException(cause, statusCode, retryable, errorDetails);
case OUT_OF_RANGE:
- return new OutOfRangeException(message, cause, statusCode, retryable);
+ return new OutOfRangeException(cause, statusCode, retryable, errorDetails);
case UNIMPLEMENTED:
- return new UnimplementedException(message, cause, statusCode, retryable);
+ return new UnimplementedException(cause, statusCode, retryable, errorDetails);
case INTERNAL:
- return new InternalException(message, cause, statusCode, retryable);
+ return new InternalException(cause, statusCode, retryable, errorDetails);
case UNAVAILABLE:
- return new UnavailableException(message, cause, statusCode, retryable);
+ return new UnavailableException(cause, statusCode, retryable, errorDetails);
case DATA_LOSS:
- return new DataLossException(message, cause, statusCode, retryable);
+ return new DataLossException(cause, statusCode, retryable, errorDetails);
case UNAUTHENTICATED:
- return new UnauthenticatedException(message, cause, statusCode, retryable);
-
+ return new UnauthenticatedException(cause, statusCode, retryable, errorDetails);
case UNKNOWN: // Fall through.
default:
- return new UnknownException(message, cause, statusCode, retryable);
+ return new UnknownException(cause, statusCode, retryable, errorDetails);
}
}
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/CancelledException.java b/gax/src/main/java/com/google/api/gax/rpc/CancelledException.java
index f0ea4c38a..7bc15f04c 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/CancelledException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/CancelledException.java
@@ -39,4 +39,9 @@ public CancelledException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public CancelledException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/DataLossException.java b/gax/src/main/java/com/google/api/gax/rpc/DataLossException.java
index 24abb294d..e3d9a8107 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/DataLossException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/DataLossException.java
@@ -39,4 +39,9 @@ public DataLossException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public DataLossException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/DeadlineExceededException.java b/gax/src/main/java/com/google/api/gax/rpc/DeadlineExceededException.java
index dfccaaaa4..37b5d8db0 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/DeadlineExceededException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/DeadlineExceededException.java
@@ -44,4 +44,9 @@ public DeadlineExceededException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public DeadlineExceededException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/ErrorDetails.java b/gax/src/main/java/com/google/api/gax/rpc/ErrorDetails.java
new file mode 100644
index 000000000..09c924b59
--- /dev/null
+++ b/gax/src/main/java/com/google/api/gax/rpc/ErrorDetails.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.rpc;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.Any;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Message;
+import com.google.rpc.BadRequest;
+import com.google.rpc.DebugInfo;
+import com.google.rpc.ErrorInfo;
+import com.google.rpc.Help;
+import com.google.rpc.LocalizedMessage;
+import com.google.rpc.PreconditionFailure;
+import com.google.rpc.QuotaFailure;
+import com.google.rpc.RequestInfo;
+import com.google.rpc.ResourceInfo;
+import com.google.rpc.RetryInfo;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** This class contains a list of standard error messages that returns from server. */
+@AutoValue
+public abstract class ErrorDetails {
+
+ /**
+ * This is the most important and special error message. It describes the cause of the error with
+ * structured details that both humans and applications can depend on.
+ */
+ @Nullable
+ public ErrorInfo getErrorInfo() {
+ return unpack(ErrorInfo.class);
+ }
+
+ /**
+ * Describes when the clients can retry a failed request. Clients could ignore the recommendation
+ * here or retry when this information is missing from error responses.
+ */
+ @Nullable
+ public RetryInfo getRetryInfo() {
+ return unpack(RetryInfo.class);
+ }
+
+ /** Describes additional debugging info. */
+ @Nullable
+ public DebugInfo getDebugInfo() {
+ return unpack(DebugInfo.class);
+ }
+
+ /** Describes how a quota check failed. */
+ @Nullable
+ public QuotaFailure getQuotaFailure() {
+ return unpack(QuotaFailure.class);
+ }
+
+ /** Describes what preconditions have failed. */
+ @Nullable
+ public PreconditionFailure getPreconditionFailure() {
+ return unpack(PreconditionFailure.class);
+ }
+
+ /**
+ * Describes violations in a client request. This error type focuses on the syntactic aspects of
+ * the request.
+ */
+ @Nullable
+ public BadRequest getBadRequest() {
+ return unpack(BadRequest.class);
+ }
+
+ /**
+ * Contains metadata about the request that clients can attach when filing a bug or providing
+ * other forms of feedback.
+ */
+ @Nullable
+ public RequestInfo getRequestInfo() {
+ return unpack(RequestInfo.class);
+ }
+
+ /** Describes the resource that is being accessed. */
+ @Nullable
+ public ResourceInfo getResourceInfo() {
+ return unpack(ResourceInfo.class);
+ }
+
+ /** Provides links to documentation or for performing an out-of-band action. */
+ @Nullable
+ public Help getHelp() {
+ return unpack(Help.class);
+ }
+
+ /**
+ * Provides a localized error message that is safe to return to the user which can be attached to
+ * an RPC error
+ */
+ @Nullable
+ public LocalizedMessage getLocalizedMessage() {
+ return unpack(LocalizedMessage.class);
+ }
+
+ /** This is a list of raw/unparsed error messages that returns from server. */
+ @Nullable
+ abstract List getRawErrorMessages();
+
+ public static Builder builder() {
+ return new AutoValue_ErrorDetails.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setRawErrorMessages(List rawErrorMessages);
+
+ public abstract ErrorDetails build();
+ }
+
+ @VisibleForTesting
+ T unpack(Class errorTypeClazz) {
+ List rawErrorMessages = getRawErrorMessages();
+ if (rawErrorMessages == null) {
+ return null;
+ }
+ for (Any detail : rawErrorMessages) {
+ if (!detail.is(errorTypeClazz)) {
+ continue;
+ }
+ try {
+ return detail.unpack(errorTypeClazz);
+ } catch (InvalidProtocolBufferException e) {
+ throw new ProtocolBufferParsingException(
+ String.format(
+ "Failed to unpack %s from raw error messages", errorTypeClazz.getSimpleName()),
+ e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/FailedPreconditionException.java b/gax/src/main/java/com/google/api/gax/rpc/FailedPreconditionException.java
index 3cb05d8ff..e1b3ec75e 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/FailedPreconditionException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/FailedPreconditionException.java
@@ -43,4 +43,9 @@ public FailedPreconditionException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public FailedPreconditionException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/InternalException.java b/gax/src/main/java/com/google/api/gax/rpc/InternalException.java
index cbe81aeec..3d5763f06 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/InternalException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/InternalException.java
@@ -42,4 +42,9 @@ public InternalException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public InternalException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/InvalidArgumentException.java b/gax/src/main/java/com/google/api/gax/rpc/InvalidArgumentException.java
index d21d4b0ed..6b0ccc6f6 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/InvalidArgumentException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/InvalidArgumentException.java
@@ -43,4 +43,9 @@ public InvalidArgumentException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public InvalidArgumentException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/NotFoundException.java b/gax/src/main/java/com/google/api/gax/rpc/NotFoundException.java
index 3a705ce72..ce8b0b1d7 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/NotFoundException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/NotFoundException.java
@@ -39,4 +39,9 @@ public NotFoundException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public NotFoundException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/OutOfRangeException.java b/gax/src/main/java/com/google/api/gax/rpc/OutOfRangeException.java
index 9a44f78ef..33ecb38ca 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/OutOfRangeException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/OutOfRangeException.java
@@ -42,4 +42,9 @@ public OutOfRangeException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public OutOfRangeException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/PermissionDeniedException.java b/gax/src/main/java/com/google/api/gax/rpc/PermissionDeniedException.java
index 8600c1599..c9e751597 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/PermissionDeniedException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/PermissionDeniedException.java
@@ -39,4 +39,9 @@ public PermissionDeniedException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public PermissionDeniedException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/ProtocolBufferParsingException.java b/gax/src/main/java/com/google/api/gax/rpc/ProtocolBufferParsingException.java
new file mode 100644
index 000000000..ce8f306a2
--- /dev/null
+++ b/gax/src/main/java/com/google/api/gax/rpc/ProtocolBufferParsingException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.rpc;
+
+/** Exception thrown when parsing protocol buffer message failed */
+public class ProtocolBufferParsingException extends RuntimeException {
+
+ public ProtocolBufferParsingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/ResourceExhaustedException.java b/gax/src/main/java/com/google/api/gax/rpc/ResourceExhaustedException.java
index 2de1516f9..2561c54ce 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/ResourceExhaustedException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/ResourceExhaustedException.java
@@ -42,4 +42,9 @@ public ResourceExhaustedException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public ResourceExhaustedException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/UnauthenticatedException.java b/gax/src/main/java/com/google/api/gax/rpc/UnauthenticatedException.java
index 77f1f4780..6daeece5a 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/UnauthenticatedException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/UnauthenticatedException.java
@@ -42,4 +42,9 @@ public UnauthenticatedException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public UnauthenticatedException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/UnavailableException.java b/gax/src/main/java/com/google/api/gax/rpc/UnavailableException.java
index 6ca890906..96fbc8d28 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/UnavailableException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/UnavailableException.java
@@ -42,4 +42,9 @@ public UnavailableException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public UnavailableException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/UnimplementedException.java b/gax/src/main/java/com/google/api/gax/rpc/UnimplementedException.java
index 13769c940..8e042f441 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/UnimplementedException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/UnimplementedException.java
@@ -41,4 +41,9 @@ public UnimplementedException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public UnimplementedException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/java/com/google/api/gax/rpc/UnknownException.java b/gax/src/main/java/com/google/api/gax/rpc/UnknownException.java
index 29d0502ed..a3ebe3599 100644
--- a/gax/src/main/java/com/google/api/gax/rpc/UnknownException.java
+++ b/gax/src/main/java/com/google/api/gax/rpc/UnknownException.java
@@ -44,4 +44,9 @@ public UnknownException(
String message, Throwable cause, StatusCode statusCode, boolean retryable) {
super(message, cause, statusCode, retryable);
}
+
+ public UnknownException(
+ Throwable cause, StatusCode statusCode, boolean retryable, ErrorDetails errorDetails) {
+ super(cause, statusCode, retryable, errorDetails);
+ }
}
diff --git a/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties b/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties
new file mode 100644
index 000000000..f5fbadc5c
--- /dev/null
+++ b/gax/src/main/resources/META-INF/native-image/com.google.api/gax/native-image.properties
@@ -0,0 +1,5 @@
+Args = --allow-incomplete-classpath \
+--enable-url-protocols=https,http \
+--initialize-at-build-time=org.conscrypt,\
+ org.slf4j.LoggerFactory,\
+ org.junit.platform.engine.TestTag
\ No newline at end of file
diff --git a/gax/src/test/java/com/google/api/gax/rpc/ApiExceptionTest.java b/gax/src/test/java/com/google/api/gax/rpc/ApiExceptionTest.java
new file mode 100644
index 000000000..85bf2d9ec
--- /dev/null
+++ b/gax/src/test/java/com/google/api/gax/rpc/ApiExceptionTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.rpc;
+
+import com.google.api.gax.rpc.StatusCode.Code;
+import com.google.api.gax.rpc.testing.FakeStatusCode;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Truth;
+import com.google.protobuf.Any;
+import com.google.rpc.ErrorInfo;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApiExceptionTest {
+
+ private static final ErrorDetails ERROR_DETAILS_WITH_NO_ERROR_INFO =
+ ErrorDetails.builder().setRawErrorMessages(Collections.emptyList()).build();
+ private static final String DOMAIN = "googleapis.com";
+ private static final String REASON = "SERVICE_DISABLED";
+ private static final String METADATA_KEY = "service";
+ private static final String METADATA_VALUE = "language.googleapis.com";
+ private static final ErrorDetails ERROR_DETAILS_WITH_ERROR_INFO =
+ ErrorDetails.builder()
+ .setRawErrorMessages(
+ ImmutableList.of(
+ Any.pack(
+ ErrorInfo.newBuilder()
+ .setDomain(DOMAIN)
+ .setReason(REASON)
+ .putMetadata(METADATA_KEY, METADATA_VALUE)
+ .build())))
+ .build();
+ private static final FakeStatusCode STATUS_CODE = FakeStatusCode.of(Code.UNAVAILABLE);
+
+ private ApiException apiException;
+
+ @Test
+ public void getReason_shouldReturnNullIfErrorDetailsIsNull() {
+ apiException = new ApiException(null, STATUS_CODE, false, null);
+
+ Truth.assertThat(apiException.getReason()).isNull();
+ }
+
+ @Test
+ public void getReason_shouldReturnNullIfErrorInfoIsNull() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_NO_ERROR_INFO);
+
+ Truth.assertThat(apiException.getReason()).isNull();
+ }
+
+ @Test
+ public void getReason_shouldReturnReasonIfAvailable() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_ERROR_INFO);
+
+ Truth.assertThat(apiException.getReason()).isEqualTo(REASON);
+ }
+
+ @Test
+ public void getDomain_shouldReturnNullIfErrorInfoIsNull() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_NO_ERROR_INFO);
+
+ Truth.assertThat(apiException.getDomain()).isNull();
+ }
+
+ @Test
+ public void getDomain_shouldReturnDomainIfAvailable() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_ERROR_INFO);
+
+ Truth.assertThat(apiException.getDomain()).isEqualTo(DOMAIN);
+ }
+
+ @Test
+ public void getMetadata_shouldReturnNullIfErrorInfoIsNull() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_NO_ERROR_INFO);
+
+ Truth.assertThat(apiException.getMetadata()).isNull();
+ }
+
+ @Test
+ public void getMetadata_shouldReturnMetadataIfAvailable() {
+ apiException = new ApiException(null, STATUS_CODE, false, ERROR_DETAILS_WITH_ERROR_INFO);
+
+ Truth.assertThat(apiException.getMetadata()).containsExactly(METADATA_KEY, METADATA_VALUE);
+ }
+}
diff --git a/gax/src/test/java/com/google/api/gax/rpc/ErrorDetailsTest.java b/gax/src/test/java/com/google/api/gax/rpc/ErrorDetailsTest.java
new file mode 100644
index 000000000..8edef41bf
--- /dev/null
+++ b/gax/src/test/java/com/google/api/gax/rpc/ErrorDetailsTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.api.gax.rpc;
+
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Truth;
+import com.google.protobuf.Any;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Duration;
+import com.google.rpc.BadRequest;
+import com.google.rpc.BadRequest.FieldViolation;
+import com.google.rpc.DebugInfo;
+import com.google.rpc.ErrorInfo;
+import com.google.rpc.Help;
+import com.google.rpc.Help.Link;
+import com.google.rpc.LocalizedMessage;
+import com.google.rpc.PreconditionFailure;
+import com.google.rpc.QuotaFailure;
+import com.google.rpc.QuotaFailure.Violation;
+import com.google.rpc.RequestInfo;
+import com.google.rpc.ResourceInfo;
+import com.google.rpc.RetryInfo;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ErrorDetailsTest {
+
+ private static final ErrorInfo ERROR_INFO =
+ ErrorInfo.newBuilder()
+ .setDomain("googleapis.com")
+ .setReason("SERVICE_DISABLED")
+ .putAllMetadata(Collections.emptyMap())
+ .build();
+
+ private static final RetryInfo RETRY_INFO =
+ RetryInfo.newBuilder().setRetryDelay(Duration.newBuilder().setSeconds(213).build()).build();
+
+ private static final DebugInfo DEBUG_INFO =
+ DebugInfo.newBuilder()
+ .setDetail("No more details available")
+ .addStackEntries("Does not matter")
+ .build();
+
+ private static final QuotaFailure QUOTA_FAILURE =
+ QuotaFailure.newBuilder()
+ .addViolations(
+ Violation.newBuilder()
+ .setDescription("new violation")
+ .setSubject("This is a breaking news")
+ .build())
+ .build();
+
+ private static final PreconditionFailure PRECONDITION_FAILURE =
+ PreconditionFailure.newBuilder()
+ .addViolations(
+ PreconditionFailure.Violation.newBuilder()
+ .setDescription("new violation")
+ .setSubject("This is a breaking news")
+ .setType("Unknown")
+ .build())
+ .build();
+
+ private static final BadRequest BAD_REQUEST =
+ BadRequest.newBuilder()
+ .addFieldViolations(
+ FieldViolation.newBuilder()
+ .setDescription("new field violation")
+ .setField("unknown field")
+ .build())
+ .build();
+
+ private static final RequestInfo REQUEST_INFO =
+ RequestInfo.newBuilder()
+ .setRequestId("ukajsdkansdk123")
+ .setServingData("no data available")
+ .build();
+
+ private static final ResourceInfo RESOURCE_INFO =
+ ResourceInfo.newBuilder()
+ .setDescription("not available")
+ .setResourceName("my resource")
+ .setResourceType("mystery")
+ .setOwner("myself")
+ .build();
+
+ private static final Help HELP =
+ Help.newBuilder()
+ .addLinks(Link.newBuilder().setDescription("new link").setUrl("https://abc.com").build())
+ .build();
+
+ private static final LocalizedMessage LOCALIZED_MESSAGE =
+ LocalizedMessage.newBuilder().setLocale("en").setMessage("nothing").build();
+
+ ErrorDetails errorDetails;
+
+ @Before
+ public void setUp() throws Exception {
+ ImmutableList rawErrorMessages =
+ ImmutableList.of(
+ Any.pack(ERROR_INFO),
+ Any.pack(RETRY_INFO),
+ Any.pack(DEBUG_INFO),
+ Any.pack(QUOTA_FAILURE),
+ Any.pack(PRECONDITION_FAILURE),
+ Any.pack(BAD_REQUEST),
+ Any.pack(REQUEST_INFO),
+ Any.pack(RESOURCE_INFO),
+ Any.pack(HELP),
+ Any.pack(LOCALIZED_MESSAGE));
+
+ errorDetails = ErrorDetails.builder().setRawErrorMessages(rawErrorMessages).build();
+ }
+
+ @Test
+ public void unpack_shouldReturnNullIfRawErrorMessagesIsNull() {
+ errorDetails = ErrorDetails.builder().setRawErrorMessages(null).build();
+
+ Truth.assertThat(errorDetails.unpack(ErrorInfo.class)).isNull();
+ }
+
+ @Test
+ public void unpack_shouldReturnNullIfErrorMessageTypeDoesNotExist() {
+ errorDetails =
+ ErrorDetails.builder().setRawErrorMessages(ImmutableList.of(Any.pack(ERROR_INFO))).build();
+
+ Truth.assertThat(errorDetails.unpack(DebugInfo.class)).isNull();
+ }
+
+ @Test
+ public void unpack_shouldThrowExceptionIfUnpackingErrorMassageFailed() {
+ Any malformedErrorType =
+ Any.newBuilder()
+ .setTypeUrl("type.googleapis.com/google.rpc.ErrorInfo")
+ .setValue(ByteString.copyFromUtf8("This is an invalid message!"))
+ .build();
+ errorDetails =
+ ErrorDetails.builder().setRawErrorMessages(ImmutableList.of(malformedErrorType)).build();
+ ProtocolBufferParsingException exception =
+ assertThrows(
+ ProtocolBufferParsingException.class, () -> errorDetails.unpack(ErrorInfo.class));
+ Truth.assertThat(exception.getMessage())
+ .isEqualTo(
+ String.format(
+ "Failed to unpack %s from raw error messages", ErrorInfo.class.getSimpleName()));
+ }
+
+ @Test
+ public void unpack_shouldReturnDesiredErrorMessageTypeIfItExist() {
+ Truth.assertThat(errorDetails.unpack(ErrorInfo.class)).isEqualTo(ERROR_INFO);
+ }
+
+ @Test
+ public void errorInfo_shouldUnpackErrorInfoProtoMessage() {
+ Truth.assertThat(errorDetails.getErrorInfo()).isEqualTo(ERROR_INFO);
+ }
+
+ @Test
+ public void retryInfo_shouldUnpackRetryInfoProtoMessage() {
+ Truth.assertThat(errorDetails.getRetryInfo()).isEqualTo(RETRY_INFO);
+ }
+
+ @Test
+ public void debugInfo_shouldUnpackDebugInfoProtoMessage() {
+ Truth.assertThat(errorDetails.getDebugInfo()).isEqualTo(DEBUG_INFO);
+ }
+
+ @Test
+ public void quotaFailure_shouldUnpackQuotaFailureProtoMessage() {
+ Truth.assertThat(errorDetails.getQuotaFailure()).isEqualTo(QUOTA_FAILURE);
+ }
+
+ @Test
+ public void preconditionFailure_shouldUnpackPreconditionFailureProtoMessage() {
+ Truth.assertThat(errorDetails.getPreconditionFailure()).isEqualTo(PRECONDITION_FAILURE);
+ }
+
+ @Test
+ public void badRequest_shouldUnpackBadRequestProtoMessage() {
+ Truth.assertThat(errorDetails.getBadRequest()).isEqualTo(BAD_REQUEST);
+ }
+
+ @Test
+ public void requestInfo_shouldUnpackRequestInfoProtoMessage() {
+ Truth.assertThat(errorDetails.getRequestInfo()).isEqualTo(REQUEST_INFO);
+ }
+
+ @Test
+ public void resourceInfo_shouldUnpackResourceInfoProtoMessage() {
+ Truth.assertThat(errorDetails.getResourceInfo()).isEqualTo(RESOURCE_INFO);
+ }
+
+ @Test
+ public void help_shouldUnpackHelpProtoMessage() {
+ Truth.assertThat(errorDetails.getHelp()).isEqualTo(HELP);
+ }
+
+ @Test
+ public void localizedMessage_shouldUnpackLocalizedMessageProtoMessage() {
+ Truth.assertThat(errorDetails.getHelp()).isEqualTo(HELP);
+ }
+}
diff --git a/repositories.bzl b/repositories.bzl
index 2896b375c..4231c5790 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -80,6 +80,14 @@ def com_google_api_gax_java_repositories():
licenses = ["notice", "reciprocal"],
)
+ _maybe(
+ jvm_maven_import_external,
+ name = "com_google_protobuf_java",
+ artifact = "com.google.protobuf:protobuf-java:%s" % PROPERTIES["version.com_google_protobuf"],
+ server_urls = ["https://repo.maven.apache.org/maven2/", "http://repo1.maven.org/maven2/"],
+ licenses = ["notice", "reciprocal"],
+ )
+
_maybe(
jvm_maven_import_external,
name = "io_grpc_grpc_netty_shaded",
diff --git a/versions.txt b/versions.txt
index 41301da08..01180a8d5 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,7 +1,7 @@
# Format:
# module:released-version:current-version
-gax:2.14.0:2.14.0
-gax-bom:2.14.0:2.14.0
-gax-grpc:2.14.0:2.14.0
-gax-httpjson:0.99.0:0.99.0
+gax:2.15.0:2.15.0
+gax-bom:2.15.0:2.15.0
+gax-grpc:2.15.0:2.15.0
+gax-httpjson:0.100.0:0.100.0