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()) {