Repository: googleapis/google-api-java-client
Pull request #1783 (2021-05-04 00:54:05)

fix: update GoogleJsonError object to accomodate all fields in Invalid parameter exception
Fixes b/185405327

Invalid parameter error message does not contain details about which parameter is the invalid one. These fields have been added to GoogleJsonError object.


Patch:
diff --git a/.kokoro/release/publish_javadoc11.sh b/.kokoro/release/publish_javadoc11.sh
index 7690b4f4..7ab04d3d 100755
--- a/.kokoro/release/publish_javadoc11.sh
+++ b/.kokoro/release/publish_javadoc11.sh
@@ -49,6 +49,13 @@ pushd target/docfx-yml
 python3 -m docuploader create-metadata \
  --name ${NAME} \
  --version ${VERSION} \
+ --xrefs devsite://java/gax \
+ --xrefs devsite://java/google-cloud-core \
+ --xrefs devsite://java/api-common \
+ --xrefs devsite://java/proto-google-common-protos \
+ --xrefs devsite://java/google-api-client \
+ --xrefs devsite://java/google-http-client \
+ --xrefs devsite://java/protobuf \
  --language java
 
 # upload yml to production bucket
diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/json/GoogleJsonError.java b/google-api-client/src/main/java/com/google/api/client/googleapis/json/GoogleJsonError.java
index 265407f5..1a530145 100644
--- a/google-api-client/src/main/java/com/google/api/client/googleapis/json/GoogleJsonError.java
+++ b/google-api-client/src/main/java/com/google/api/client/googleapis/json/GoogleJsonError.java
@@ -21,6 +21,7 @@ import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.JsonObjectParser;
 import com.google.api.client.util.Data;
 import com.google.api.client.util.Key;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -183,6 +184,64 @@ public class GoogleJsonError extends GenericJson {
     }
   }
 
+  public static class Details {
+    @Key("@type")
+    private String type;
+
+    @Key private String detail;
+    @Key private List<ParameterViolations> parameterViolations;
+
+    public String getType() {
+      return type;
+    }
+
+    public void setType(String type) {
+      this.type = type;
+    }
+
+    public String getDetail() {
+      return detail;
+    }
+
+    public void setDetail(String detail) {
+      this.detail = detail;
+    }
+
+    public List<ParameterViolations> getParameterViolations() {
+      return parameterViolations;
+    }
+
+    /**
+     * Sets parameterViolations list as immutable to prevent exposing mutable state.
+     *
+     * @param parameterViolations
+     */
+    public void setParameterViolations(List<ParameterViolations> parameterViolations) {
+      this.parameterViolations = ImmutableList.copyOf(parameterViolations);
+    }
+  }
+
+  public static class ParameterViolations {
+    @Key private String parameter;
+    @Key private String description;
+
+    public String getDescription() {
+      return description;
+    }
+
+    public void setDescription(String description) {
+      this.description = description;
+    }
+
+    public String getParameter() {
+      return parameter;
+    }
+
+    public void setParameter(String parameter) {
+      this.parameter = parameter;
+    }
+  }
+
   /** List of detailed errors or {@code null} for none. */
   @Key private List<ErrorInfo> errors;
 
@@ -192,6 +251,9 @@ public class GoogleJsonError extends GenericJson {
   /** Human-readable explanation of the error or {@code null} for none. */
   @Key private String message;
 
+  /** Lists type and parameterViolation details of an Exception */
+  @Key private List<Details> details;
+
   /**
    * Returns the list of detailed errors or {@code null} for none.
    *
@@ -202,12 +264,13 @@ public class GoogleJsonError extends GenericJson {
   }
 
   /**
-   * Sets the list of detailed errors or {@code null} for none.
+   * Sets the list of detailed errors or {@code null} for none. Sets the list of detailed errors as
+   * immutable to prevent exposing mutable state.
    *
    * @since 1.8
    */
   public final void setErrors(List<ErrorInfo> errors) {
-    this.errors = errors;
+    this.errors = ImmutableList.copyOf(errors);
   }
 
   /**
@@ -246,6 +309,20 @@ public class GoogleJsonError extends GenericJson {
     this.message = message;
   }
 
+  public List<Details> getDetails() {
+    return details;
+  }
+
+  /**
+   * Sets the list of invalid parameter error details as immutable to prevent exposing mutable
+   * state.
+   *
+   * @param details
+   */
+  public void setDetails(List<Details> details) {
+    this.details = ImmutableList.copyOf(details);
+  }
+
   @Override
   public GoogleJsonError set(String fieldName, Object value) {
     return (GoogleJsonError) super.set(fieldName, value);
diff --git a/pom.xml b/pom.xml
index 45674e38..0cfce5e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,17 +2,13 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <groupId>org.sonatype.oss</groupId>
-    <artifactId>oss-parent</artifactId>
-    <version>7</version>
-  </parent>
   <groupId>com.google.api-client</groupId>
   <artifactId>google-api-client-parent</artifactId>
   <version>1.31.5-SNAPSHOT</version><!-- {x-version-update:google-api-client:current} -->
   <packaging>pom</packaging>
   <name>Parent for the Google API Client Library for Java</name>
-
+  <description>The Google APIs Client Library for Java is a Java client library
+               for accessing any HTTP-based API on the web</description>
   <url>https://github.com/googleapis/google-api-java-client</url>
 
   <issueManagement>
@@ -300,7 +296,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>3.1.1</version>
+          <version>3.1.2</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -482,7 +478,7 @@
     <project.oauth.version>1.31.5</project.oauth.version>
     <project.jsr305.version>3.0.2</project.jsr305.version>
     <project.gson.version>2.8.6</project.gson.version>
-    <project.protobuf-java.version>3.15.8</project.protobuf-java.version>
+    <project.protobuf-java.version>3.16.0</project.protobuf-java.version>
     <project.guava.version>30.1.1-android</project.guava.version>
     <project.appengine.version>1.9.84</project.appengine.version>
     <project.xpp3.version>1.1.4c</project.xpp3.version>
diff --git a/synth.metadata b/synth.metadata
index 4ab5aca4..66ce3392 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,14 +4,14 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/google-api-java-client.git",
-        "sha": "441e1cc3d12271a5590451e147d5fd46bc83f868"
+        "sha": "dbeebd8be5c60cbe60aeda97eeea4b4dca99c0d6"
       }
     },
     {
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "8285c2b4cdbc3771d031ad91e1c4ec9e55fff45d"
+        "sha": "046994f491c02806aea60118e214a9edd67f5ab7"
       }
     }
   ],


