Skip to content

Commit 31b3b9f

Browse files
committed
Allow unknown group types in lenient parser
1 parent ce08a38 commit 31b3b9f

File tree

3 files changed

+81
-19
lines changed
  • client/rest-high-level/src
  • x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot

3 files changed

+81
-19
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
package org.elasticsearch.client.dataframe.transforms.pivot;
2121

2222
import org.elasticsearch.common.ParsingException;
23+
import org.elasticsearch.common.Strings;
2324
import org.elasticsearch.common.xcontent.ToXContentObject;
2425
import org.elasticsearch.common.xcontent.XContentBuilder;
2526
import org.elasticsearch.common.xcontent.XContentParser;
2627

2728
import java.io.IOException;
2829
import java.util.LinkedHashMap;
29-
import java.util.Locale;
3030
import java.util.Map;
3131
import java.util.Objects;
3232

@@ -51,9 +51,7 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc
5151

5252
// be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does)
5353
XContentParser.Token token;
54-
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
55-
parser.currentToken();
56-
} else {
54+
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
5755
token = parser.nextToken();
5856
if (token != XContentParser.Token.START_OBJECT) {
5957
throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: Expected START_OBJECT but was: " + token);
@@ -70,37 +68,64 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc
7068
}
7169
continue;
7270
}
73-
String destinationFieldName = parser.currentName();
7471

72+
String destinationFieldName = parser.currentName();
7573
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
7674
token = parser.nextToken();
7775
ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
78-
SingleGroupSource.Type groupType = SingleGroupSource.Type.valueOf(parser.currentName().toUpperCase(Locale.ROOT));
76+
String groupType = parser.currentName();
7977

8078
token = parser.nextToken();
81-
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
82-
SingleGroupSource groupSource;
79+
if (token != XContentParser.Token.START_OBJECT) {
80+
// need to consume up to dest field end obj
81+
consumeUntilEndObject(parser, 1);
82+
continue;
83+
}
84+
85+
SingleGroupSource groupSource = null;
8386
switch (groupType) {
84-
case TERMS:
87+
case "terms":
8588
groupSource = TermsGroupSource.fromXContent(parser);
8689
break;
87-
case HISTOGRAM:
90+
case "histogram":
8891
groupSource = HistogramGroupSource.fromXContent(parser);
8992
break;
90-
case DATE_HISTOGRAM:
93+
case "date_histogram":
9194
groupSource = DateHistogramGroupSource.fromXContent(parser);
9295
break;
9396
default:
94-
throw new ParsingException(parser.getTokenLocation(), "invalid grouping type: " + groupType);
97+
// not a valid group source. Consume up to the dest field end object
98+
consumeUntilEndObject(parser, 2);
9599
}
96-
// destination field end_object
97-
parser.nextToken();
98100

99-
groups.put(destinationFieldName, groupSource);
101+
if (groupSource != null) {
102+
groups.put(destinationFieldName, groupSource);
103+
// destination field end_object
104+
parser.nextToken();
105+
}
100106
}
101107
return new GroupConfig(groups);
102108
}
103109

110+
/**
111+
* Consume tokens from the parser until {@code endObjectCount} of end object
112+
* tokens have been read. Nested objects that start and end inside the current
113+
* field are skipped and do contribute to the end object count.
114+
* @param parser The XContent parser
115+
* @param endObjectCount Number of end object tokens to consume
116+
* @throws IOException On parsing error
117+
*/
118+
private static void consumeUntilEndObject(XContentParser parser, int endObjectCount) throws IOException {
119+
do {
120+
XContentParser.Token token = parser.nextToken();
121+
if (token == XContentParser.Token.START_OBJECT) {
122+
endObjectCount++;
123+
} else if (token == XContentParser.Token.END_OBJECT) {
124+
endObjectCount--;
125+
}
126+
} while (endObjectCount != 0);
127+
}
128+
104129
public GroupConfig(Map<String, SingleGroupSource> groups) {
105130
this.groups = groups;
106131
}
@@ -144,4 +169,9 @@ public boolean equals(Object other) {
144169
public int hashCode() {
145170
return Objects.hash(groups);
146171
}
172+
173+
@Override
174+
public String toString() {
175+
return Strings.toString(this, true, true);
176+
}
147177
}

client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.LinkedHashMap;
3232
import java.util.Map;
3333
import java.util.Set;
34+
import java.util.function.Predicate;
3435

3536
import static org.hamcrest.Matchers.instanceOf;
3637

@@ -76,7 +77,12 @@ protected GroupConfig doParseInstance(XContentParser parser) throws IOException
7677

7778
@Override
7879
protected boolean supportsUnknownFields() {
79-
return false;
80+
return true;
81+
}
82+
83+
@Override
84+
protected Predicate<String> getRandomFieldsExcludeFilter() {
85+
return field -> !field.isEmpty();
8086
}
8187

8288
public void testLenientParsing() throws IOException {
@@ -107,4 +113,32 @@ public void testLenientParsing() throws IOException {
107113
assertThat(groupSource, instanceOf(TermsGroupSource.class));
108114
assertEquals(groupSource.getField(), "term-field");
109115
}
116+
117+
public void testLenientParsingUnknowGroupType() throws IOException {
118+
BytesArray json = new BytesArray(
119+
"{ " +
120+
"\"destination-field1\": {" +
121+
"\"newgroup\": {" +
122+
"\"field1\": \"bar\"," +
123+
"\"field2\": \"foo\"" +
124+
"}" +
125+
"}," +
126+
"\"unknown-field\":\"bar\"," +
127+
"\"destination-field2\": {" +
128+
"\"terms\": {" +
129+
"\"field\": \"term-field\"" +
130+
"}" +
131+
"}" +
132+
"}");
133+
XContentParser parser = JsonXContent.jsonXContent
134+
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput());
135+
136+
GroupConfig gc = GroupConfig.fromXContent(parser);
137+
138+
assertEquals(gc.getGroups().size(), 1);
139+
assertTrue(gc.getGroups().containsKey("destination-field2"));
140+
SingleGroupSource groupSource = gc.getGroups().get("destination-field2");
141+
assertThat(groupSource, instanceOf(TermsGroupSource.class));
142+
assertEquals(groupSource.getField(), "term-field");
143+
}
110144
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot/GroupConfig.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ private static Map<String, SingleGroupSource<?>> parseGroupConfig(final XContent
139139

140140
// be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does)
141141
XContentParser.Token token;
142-
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
143-
token = parser.currentToken();
144-
} else {
142+
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
145143
token = parser.nextToken();
146144
if (token != XContentParser.Token.START_OBJECT) {
147145
throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: Expected START_OBJECT but was: " + token);

0 commit comments

Comments
 (0)