2121import java .util .ArrayList ;
2222import java .util .List ;
2323import java .util .Optional ;
24+ import java .util .stream .Collectors ;
25+ import java .util .stream .StreamSupport ;
2426
2527/** Contains utility methods for handling partial failure of operations. */
2628public abstract class AbstractErrorUtils <
@@ -77,9 +79,9 @@ public abstract class AbstractErrorUtils<
7779 * empty list otherwise.
7880 *
7981 * <p>This method supports <code>XXXService.mutate(request)</code> where the request contains a
80- * list of operations named "operations". It also supports
81- * <code> GoogleAdsService.mutateGoogleAds(request)</code>, where the request contains a list of
82- * <code> MutateOperation</code>s named "mutate_operations".
82+ * list of operations named "operations". It also supports <code>
83+ * GoogleAdsService.mutateGoogleAds(request)</code>, where the request contains a list of <code>
84+ * MutateOperation</code>s named "mutate_operations".
8385 *
8486 * @param operationIndex the index of the operation, starting from 0.
8587 * @param partialFailureStatus a partialFailure status, with the detail list containing {@link
@@ -108,8 +110,7 @@ public List<GoogleAdsErrorT> getGoogleAdsErrors(
108110 List <GoogleAdsErrorT > result = new ArrayList ();
109111 // Searches all the errors for one relating to the specified operation.
110112 for (ErrorPath <GoogleAdsErrorT > path : getErrorPaths (googleAdsFailure )) {
111- if (("operations" .equals (path .getFieldName ())
112- || "mutate_operations" .equals (path .getFieldName ()))
113+ if (path .isOperationIndex ()
113114 && path .getIndex ().isPresent ()
114115 && path .getIndex ().get () == operationIndex ) {
115116 GoogleAdsErrorT error = path .getError ();
@@ -121,15 +122,49 @@ public List<GoogleAdsErrorT> getGoogleAdsErrors(
121122 return result ;
122123 }
123124
125+ /** Provides a convenience method to get all failed operation indices. */
126+ public List <Long > getFailedOperationIndices (GoogleAdsFailureT googleAdsFailureT ) {
127+ return StreamSupport .stream (getErrorPaths (googleAdsFailureT ).spliterator (), false )
128+ .filter (ErrorPath ::isOperationIndex )
129+ .filter (p -> p .getIndex ().isPresent ())
130+ .map (p -> (Long ) p .getIndex ().get ())
131+ .distinct ()
132+ .collect (Collectors .toList ());
133+ }
134+
124135 /**
125136 * Unpacks a single {@link GoogleAdsFailureT} from an {@link Any} instance.
126137 *
127- * @throws InvalidProtocolBufferException if {@link GoogleAdsFailureT} is not able to unpack the
128- * protocol buffer. This is most likely due to using the wrong version of <code>ErrorUtils
129- * </code> being used.
138+ * @throws DeserializeException if an {@link InvalidProtocolBufferException} is encountered. This
139+ * would indicate that the detail object was not-null, but the contents couldn't be
140+ * deserialized to the target type. This may indicate that the target type is incorrect, or
141+ * that the content of the Any message is incorrect.
142+ * @throws NullPointerException if detail is null.
130143 */
131- public GoogleAdsFailureT getGoogleAdsFailure (Any detail ) throws InvalidProtocolBufferException {
132- return detail .unpack (getGoogleAdsFailureClass ());
144+ public GoogleAdsFailureT getGoogleAdsFailure (Any detail ) {
145+ try {
146+ return detail .unpack (getGoogleAdsFailureClass ());
147+ } catch (InvalidProtocolBufferException e ) {
148+ throw new DeserializeException (e );
149+ }
150+ }
151+
152+ /**
153+ * Unpacks the GoogleAdsFailureT instance form a partial failure status object.
154+ *
155+ * <p>The status object contains a details repeated field. This contains at most 1 Any protos
156+ * which encode a GoogleAdsFailure instance.
157+ *
158+ * @param partialFailureStatus the partial failure Status object returned in the repsponse.
159+ * @return the GoogleAdsFailure instance describing the partial failures, or null if none is
160+ * found.
161+ * @throws DeserializeException if an {@link InvalidProtocolBufferException} is encountered.
162+ * @throws NullPointerException if partialFailureStatus is null.
163+ */
164+ public GoogleAdsFailureT getGoogleAdsFailure (Status partialFailureStatus ) {
165+ return partialFailureStatus .getDetailsCount () == 0
166+ ? null
167+ : getGoogleAdsFailure (partialFailureStatus .getDetails (0 ));
133168 }
134169
135170 /** Checks if a result in a mutate response is a partial failure. */
@@ -192,8 +227,7 @@ protected static class ErrorPath<GoogleAdsErrorType extends Message> {
192227 private final String fieldName ;
193228 private final Optional <Long > index ;
194229
195- public ErrorPath (
196- GoogleAdsErrorType error , String fieldName , Optional <Long > index ) {
230+ public ErrorPath (GoogleAdsErrorType error , String fieldName , Optional <Long > index ) {
197231 this .error = error ;
198232 this .fieldName = fieldName ;
199233 this .index = index ;
@@ -210,5 +244,17 @@ public String getFieldName() {
210244 public Optional <Long > getIndex () {
211245 return index ;
212246 }
247+
248+ public boolean isOperationIndex () {
249+ return "operations" .equals (getFieldName ()) || "mutate_operations" .equals (getFieldName ());
250+ }
251+ }
252+
253+ /** Indicates an error occurred deserializing an API error object. */
254+ public static class DeserializeException extends RuntimeException {
255+
256+ public DeserializeException (Throwable cause ) {
257+ super (cause );
258+ }
213259 }
214260}
0 commit comments