diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/errorhandling/HandlePartialFailure.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/errorhandling/HandlePartialFailure.java index 7d2e350780..2db2aaf2d6 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/errorhandling/HandlePartialFailure.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/errorhandling/HandlePartialFailure.java @@ -22,6 +22,7 @@ import com.google.ads.googleads.lib.GoogleAdsClient; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.resources.AdGroup; import com.google.ads.googleads.v8.services.AdGroupOperation; import com.google.ads.googleads.v8.services.AdGroupServiceClient; @@ -30,7 +31,6 @@ import com.google.ads.googleads.v8.services.MutateAdGroupsResponse; import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.ads.googleads.v8.utils.ResourceNames; -import com.google.protobuf.InvalidProtocolBufferException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -57,7 +57,7 @@ private static class HandlePartialFailureParams extends CodeSampleParams { private Long campaignId; } - public static void main(String[] args) throws InvalidProtocolBufferException { + public static void main(String[] args) { HandlePartialFailureParams params = new HandlePartialFailureParams(); if (!params.parseArguments(args)) { // Either pass the required parameters for this example on the command line, or insert them @@ -96,8 +96,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException { } /** Runs the example. */ - public void runExample(GoogleAdsClient googleAdsClient, long customerId, long campaignId) - throws InvalidProtocolBufferException { + public void runExample(GoogleAdsClient googleAdsClient, long customerId, long campaignId) { MutateAdGroupsResponse response = createAdGroups(googleAdsClient, customerId, campaignId); // Checks for existence of any partial failures in the response. @@ -165,15 +164,18 @@ private boolean checkIfPartialFailureErrorExists(MutateAdGroupsResponse response /** Displays the result from the mutate operation. */ // [START handle_partial_failure_2] - private void printResults(MutateAdGroupsResponse response) throws InvalidProtocolBufferException { + private void printResults(MutateAdGroupsResponse response) { int operationIndex = 0; for (MutateAdGroupResult result : response.getResultsList()) { if (ErrorUtils.getInstance().isPartialFailureResult(result)) { // May throw on this line. Most likely this means the wrong version of the ErrorUtils // class has been used. + GoogleAdsFailure googleAdsFailure = ErrorUtils.getInstance() + .getGoogleAdsFailure(response.getPartialFailureError()); + for (GoogleAdsError error : ErrorUtils.getInstance() - .getGoogleAdsErrors(operationIndex, response.getPartialFailureError())) { + .getGoogleAdsErrors(operationIndex, googleAdsFailure)) { System.out.printf("Operation %d failed with error: %s%n", operationIndex, error); } } else { diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java index bd8d8e609e..967351df62 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java @@ -30,6 +30,7 @@ import com.google.ads.googleads.v8.enums.OfflineUserDataJobTypeEnum.OfflineUserDataJobType; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.resources.OfflineUserDataJob; import com.google.ads.googleads.v8.resources.UserList; import com.google.ads.googleads.v8.services.AddOfflineUserDataJobOperationsRequest; @@ -44,6 +45,7 @@ import com.google.ads.googleads.v8.services.SearchGoogleAdsStreamResponse; import com.google.ads.googleads.v8.services.UserListOperation; import com.google.ads.googleads.v8.services.UserListServiceClient; +import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.api.gax.rpc.ServerStream; import com.google.common.collect.ImmutableList; import java.io.FileNotFoundException; @@ -219,11 +221,13 @@ private void addUsersToCustomerMatchUserList( // NOTE: The details of each partial failure error are not printed here, you can refer to // the example HandlePartialFailure.java to learn more. if (response.hasPartialFailureError()) { + GoogleAdsFailure googleAdsFailure = + ErrorUtils.getInstance().getGoogleAdsFailure(response.getPartialFailureError()); System.out.printf( "Encountered %d partial failure errors while adding %d operations to the offline user " + "data job: '%s'. Only the successfully added operations will be executed when " + "the job runs.%n", - response.getPartialFailureError().getDetailsCount(), + googleAdsFailure.getErrorsCount(), userDataJobOperations.size(), response.getPartialFailureError().getMessage()); } else { diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadCallConversion.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadCallConversion.java index 5a1b9d38fb..daec66a49a 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadCallConversion.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadCallConversion.java @@ -20,12 +20,14 @@ import com.google.ads.googleads.lib.GoogleAdsClient; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.services.CallConversion; import com.google.ads.googleads.v8.services.CallConversionResult; import com.google.ads.googleads.v8.services.ConversionUploadServiceClient; import com.google.ads.googleads.v8.services.CustomVariable; import com.google.ads.googleads.v8.services.UploadCallConversionsRequest; import com.google.ads.googleads.v8.services.UploadCallConversionsResponse; +import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.ads.googleads.v8.utils.ResourceNames; import java.io.FileNotFoundException; import java.io.IOException; @@ -185,6 +187,11 @@ private void runExample( // Prints any partial failure errors returned. if (response.hasPartialFailureError()) { + GoogleAdsFailure googleAdsFailure = + ErrorUtils.getInstance().getGoogleAdsFailure(response.getPartialFailureError()); + googleAdsFailure + .getErrorsList() + .forEach(e -> System.out.println("Partial failure occurred: " + e.getMessage())); throw new RuntimeException( "Partial failure occurred " + response.getPartialFailureError().getMessage()); } diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadConversionAdjustment.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadConversionAdjustment.java index d0dc90f3d5..993fc7033f 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadConversionAdjustment.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadConversionAdjustment.java @@ -21,6 +21,7 @@ import com.google.ads.googleads.v8.enums.ConversionAdjustmentTypeEnum.ConversionAdjustmentType; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.services.ConversionAdjustment; import com.google.ads.googleads.v8.services.ConversionAdjustmentResult; import com.google.ads.googleads.v8.services.ConversionAdjustmentUploadServiceClient; @@ -28,6 +29,7 @@ import com.google.ads.googleads.v8.services.RestatementValue; import com.google.ads.googleads.v8.services.UploadConversionAdjustmentsRequest; import com.google.ads.googleads.v8.services.UploadConversionAdjustmentsResponse; +import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.ads.googleads.v8.utils.ResourceNames; import java.io.FileNotFoundException; import java.io.IOException; @@ -203,8 +205,11 @@ private void runExample( // Prints any partial errors returned. if (response.hasPartialFailureError()) { - System.out.printf( - "Partial error encountered: '%s'.%n", response.getPartialFailureError().getMessage()); + GoogleAdsFailure googleAdsFailure = + ErrorUtils.getInstance().getGoogleAdsFailure(response.getPartialFailureError()); + googleAdsFailure + .getErrorsList() + .forEach(e -> System.out.println("Partial failure occurred: " + e.getMessage())); } else { // Prints the result. ConversionAdjustmentResult result = response.getResults(0); diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadOfflineConversion.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadOfflineConversion.java index 534da23b7f..150bc41a6d 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadOfflineConversion.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadOfflineConversion.java @@ -20,12 +20,14 @@ import com.google.ads.googleads.lib.GoogleAdsClient; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.services.ClickConversion; import com.google.ads.googleads.v8.services.ClickConversionResult; import com.google.ads.googleads.v8.services.ConversionUploadServiceClient; import com.google.ads.googleads.v8.services.CustomVariable; import com.google.ads.googleads.v8.services.UploadClickConversionsRequest; import com.google.ads.googleads.v8.services.UploadClickConversionsResponse; +import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.ads.googleads.v8.utils.ResourceNames; import java.io.FileNotFoundException; import java.io.IOException; @@ -182,8 +184,11 @@ private void runExample( // Prints any partial errors returned. if (response.hasPartialFailureError()) { - System.out.printf( - "Partial error encountered: '%s'.%n", response.getPartialFailureError().getMessage()); + GoogleAdsFailure googleAdsFailure = + ErrorUtils.getInstance().getGoogleAdsFailure(response.getPartialFailureError()); + googleAdsFailure + .getErrorsList() + .forEach(e -> System.out.println("Partial failure occurred: " + e.getMessage())); } // Prints the result. diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java index 855c84f309..aaee8be216 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java @@ -28,6 +28,7 @@ import com.google.ads.googleads.v8.enums.OfflineUserDataJobTypeEnum.OfflineUserDataJobType; import com.google.ads.googleads.v8.errors.GoogleAdsError; import com.google.ads.googleads.v8.errors.GoogleAdsException; +import com.google.ads.googleads.v8.errors.GoogleAdsFailure; import com.google.ads.googleads.v8.resources.OfflineUserDataJob; import com.google.ads.googleads.v8.services.AddOfflineUserDataJobOperationsRequest; import com.google.ads.googleads.v8.services.AddOfflineUserDataJobOperationsResponse; @@ -36,6 +37,7 @@ import com.google.ads.googleads.v8.services.GoogleAdsServiceClient; import com.google.ads.googleads.v8.services.OfflineUserDataJobOperation; import com.google.ads.googleads.v8.services.OfflineUserDataJobServiceClient; +import com.google.ads.googleads.v8.utils.ErrorUtils; import com.google.ads.googleads.v8.utils.ResourceNames; import com.google.common.collect.ImmutableList; import java.io.FileNotFoundException; @@ -469,11 +471,16 @@ private void addTransactionsToOfflineUserDataJob( // NOTE: The details of each partial failure error are not printed here, you can refer to // the example HandlePartialFailure.java to learn more. if (response.hasPartialFailureError()) { + GoogleAdsFailure googleAdsFailure = + ErrorUtils.getInstance().getGoogleAdsFailure(response.getPartialFailureError()); + googleAdsFailure + .getErrorsList() + .forEach(e -> System.out.println("Partial failure occurred: " + e.getMessage())); System.out.printf( "Encountered %d partial failure errors while adding %d operations to the offline user " + "data job: '%s'. Only the successfully added operations will be executed when " + "the job runs.%n", - response.getPartialFailureError().getDetailsCount(), + ErrorUtils.getInstance().getFailedOperationIndices(googleAdsFailure).size(), userDataJobOperations.size(), response.getPartialFailureError().getMessage()); } else { diff --git a/google-ads/build.gradle b/google-ads/build.gradle index 65e6824842..e5961fd94e 100644 --- a/google-ads/build.gradle +++ b/google-ads/build.gradle @@ -49,6 +49,7 @@ sourceSets { runtimeClasspath += sourceSets.main.output } } +sourceSets.test.java.srcDir new File(buildDir, 'generated/source/proto/test') protobuf { protoc { diff --git a/google-ads/src/main/java/com/google/ads/googleads/lib/utils/AbstractErrorUtils.java b/google-ads/src/main/java/com/google/ads/googleads/lib/utils/AbstractErrorUtils.java index e1dab0c56c..66ed48cf0a 100644 --- a/google-ads/src/main/java/com/google/ads/googleads/lib/utils/AbstractErrorUtils.java +++ b/google-ads/src/main/java/com/google/ads/googleads/lib/utils/AbstractErrorUtils.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** Contains utility methods for handling partial failure of operations. */ public abstract class AbstractErrorUtils< @@ -77,9 +79,9 @@ public abstract class AbstractErrorUtils< * empty list otherwise. * *

This method supports XXXService.mutate(request) where the request contains a - * list of operations named "operations". It also supports - * GoogleAdsService.mutateGoogleAds(request), where the request contains a list of - * MutateOperations named "mutate_operations". + * list of operations named "operations". It also supports + * GoogleAdsService.mutateGoogleAds(request), where the request contains a list of + * MutateOperations named "mutate_operations". * * @param operationIndex the index of the operation, starting from 0. * @param partialFailureStatus a partialFailure status, with the detail list containing {@link @@ -108,8 +110,7 @@ public List getGoogleAdsErrors( List result = new ArrayList(); // Searches all the errors for one relating to the specified operation. for (ErrorPath path : getErrorPaths(googleAdsFailure)) { - if (("operations".equals(path.getFieldName()) - || "mutate_operations".equals(path.getFieldName())) + if (path.isOperationIndex() && path.getIndex().isPresent() && path.getIndex().get() == operationIndex) { GoogleAdsErrorT error = path.getError(); @@ -121,15 +122,49 @@ public List getGoogleAdsErrors( return result; } + /** Provides a convenience method to get all failed operation indices. */ + public List getFailedOperationIndices(GoogleAdsFailureT googleAdsFailureT) { + return StreamSupport.stream(getErrorPaths(googleAdsFailureT).spliterator(), false) + .filter(ErrorPath::isOperationIndex) + .filter(p -> p.getIndex().isPresent()) + .map(p -> (Long) p.getIndex().get()) + .distinct() + .collect(Collectors.toList()); + } + /** * Unpacks a single {@link GoogleAdsFailureT} from an {@link Any} instance. * - * @throws InvalidProtocolBufferException if {@link GoogleAdsFailureT} is not able to unpack the - * protocol buffer. This is most likely due to using the wrong version of ErrorUtils - * being used. + * @throws DeserializeException if an {@link InvalidProtocolBufferException} is encountered. This + * would indicate that the detail object was not-null, but the contents couldn't be + * deserialized to the target type. This may indicate that the target type is incorrect, or + * that the content of the Any message is incorrect. + * @throws NullPointerException if detail is null. */ - public GoogleAdsFailureT getGoogleAdsFailure(Any detail) throws InvalidProtocolBufferException { - return detail.unpack(getGoogleAdsFailureClass()); + public GoogleAdsFailureT getGoogleAdsFailure(Any detail) { + try { + return detail.unpack(getGoogleAdsFailureClass()); + } catch (InvalidProtocolBufferException e) { + throw new DeserializeException(e); + } + } + + /** + * Unpacks the GoogleAdsFailureT instance form a partial failure status object. + * + *

The status object contains a details repeated field. This contains at most 1 Any protos + * which encode a GoogleAdsFailure instance. + * + * @param partialFailureStatus the partial failure Status object returned in the repsponse. + * @return the GoogleAdsFailure instance describing the partial failures, or null if none is + * found. + * @throws DeserializeException if an {@link InvalidProtocolBufferException} is encountered. + * @throws NullPointerException if partialFailureStatus is null. + */ + public GoogleAdsFailureT getGoogleAdsFailure(Status partialFailureStatus) { + return partialFailureStatus.getDetailsCount() == 0 + ? null + : getGoogleAdsFailure(partialFailureStatus.getDetails(0)); } /** Checks if a result in a mutate response is a partial failure. */ @@ -192,8 +227,7 @@ protected static class ErrorPath { private final String fieldName; private final Optional index; - public ErrorPath( - GoogleAdsErrorType error, String fieldName, Optional index) { + public ErrorPath(GoogleAdsErrorType error, String fieldName, Optional index) { this.error = error; this.fieldName = fieldName; this.index = index; @@ -210,5 +244,17 @@ public String getFieldName() { public Optional getIndex() { return index; } + + public boolean isOperationIndex() { + return "operations".equals(getFieldName()) || "mutate_operations".equals(getFieldName()); + } + } + + /** Indicates an error occurred deserializing an API error object. */ + public static class DeserializeException extends RuntimeException { + + public DeserializeException(Throwable cause) { + super(cause); + } } } diff --git a/google-ads/src/test/java/com/google/ads/googleads/lib/utils/AbstractErrorUtilsTest.java b/google-ads/src/test/java/com/google/ads/googleads/lib/utils/AbstractErrorUtilsTest.java index e0b1de933a..feb5d702c0 100644 --- a/google-ads/src/test/java/com/google/ads/googleads/lib/utils/AbstractErrorUtilsTest.java +++ b/google-ads/src/test/java/com/google/ads/googleads/lib/utils/AbstractErrorUtilsTest.java @@ -176,6 +176,53 @@ public void getGoogleAdsErrors_duplicates_whenErrorsDiffer() assertEquals(error1, result.get(1)); } + @Test + public void getFailedOperationIndices_returnsOperation() { + MockPath path0 = + MockPath.newBuilder() + .setIndex(Int64Value.newBuilder().setValue(123)) + .setFieldName(operationsFieldName) + .build(); + MockError error0 = MockError.newBuilder().addLocation(path0).build(); + MockFailure failure = MockFailure.newBuilder().addErrors(error0).build(); + List result = impl.getFailedOperationIndices(failure); + assertEquals(1, result.size()); + assertEquals(123L, (long) result.get(0)); + } + + @Test + public void getFailedOperationIndices_removesDuplicates() { + MockPath path0 = + MockPath.newBuilder() + .setIndex(Int64Value.newBuilder().setValue(123)) + .setFieldName(operationsFieldName) + .build(); + MockPath path1 = + MockPath.newBuilder() + .setIndex(Int64Value.newBuilder().setValue(123)) + .setFieldName(operationsFieldName) + .build(); + MockError error0 = MockError.newBuilder().addLocation(path0).build(); + MockError error1 = MockError.newBuilder().addLocation(path1).build(); + MockFailure failure = MockFailure.newBuilder().addErrors(error0).addErrors(error1).build(); + List result = impl.getFailedOperationIndices(failure); + assertEquals(1, result.size()); + assertEquals(123L, (long) result.get(0)); + } + + @Test + public void getFailedOperationIndices_ignoresNonOperationErrors() { + MockPath path0 = + MockPath.newBuilder() + .setIndex(Int64Value.newBuilder().setValue(123)) + .setFieldName("someotherfield") + .build(); + MockError error0 = MockError.newBuilder().addLocation(path0).build(); + MockFailure failure = MockFailure.newBuilder().addErrors(error0).build(); + List result = impl.getFailedOperationIndices(failure); + assertEquals(0, result.size()); + } + // We do want a dummy here for the version specific code, rather than a mock, so we can test the // base class methods. private static class TestImpl extends AbstractErrorUtils {