diff --git a/google-ads-stubs-lib/src/main/java/com/google/ads/googleads/lib/stubs/exceptions/BaseGoogleAdsException.java b/google-ads-stubs-lib/src/main/java/com/google/ads/googleads/lib/stubs/exceptions/BaseGoogleAdsException.java
index 657a38d0f5..1ac772dc21 100644
--- a/google-ads-stubs-lib/src/main/java/com/google/ads/googleads/lib/stubs/exceptions/BaseGoogleAdsException.java
+++ b/google-ads-stubs-lib/src/main/java/com/google/ads/googleads/lib/stubs/exceptions/BaseGoogleAdsException.java
@@ -16,10 +16,12 @@
import com.google.api.gax.rpc.ApiException;
import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.grpc.Metadata;
import io.grpc.Status;
+import io.grpc.protobuf.StatusProto;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,9 +78,9 @@ public Message getGoogleAdsFailure() {
* Optionally create a GoogleAdsException from a ApiException.
*
*
Returns an Optional containing the underlying GoogleAdsException if the ApiException
- * contains the appropriate metadata.
+ * contains the appropriate metadata or status details.
*
- *
Returns an empty Optional if the required metadata is not present or is not parsable.
+ *
Returns an empty Optional if the required metadata or status details is not present or is not parsable.
*/
public abstract static class Factory {
protected static Metadata.Key createKey(String trailerKey) {
@@ -94,15 +96,36 @@ public Optional createGoogleAdsException(ApiException source) {
return Optional.empty();
}
Metadata metadata = Status.trailersFromThrowable(cause);
- if (metadata == null) {
- return Optional.empty();
+ byte[] protoData = null;
+ if (metadata != null) {
+ protoData = metadata.get(getTrailerKey());
+ }
+
+ // If the GoogleAdsFailure was not found in the metadata, checks if it is in the status
+ // details.
+ if (protoData == null) {
+ com.google.rpc.Status statusProto = StatusProto.fromThrowable(cause);
+ if (statusProto != null) {
+ String expectedTypeUrl =
+ "type.googleapis.com/" + createGoogleAdsFailure().getDescriptorForType().getFullName();
+ for (Any any : statusProto.getDetailsList()) {
+ if (any.getTypeUrl().equals(expectedTypeUrl)) {
+ protoData = any.getValue().toByteArray();
+ break;
+ }
+ }
+ }
}
- byte[] protoData = metadata.get(getTrailerKey());
+
if (protoData == null) {
return Optional.empty();
}
+
try {
- return Optional.of(createException(source, protoData, metadata));
+ // The original implementation requires metadata to be non-null, but it might be if we
+ // extracted from status details instead of trailers.
+ Metadata nonNullMetadata = metadata == null ? new Metadata() : metadata;
+ return Optional.of(createException(source, protoData, nonNullMetadata));
} catch (InvalidProtocolBufferException e) {
logger.error("Failed to decode GoogleAdsFailure", e);
return Optional.empty();
diff --git a/google-ads/src/test/java/com/google/ads/googleads/lib/GoogleAdsExceptionTransformationTest.java b/google-ads/src/test/java/com/google/ads/googleads/lib/GoogleAdsExceptionTransformationTest.java
index 37d08c07c6..6683cdeb28 100644
--- a/google-ads/src/test/java/com/google/ads/googleads/lib/GoogleAdsExceptionTransformationTest.java
+++ b/google-ads/src/test/java/com/google/ads/googleads/lib/GoogleAdsExceptionTransformationTest.java
@@ -22,11 +22,15 @@
import com.google.ads.googleads.lib.stubs.exceptions.BaseGoogleAdsException;
import com.google.api.gax.grpc.GrpcStatusCode;
import com.google.api.gax.rpc.ApiException;
+import com.google.protobuf.Any;
import com.google.protobuf.Message;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.ProtoUtils;
+import io.grpc.protobuf.StatusProto;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -77,6 +81,38 @@ public void handlesEmptyTrailers() {
}
}
+ @Test
+ public void handlesFailureInRpcStatusButNotInTrailers() {
+ for (Version version : catalog.getSupportedVersions()) {
+ // Creates a GoogleAdsFailure instance.
+ Message googleAdsFailure = version.getExceptionFactory().createGoogleAdsFailure();
+
+ // Creates a com.google.rpc.Status proto with the GoogleAdsFailure in the details field.
+ com.google.rpc.Status rpcStatus =
+ com.google.rpc.Status.newBuilder()
+ .setCode(com.google.rpc.Code.INVALID_ARGUMENT.getNumber())
+ .addDetails(Any.pack(googleAdsFailure))
+ .build();
+
+ // Creates a StatusRuntimeException that will be the cause of the ApiException.
+ StatusRuntimeException statusRuntimeException = StatusProto.toStatusRuntimeException(rpcStatus);
+
+ // Creates an ApiException that contains the metadata.
+ ApiException exception =
+ new ApiException(
+ statusRuntimeException,
+ GrpcStatusCode.of(io.grpc.Status.Code.INVALID_ARGUMENT),
+ false);
+
+ // Transforms the exception.
+ Throwable result = transformation.transform(exception);
+ // Retrieves the GoogleAdsFailure from the transformed exception.
+ Message actualFailure = ((BaseGoogleAdsException) result).getGoogleAdsFailure();
+ // Asserts that the expected and actual GoogleAdsFailure protos are the same.
+ assertEquals(googleAdsFailure, actualFailure);
+ }
+ }
+
@Test
public void failsGracefullyWithUnparsableFailureProto() {
for (Version version : catalog.getSupportedVersions()) {