Repository: googleapis/google-api-java-client
Pull request #2210 (2022-12-16 05:55:34)

fix: Make Details field extends GenericJson so that GoogleJsonError should include any arbitrary error info from services
fixes: #1974 
The root cause of the issue is that `details` attribute from backend could be of any arbitrary type, we need a generic way to parse error details from backend. 
This approach makes `Details` field extends `GenericJson` so that it can parse any data from backend. This is the preferred approach since this is the simplest and most generic one.
Other approaches: #2044, #2211

generated-files-bot[bot]: <!-- probot comment [12634855]-->
*Warning*: This pull request is touching the following templated files:

* .github/CODEOWNERS
* .github/release-please.yml
* .github/sync-repo-settings.yaml
* .kokoro/build.sh
* .kokoro/common.sh
* .kokoro/dependencies.sh
* .kokoro/release/publish_javadoc.sh
* .kokoro/release/publish_javadoc11.sh
* .kokoro/release/stage.sh
* samples/install-without-bom/pom.xml
* samples/pom.xml
* samples/snapshot/pom.xml
* samples/snippets/pom.xml
google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/googleapis/google-api-java-client/pull/2210/checks?check_run_id=10133511834) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Patch:
diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index c1e4d2da..288e3948 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,4 +13,4 @@
 # limitations under the License.
 docker:
   image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest
-  digest: sha256:a57d2ea6d1a77aa96c17ad0850b779ec6295f88b6c1da3d214b2095d140a2066
+  digest: sha256:9de537d592b60e5eac73b374a28263969bae91ecdb29b445e894576fbf54851c
diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg
index a3f7fb9d..e20330c3 100644
--- a/.kokoro/presubmit/graalvm-native-17.cfg
+++ b/.kokoro/presubmit/graalvm-native-17.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-kokoro-resources/graalvm17"
+  value: "gcr.io/cloud-devrel-kokoro-resources/graalvm17:22.3.0"
 }
 
 env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index 4c7225ec..0fd6ba2f 100644
--- a/.kokoro/presubmit/graalvm-native.cfg
+++ b/.kokoro/presubmit/graalvm-native.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-kokoro-resources/graalvm"
+  value: "gcr.io/cloud-devrel-kokoro-resources/graalvm:22.3.0"
 }
 
 env_vars: {
diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in
index 924f94ae..a5010f77 100644
--- a/.kokoro/requirements.in
+++ b/.kokoro/requirements.in
@@ -17,7 +17,7 @@ pycparser==2.21
 pyperclip==1.8.2
 python-dateutil==2.8.2
 requests==2.27.1
-certifi==2022.9.24
+certifi==2022.12.7
 importlib-metadata==4.8.3
 zipp==3.6.0
 google_api_core==2.8.2
diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt
index 71fcafc7..15c404aa 100644
--- a/.kokoro/requirements.txt
+++ b/.kokoro/requirements.txt
@@ -16,9 +16,9 @@ cachetools==4.2.4 \
     # via
     #   -r requirements.in
     #   google-auth
-certifi==2022.9.24 \
-    --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \
-    --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382
+certifi==2022.12.7 \
+    --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+    --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
     # via
     #   -r requirements.in
     #   requests
diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml
index c06f356f..2232616d 100644
--- a/google-api-client/pom.xml
+++ b/google-api-client/pom.xml
@@ -159,6 +159,11 @@
       <groupId>com.google.http-client</groupId>
       <artifactId>google-http-client</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>com.google.protobuf</groupId>
       <artifactId>protobuf-java</artifactId>
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 c0f0eb86..dadd172f 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
@@ -184,7 +184,7 @@ public class GoogleJsonError extends GenericJson {
     }
   }
 
