Browse Source

Added parsing of erroneous field value (#42321)

sandmannn 6 years ago
parent
commit
1ad8af127b

+ 1 - 1
libs/x-content/src/main/java/org/elasticsearch/common/xcontent/support/AbstractXContentParser.java

@@ -367,7 +367,7 @@ public abstract class AbstractXContentParser implements XContentParser {
         return list;
     }
 
-    static Object readValue(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {
+    public static Object readValue(XContentParser parser, Supplier<Map<String, Object>> mapFactory) throws IOException {
         switch (parser.currentToken()) {
             case VALUE_STRING: return parser.text();
             case VALUE_NUMBER: return parser.numberValue();

+ 24 - 2
server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java

@@ -33,6 +33,8 @@ import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting.Property;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.support.AbstractXContentParser;
 import org.elasticsearch.index.analysis.NamedAnalyzer;
 import org.elasticsearch.index.mapper.FieldNamesFieldMapper.FieldNamesFieldType;
 import org.elasticsearch.index.similarity.SimilarityProvider;
@@ -46,6 +48,7 @@ import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
 import java.util.Objects;
 import java.util.stream.StreamSupport;
 
@@ -276,14 +279,33 @@ public abstract class FieldMapper extends Mapper implements Cloneable {
                 context.doc().add(field);
             }
         } catch (Exception e) {
-            throw new MapperParsingException("failed to parse field [{}] of type [{}] in document with id '{}'", e, fieldType().name(),
-                    fieldType().typeName(), context.sourceToParse().id());
+            String valuePreview = "";
+            try {
+                XContentParser parser = context.parser();
+                Object complexValue = AbstractXContentParser.readValue(parser, ()-> new HashMap<String, Object>());
+                if (complexValue == null) {
+                    valuePreview = "null";
+                } else {
+                    valuePreview = complexValue.toString();
+                }
+            } catch (Exception innerException) {
+                throw new MapperParsingException("failed to parse field [{}] of type [{}] in document with id '{}'. " +
+                    "Could not parse field value preview,",
+                    e, fieldType().name(), fieldType().typeName(), context.sourceToParse().id());
+            }
+
+            throw new MapperParsingException("failed to parse field [{}] of type [{}] in document with id '{}'. " +
+                "Preview of field's value: '{}'", e, fieldType().name(), fieldType().typeName(),
+                context.sourceToParse().id(), valuePreview);
         }
         multiFields.parse(this, context);
     }
 
     /**
      * Parse the field value and populate <code>fields</code>.
+     *
+     * Implementations of this method should ensure that on failing to parse parser.currentToken() must be the
+     * current failing token
      */
     protected abstract void parseCreateField(ParseContext context, List<IndexableField> fields) throws IOException;
 

+ 35 - 3
server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java

@@ -135,16 +135,48 @@ public class BooleanFieldMapperTests extends ESSingleNodeTestCase {
                 .endObject()
             .endObject());
         DocumentMapper defaultMapper = parser.parse("type", new CompressedXContent(mapping));
+        // omit "false"/"true" here as they should still be parsed correctly
+        String randomValue = randomFrom("off", "no", "0", "on", "yes", "1");
         BytesReference source = BytesReference.bytes(XContentFactory.jsonBuilder()
                 .startObject()
-                    // omit "false"/"true" here as they should still be parsed correctly
-                    .field("field", randomFrom("off", "no", "0", "on", "yes", "1"))
+                    .field("field", randomValue)
                 .endObject());
         MapperParsingException ex = expectThrows(MapperParsingException.class,
                 () -> defaultMapper.parse(new SourceToParse("test", "type", "1", source, XContentType.JSON)));
-        assertEquals("failed to parse field [field] of type [boolean] in document with id '1'", ex.getMessage());
+        assertEquals("failed to parse field [field] of type [boolean] in document with id '1'. " +
+            "Preview of field's value: '" + randomValue + "'", ex.getMessage());
     }
 
+
+    public void testParsesBooleansNestedStrict() throws IOException {
+        String mapping = Strings.toString(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("type")
+                    .startObject("properties")
+                        .startObject("field")
+                            .field("type", "boolean")
+                        .endObject()
+                    .endObject()
+                .endObject()
+            .endObject());
+        DocumentMapper defaultMapper = parser.parse("type", new CompressedXContent(mapping));
+        // omit "false"/"true" here as they should still be parsed correctly
+        String randomValue = "no";
+        BytesReference source = BytesReference.bytes(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("field")
+                    .field("inner_field", randomValue)
+                .endObject()
+            .endObject());
+        MapperParsingException ex = expectThrows(MapperParsingException.class,
+                () -> defaultMapper.parse(new SourceToParse("test", "type", "1", source, XContentType.JSON)));
+        assertEquals("failed to parse field [field] of type [boolean] in document with id '1'. " +
+            "Preview of field's value: '{inner_field=" + randomValue + "}'", ex.getMessage());
+    }
+
+
+
+
     public void testMultiFields() throws IOException {
         String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
                 .startObject("properties")

+ 79 - 0
server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java

@@ -386,6 +386,85 @@ public class KeywordFieldMapperTests extends ESSingleNodeTestCase {
         assertEquals(DocValuesType.SORTED_SET, fieldType.docValuesType());
     }
 
+    public void testParsesKeywordNestedEmptyObjectStrict() throws IOException {
+        String mapping = Strings.toString(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("type")
+                    .startObject("properties")
+                        .startObject("field")
+                            .field("type", "keyword")
+                        .endObject()
+                    .endObject()
+                .endObject()
+            .endObject());
+        DocumentMapper defaultMapper = parser.parse("type", new CompressedXContent(mapping));
+
+        BytesReference source = BytesReference.bytes(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("field")
+                .endObject()
+            .endObject());
+        MapperParsingException ex = expectThrows(MapperParsingException.class,
+                () -> defaultMapper.parse(new SourceToParse("test", "type", "1", source, XContentType.JSON)));
+        assertEquals("failed to parse field [field] of type [keyword] in document with id '1'. " +
+            "Preview of field's value: '{}'", ex.getMessage());
+    }
+
+    public void testParsesKeywordNestedListStrict() throws IOException {
+        String mapping = Strings.toString(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("type")
+                    .startObject("properties")
+                        .startObject("field")
+                            .field("type", "keyword")
+                        .endObject()
+                    .endObject()
+                .endObject()
+            .endObject());
+        DocumentMapper defaultMapper = parser.parse("type", new CompressedXContent(mapping));
+
+        BytesReference source = BytesReference.bytes(XContentFactory.jsonBuilder()
+            .startObject()
+                .startArray("field")
+                    .startObject()
+                        .startArray("array_name")
+                            .value("inner_field_first")
+                            .value("inner_field_second")
+                        .endArray()
+                    .endObject()
+                .endArray()
+            .endObject());
+        MapperParsingException ex = expectThrows(MapperParsingException.class,
+                () -> defaultMapper.parse(new SourceToParse("test", "type", "1", source, XContentType.JSON)));
+        assertEquals("failed to parse field [field] of type [keyword] in document with id '1'. " +
+            "Preview of field's value: '{array_name=[inner_field_first, inner_field_second]}'", ex.getMessage());
+    }
+
+    public void testParsesKeywordNullStrict() throws IOException {
+        String mapping = Strings.toString(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("type")
+                    .startObject("properties")
+                        .startObject("field")
+                            .field("type", "keyword")
+                        .endObject()
+                    .endObject()
+                .endObject()
+            .endObject());
+        DocumentMapper defaultMapper = parser.parse("type", new CompressedXContent(mapping));
+
+        BytesReference source = BytesReference.bytes(XContentFactory.jsonBuilder()
+            .startObject()
+                .startObject("field")
+                    .nullField("field_name")
+                .endObject()
+            .endObject());
+        MapperParsingException ex = expectThrows(MapperParsingException.class,
+                () -> defaultMapper.parse(new SourceToParse("test", "type", "1", source, XContentType.JSON)));
+        assertEquals("failed to parse field [field] of type [keyword] in document with id '1'. " +
+            "Preview of field's value: '{field_name=null}'", ex.getMessage());
+    }
+
     public void testUpdateNormalizer() throws IOException {
         String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
                 .startObject("properties").startObject("field")