Test patch:
diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonErrorTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonErrorTest.java
index 83960bc8..99cc3b1d 100644
--- a/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonErrorTest.java
+++ b/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonErrorTest.java
@@ -27,10 +27,11 @@ import com.google.api.client.testing.http.HttpTesting;
 import com.google.api.client.testing.http.MockHttpTransport;
 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import java.io.InputStream;
 import junit.framework.TestCase;
 
 /**
- * Tests {@link GoogleJsonError}.
+ * Tests {@link com.google.api.client.googleapis.json.GoogleJsonError}.
  *
  * @author Yaniv Inbar
  */
@@ -51,7 +52,8 @@ public class GoogleJsonErrorTest extends TestCase {
   public void test_json() throws Exception {
     JsonParser parser = FACTORY.createJsonParser(ERROR);
     parser.nextToken();
-    GoogleJsonError e = parser.parse(GoogleJsonError.class);
+    com.google.api.client.googleapis.json.GoogleJsonError e =
+        parser.parse(com.google.api.client.googleapis.json.GoogleJsonError.class);
     assertEquals(ERROR, FACTORY.toString(e));
   }
 
@@ -70,6 +72,10 @@ public class GoogleJsonErrorTest extends TestCase {
               .setStatusCode(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
     }
 
+    ErrorTransport(MockLowLevelHttpResponse mockLowLevelHttpResponse) {
+      response = mockLowLevelHttpResponse;
+    }
+
     @Override
     public LowLevelHttpRequest buildRequest(String name, String url) {
       return new MockLowLevelHttpRequest(url).setResponse(response);
@@ -82,7 +88,41 @@ public class GoogleJsonErrorTest extends TestCase {
         transport.createRequestFactory().buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL);
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
-    GoogleJsonError errorResponse = GoogleJsonError.parse(FACTORY, response);
+    com.google.api.client.googleapis.json.GoogleJsonError errorResponse =
+        com.google.api.client.googleapis.json.GoogleJsonError.parse(FACTORY, response);
     assertEquals(ERROR, FACTORY.toString(errorResponse));
   }
+
+  public void testParse_withDetails() throws Exception {
+    String DETAILS_ERROR =
+        "{"
+            + "\"code\":400,"
+            + "\"details\":[{"
+            + "\"@type\":\"type.googleapis.com/google.dataflow.v1beta3.InvalidTemplateParameters\","
+            + "\"parameterViolations\":[{"
+            + "\"description\":\"Parameter didn't match regex '^[0-9a-zA-Z_]+$'\","
+            + "\"parameter\":\"safeBrowsingApiKey\""
+            + "}]},{"
+            + "\"@type\":\"type.googleapis.com/google.rpc.DebugInfo\","
+            + "\"detail\":\"test detail\"}],"
+            + "\"message\":\"The template parameters are invalid.\","
+            + "\"status\":\"INVALID_ARGUMENT\""
+            + "}";
+    InputStream errorContent = GoogleJsonErrorTest.class.getResourceAsStream("error.json");
+    HttpTransport transport =
+        new ErrorTransport(
+            new MockLowLevelHttpResponse()
+                .setContent(errorContent)
+                .setContentType(Json.MEDIA_TYPE)
+                .setStatusCode(HttpStatusCodes.STATUS_CODE_FORBIDDEN));
+    HttpRequest request =
+        transport.createRequestFactory().buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL);
+    request.setThrowExceptionOnExecuteError(false);
+    HttpResponse response = request.execute();
+    com.google.api.client.googleapis.json.GoogleJsonError errorResponse =
+        com.google.api.client.googleapis.json.GoogleJsonError.parse(FACTORY, response);
+
+    assertEquals(DETAILS_ERROR, FACTORY.toString(errorResponse));
+    assertNotNull(errorResponse.getDetails());
+  }
 }
diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonResponseExceptionTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonResponseExceptionTest.java
index 075154cc..ad28bd0c 100644
--- a/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonResponseExceptionTest.java
+++ b/google-api-client/src/test/java/com/google/api/client/googleapis/json/GoogleJsonResponseExceptionTest.java
@@ -17,10 +17,13 @@ package com.google.api.client.googleapis.json;
 import com.google.api.client.googleapis.json.GoogleJsonErrorTest.ErrorTransport;
 import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpStatusCodes;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.Json;
 import com.google.api.client.testing.http.HttpTesting;
 import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import java.io.InputStream;
 import junit.framework.TestCase;
 
 /**
@@ -37,7 +40,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("200"));
   }
@@ -49,8 +53,12 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
-    assertEquals(GoogleJsonErrorTest.ERROR, GoogleJsonErrorTest.FACTORY.toString(ge.getDetails()));
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
+    assertEquals(
+        com.google.api.client.googleapis.json.GoogleJsonErrorTest.ERROR,
+        com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY.toString(
+            ge.getDetails()));
     assertTrue(ge.getMessage().startsWith("403"));
   }
 
@@ -61,7 +69,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
   }
@@ -73,7 +82,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
   }
@@ -85,7 +95,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
     assertTrue(ge.getMessage().contains("<foo>"));
@@ -98,7 +109,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
   }
@@ -110,7 +122,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
   }
@@ -125,7 +138,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNotNull(ge.getDetails());
     assertEquals("invalid_token", ge.getDetails().getMessage());
     assertTrue(ge.getMessage().contains("403"));
@@ -141,7 +155,8 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().contains("403"));
     assertTrue(ge.getMessage().contains("invalid_token"));
@@ -155,8 +170,43 @@ public class GoogleJsonResponseExceptionTest extends TestCase {
     request.setThrowExceptionOnExecuteError(false);
     HttpResponse response = request.execute();
     GoogleJsonResponseException ge =
-        GoogleJsonResponseException.from(GoogleJsonErrorTest.FACTORY, response);
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
     assertNull(ge.getDetails());
     assertTrue(ge.getMessage().startsWith("403"));
   }
+
+  public void testFrom_detailsWithInvalidParameter() throws Exception {
+    String DETAILS_ERROR =
+        "{"
+            + "\"code\":400,"
+            + "\"details\":[{"
+            + "\"@type\":\"type.googleapis.com/google.dataflow.v1beta3.InvalidTemplateParameters\","
+            + "\"parameterViolations\":[{"
+            + "\"description\":\"Parameter didn't match regex '^[0-9a-zA-Z_]+$'\","
+            + "\"parameter\":\"safeBrowsingApiKey\""
+            + "}]},{"
+            + "\"@type\":\"type.googleapis.com/google.rpc.DebugInfo\","
+            + "\"detail\":\"test detail\"}],"
+            + "\"message\":\"The template parameters are invalid.\","
+            + "\"status\":\"INVALID_ARGUMENT\""
+            + "}";
+    InputStream errorContent =
+        com.google.api.client.googleapis.json.GoogleJsonErrorTest.class.getResourceAsStream(
+            "error.json");
+    HttpTransport transport =
+        new ErrorTransport(
+            new MockLowLevelHttpResponse()
+                .setContent(errorContent)
+                .setContentType(Json.MEDIA_TYPE)
+                .setStatusCode(HttpStatusCodes.STATUS_CODE_FORBIDDEN));
+    HttpRequest request =
+        transport.createRequestFactory().buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL);
+    request.setThrowExceptionOnExecuteError(false);
+    HttpResponse response = request.execute();
+    GoogleJsonResponseException ge =
+        GoogleJsonResponseException.from(
+            com.google.api.client.googleapis.json.GoogleJsonErrorTest.FACTORY, response);
+    assertNotNull(ge.getDetails().getDetails());
+  }
 }
diff --git a/google-api-client/src/test/resources/com/google/api/client/googleapis/json/error.json b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/error.json
new file mode 100644
index 00000000..578abec2
--- /dev/null
+++ b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/error.json
@@ -0,0 +1,23 @@
+{
+  "error": {
+    "code": 400,
+    "message": "The template parameters are invalid.",
+    "status": "INVALID_ARGUMENT",
+    "details": [
+      {
+        "@type": "type.googleapis.com/google.dataflow.v1beta3.InvalidTemplateParameters",
+        "parameterViolations": [
+          {
+            "parameter": "safeBrowsingApiKey",
+            "description": "Parameter didn't match regex '^[0-9a-zA-Z_]+$'"
+          }
+        ]
+      },
+      {
+        "@type": "type.googleapis.com/google.rpc.DebugInfo",
+        "detail": "test detail"
+      }
+    ]
+  }
+}
+