-  public static class Details {
+  public static class Details extends GenericJson {
     @Key("@type")
     private String type;
 
diff --git a/pom.xml b/pom.xml
index 69a91082..cfd9fab3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -222,6 +222,12 @@
         <version>${project.protobuf-java.version}</version>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <groupId>com.google.code.gson</groupId>
+        <artifactId>gson</artifactId>
+        <version>${gson.version}</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
@@ -337,7 +343,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-dependency-plugin</artifactId>
-          <version>3.3.0</version>
+          <version>3.4.0</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -515,6 +521,7 @@
     <project.datanucleus-maven-plugin.version>4.0.3</project.datanucleus-maven-plugin.version>
     <project.servlet-api.version>2.5</project.servlet-api.version>
     <deploy.autorelease>false</deploy.autorelease>
+    <gson.version>2.10</gson.version>
   </properties>
 
   <profiles>


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 c988804a..380114ae 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
@@ -28,6 +28,7 @@ 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 java.io.InputStreamReader;
 import junit.framework.TestCase;
 
 /**
@@ -37,6 +38,7 @@ import junit.framework.TestCase;
  */
 public class GoogleJsonErrorTest extends TestCase {
 
+  public static final com.google.gson.JsonParser JSON_PARSER = new com.google.gson.JsonParser();
   static final JsonFactory FACTORY = new GsonFactory();
   static final String ERROR =
       "{"
@@ -93,6 +95,32 @@ public class GoogleJsonErrorTest extends TestCase {
     assertEquals(ERROR, FACTORY.toString(errorResponse));
   }
 
+  public void testParse_withMultipleErrorTypesInDetails() throws Exception {
+    InputStream errorResponseStream =
+        GoogleJsonErrorTest.class.getResourceAsStream(
+            "errorResponseWithMultipleTypesInDetails.json");
+
+    InputStream expectedParsedErrorResponse =
+        GoogleJsonErrorTest.class.getResourceAsStream(
+            "expectedParsedErrorWithMultipleTypesInDetails.json");
+
+    HttpTransport transport =
+        new ErrorTransport(
+            new MockLowLevelHttpResponse()
+                .setContent(errorResponseStream)
+                .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 actualParsedErrorResponse =
+        com.google.api.client.googleapis.json.GoogleJsonError.parse(FACTORY, response);
+    assertEquals(
+        JSON_PARSER.parse(new InputStreamReader(expectedParsedErrorResponse)),
+        JSON_PARSER.parse(FACTORY.toString(actualParsedErrorResponse)));
+  }
+
   public void testParse_withDetails() throws Exception {
     String DETAILS_ERROR =
         "{"
@@ -122,7 +150,8 @@ public class GoogleJsonErrorTest extends TestCase {
     com.google.api.client.googleapis.json.GoogleJsonError errorResponse =
         com.google.api.client.googleapis.json.GoogleJsonError.parse(FACTORY, response);
 
-    assertEquals(DETAILS_ERROR, FACTORY.toString(errorResponse));
+    assertEquals(
+        JSON_PARSER.parse(DETAILS_ERROR), JSON_PARSER.parse(FACTORY.toString(errorResponse)));
     assertNotNull(errorResponse.getDetails());
   }
 
@@ -166,7 +195,8 @@ public class GoogleJsonErrorTest extends TestCase {
     com.google.api.client.googleapis.json.GoogleJsonError errorResponse =
         com.google.api.client.googleapis.json.GoogleJsonError.parse(FACTORY, response);
 
-    assertEquals(DETAILS_ERROR, FACTORY.toString(errorResponse));
+    assertEquals(
+        JSON_PARSER.parse(DETAILS_ERROR), JSON_PARSER.parse(FACTORY.toString(errorResponse)));
     assertNotNull(errorResponse.getDetails().get(2).getReason());
   }
 }
diff --git a/google-api-client/src/test/resources/com/google/api/client/googleapis/json/errorResponseWithMultipleTypesInDetails.json b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/errorResponseWithMultipleTypesInDetails.json
new file mode 100644
index 00000000..fc2826ac
--- /dev/null
+++ b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/errorResponseWithMultipleTypesInDetails.json
@@ -0,0 +1,65 @@
+{
+  "error": {
+    "code": 400,
+    "message": "The template parameters are invalid.",
+    "status": "INVALID_ARGUMENT",
+    "details": [
+      {
+        "@type": "type.googleapis.com/google.dataflow.v1beta3.InvalidTemplateParameters",
+        "reason": "TEST REASON 1",
+        "parameterViolations": [
+          {
+            "parameter": "safeBrowsingApiKey",
+            "description": "Parameter didn't match regex '^[0-9a-zA-Z_]+$'"
+          }
+        ]
+      },
+      {
+        "@type": "type.googleapis.com/google.rpc.DebugInfo",
+        "detail": "test detail"
+      },
+      {
+        "@type": "type.googleapis.com/google.rpc.DebugInfo",
+        "reason": "test reason 2"
+      },
+      {
+        "@type": "type.googleapis.com/google.rpc.BadRequest",
+        "fieldViolations": [
+          {
+            "field": "language_code",
+            "description": "Field is required"
+          }
+        ]
+      },
+      {
+        "@type": "type.googleapis.com/google.chrome.policy.v1.PolicyModificationErrorDetails",
+        "modificationErrors": [
+          {
+            "policyTargetKey": {
+              "targetResource": "orgunits/03ph8a2z19ryqq8"
+            },
+            "policySchema": "chrome.users.BrowserThemeColor",
+            "fieldErrors": [
+              {
+                "field": "browserThemeColor",
+                "error": "Field is an invalid hex color. Only the numbers 0-9 and letters A-F are permitted."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "@type": "type.googleapis.com/google.mybusiness.v4.ValidationError",
+        "errorDetails": [
+          {
+            "code": 3,
+            "field": "regular_hours.periods.close_time",
+            "message": "Time field must follow hh:mm format.",
+            "value": "25:00"
+          }
+        ]
+      }
+    ]
+  }
+}
+
diff --git a/google-api-client/src/test/resources/com/google/api/client/googleapis/json/expectedParsedErrorWithMultipleTypesInDetails.json b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/expectedParsedErrorWithMultipleTypesInDetails.json
new file mode 100644
index 00000000..7cbf42e7
--- /dev/null
+++ b/google-api-client/src/test/resources/com/google/api/client/googleapis/json/expectedParsedErrorWithMultipleTypesInDetails.json
@@ -0,0 +1,63 @@
+{
+  "code": 400,
+  "message": "The template parameters are invalid.",
+  "status": "INVALID_ARGUMENT",
+  "details": [
+    {
+      "@type": "type.googleapis.com/google.dataflow.v1beta3.InvalidTemplateParameters",
+      "reason": "TEST REASON 1",
+      "parameterViolations": [
+        {
+          "parameter": "safeBrowsingApiKey",
+          "description": "Parameter didn't match regex '^[0-9a-zA-Z_]+$'"
+        }
+      ]
+    },
+    {
+      "@type": "type.googleapis.com/google.rpc.DebugInfo",
+      "detail": "test detail"
+    },
+    {
+      "@type": "type.googleapis.com/google.rpc.DebugInfo",
+      "reason": "test reason 2"
+    },
+    {
+      "@type": "type.googleapis.com/google.rpc.BadRequest",
+      "fieldViolations": [
+        {
+          "field": "language_code",
+          "description": "Field is required"
+        }
+      ]
+    },
+    {
+      "@type": "type.googleapis.com/google.chrome.policy.v1.PolicyModificationErrorDetails",
+      "modificationErrors": [
+        {
+          "policyTargetKey": {
+            "targetResource": "orgunits/03ph8a2z19ryqq8"
+          },
+          "policySchema": "chrome.users.BrowserThemeColor",
+          "fieldErrors": [
+            {
+              "field": "browserThemeColor",
+              "error": "Field is an invalid hex color. Only the numbers 0-9 and letters A-F are permitted."
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "@type": "type.googleapis.com/google.mybusiness.v4.ValidationError",
+      "errorDetails": [
+        {
+          "code": 3,
+          "field": "regular_hours.periods.close_time",
+          "message": "Time field must follow hh:mm format.",
+          "value": "25:00"
+        }
+      ]
+    }
+  ]
+}
+