Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -76,9 +78,9 @@ public Message getGoogleAdsFailure() {
* Optionally create a GoogleAdsException from a ApiException.
*
* <p>Returns an Optional containing the underlying GoogleAdsException if the ApiException
* contains the appropriate metadata.
* contains the appropriate metadata or status details.
*
* <p>Returns an empty Optional if the required metadata is not present or is not parsable.
* <p>Returns an empty Optional if the required metadata or status details is not present or is not parsable.
*/
public abstract static class Factory<T extends BaseGoogleAdsException, U extends Message> {
protected static Metadata.Key<byte[]> createKey(String trailerKey) {
Expand All @@ -94,15 +96,36 @@ public Optional<T> 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;
}
}
Comment on lines +109 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be replaced with usage of T com.google.api.gax.rpc.ErrorDetails.getMessage(Class messageClass)? E.g.,

com.google.api.gax.rpc.ErrorDetails.builder()
  .setRawErrorMessages(statusProto.getDetailsList())
  .build()
  .getMessage(createGoogleAdsFailure().getClass());

}
}
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down