浏览代码

Don't throw exceptions during DocumentField serialization (#95673)

As a prerequisite to making search responses use chunked REST serialization,
we need to ensure that no exceptions are thrown when toXContent gets called.
DocumentFields can currently throw exceptions if their contained values are not
serializable. This commit changes the serialization code here to replace
unserializable values with a placeholder value rather than failing the whole request.

Relates to #95661
Alan Woodward 2 年之前
父节点
当前提交
636574904d

+ 6 - 4
server/src/main/java/org/elasticsearch/common/document/DocumentField.java

@@ -135,10 +135,12 @@ public class DocumentField implements Writeable, Iterable<Object> {
         return (builder, params) -> {
         return (builder, params) -> {
             builder.startArray(name);
             builder.startArray(name);
             for (Object value : values) {
             for (Object value : values) {
-                // This call doesn't really need to support writing any kind of object, since the values
-                // here are always serializable to xContent. Each value could be a leaf types like a string,
-                // number, or boolean, a list of such values, or a map of such values with string keys.
-                builder.value(value);
+                try {
+                    builder.value(value);
+                } catch (RuntimeException e) {
+                    // if the value cannot be serialized, we catch here and return a placeholder value
+                    builder.value("<unserializable>");
+                }
             }
             }
             builder.endArray();
             builder.endArray();
             return builder;
             return builder;

+ 10 - 0
server/src/test/java/org/elasticsearch/index/get/DocumentFieldTests.java

@@ -43,6 +43,16 @@ public class DocumentFieldTests extends ESTestCase {
         assertEquals("{\"field\":[\"ignored1\",\"ignored2\"]}", ignoredOutput);
         assertEquals("{\"field\":[\"ignored1\",\"ignored2\"]}", ignoredOutput);
     }
     }
 
 
+    public void testUnserializableXContent() {
+        DocumentField df = new DocumentField(
+            "field",
+            List.of((ToXContent) (builder, params) -> { throw new UnsupportedOperationException(); })
+        );
+        String output = Strings.toString(df.getValidValuesWriter());
+        assertEquals("""
+            {"field":["<unserializable>"]}""", output);
+    }
+
     public void testEqualsAndHashcode() {
     public void testEqualsAndHashcode() {
         checkEqualsAndHashCode(
         checkEqualsAndHashCode(
             randomDocumentField(XContentType.JSON).v1(),
             randomDocumentField(XContentType.JSON).v1(),

+ 1 - 0
x-pack/plugin/build.gradle

@@ -154,6 +154,7 @@ tasks.named("yamlRestTestV7CompatTransform").configure { task ->
   task.skipTest("sql/translate/Translate SQL", "query folding changed in v 8.5, added track_total_hits: -1")
   task.skipTest("sql/translate/Translate SQL", "query folding changed in v 8.5, added track_total_hits: -1")
   task.skipTest("service_accounts/10_basic/Test get service accounts", "new service accounts are added")
   task.skipTest("service_accounts/10_basic/Test get service accounts", "new service accounts are added")
   task.skipTest("spatial/70_script_doc_values/diagonal length", "precision changed in 8.4.0")
   task.skipTest("spatial/70_script_doc_values/diagonal length", "precision changed in 8.4.0")
+  task.skipTest("spatial/70_script_doc_values/geoshape value", "error message changed in 8.9.0")
 
 
   task.replaceValueInMatch("_type", "_doc")
   task.replaceValueInMatch("_type", "_doc")
   task.addAllowedWarningRegex("\\[types removal\\].*")
   task.addAllowedWarningRegex("\\[types removal\\].*")

+ 7 - 6
x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/spatial/70_script_doc_values.yml

@@ -93,8 +93,11 @@ setup:
 
 
 ---
 ---
 "geoshape value":
 "geoshape value":
+  - skip:
+      version: " - 8.8.99"
+      reason: output changed in 8.9
+
   - do:
   - do:
-      catch: /illegal_argument_exception/
       search:
       search:
         rest_total_hits_as_int: true
         rest_total_hits_as_int: true
         body:
         body:
@@ -103,10 +106,9 @@ setup:
               script:
               script:
                 source: "doc['geo_shape'].get(0)"
                 source: "doc['geo_shape'].get(0)"
 
 
-  - match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
+  - match: { hits.hits.0.fields.type.0: '<unserializable>' }
 
 
   - do:
   - do:
-      catch: /illegal_argument_exception/
       search:
       search:
         rest_total_hits_as_int: true
         rest_total_hits_as_int: true
         body:
         body:
@@ -115,10 +117,9 @@ setup:
               script:
               script:
                 source: "field('geo_shape').get(null)"
                 source: "field('geo_shape').get(null)"
 
 
-  - match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
+  - match: { hits.hits.0.fields.type.0: '<unserializable>' }
 
 
   - do:
   - do:
-      catch: /illegal_argument_exception/
       search:
       search:
         rest_total_hits_as_int: true
         rest_total_hits_as_int: true
         body:
         body:
@@ -127,7 +128,7 @@ setup:
               script:
               script:
                 source: "/* avoid yaml stash */ $('geo_shape', null)"
                 source: "/* avoid yaml stash */ $('geo_shape', null)"
 
 
-  - match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
+  - match: { hits.hits.0.fields.type.0: '<unserializable>' }
 
 
 ---
 ---
 "diagonal length":
 "diagonal length":