Skip to content

Commit f28f454

Browse files
authored
In the field capabilities API, re-add support for fields in the request body (#88972)
We previously removed support for `fields` in the request body, to ensure there was only one way to specify the parameter. We've now decided to undo the change, since it was disruptive and the request body is actually the best place to pass variable-length data like `fields`. This PR restores support for `fields` in the request body. It throws an error if the parameter is specified both in the URL and the body. Closes #86875
1 parent b81f418 commit f28f454

File tree

6 files changed

+109
-6
lines changed

6 files changed

+109
-6
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# Elasticsearch Changlog
1+
# Elasticsearch Changelog
22

33
Please see the [release notes](https://www.elastic.co/guide/en/elasticsearch/reference/current/es-release-notes.html) in the reference manual.

docs/changelog/88972.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 88972
2+
summary: In the field capabilities API, re-add support for fields in the request body
3+
area: Search
4+
type: enhancement
5+
issues:
6+
- 86875

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/field_caps/10_basic.yml

+15-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ setup:
121121

122122
---
123123
"Get simple field caps":
124-
125124
- do:
126125
field_caps:
127126
index: 'test1,test2,test3'
@@ -162,6 +161,21 @@ setup:
162161
- match: {fields.geo.keyword.indices: ["test3"]}
163162
- is_false: fields.geo.keyword.non_searchable_indices
164163
- is_false: fields.geo.keyword.on_aggregatable_indices
164+
165+
---
166+
"Get field caps with fields in body":
167+
- skip:
168+
version: " - 8.4.99"
169+
reason: re-added support for fields in the request body in 8.5
170+
- do:
171+
field_caps:
172+
index: 'test1,test2,test3'
173+
body:
174+
fields: [text]
175+
176+
- match: {fields.text.text.searchable: true}
177+
- match: {fields.text.text.aggregatable: false}
178+
- is_false: fields.keyword
165179
---
166180
"Get date_nanos field caps":
167181

server/src/main/java/org/elasticsearch/rest/action/RestFieldCapabilitiesAction.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder;
2424
import static org.elasticsearch.rest.RestRequest.Method.GET;
2525
import static org.elasticsearch.rest.RestRequest.Method.POST;
26+
import static org.elasticsearch.xcontent.ObjectParser.fromList;
2627

2728
public class RestFieldCapabilitiesAction extends BaseRestHandler {
2829

@@ -43,10 +44,9 @@ public String getName() {
4344

4445
@Override
4546
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
46-
String[] indices = Strings.splitStringByCommaToArray(request.param("index"));
47-
FieldCapabilitiesRequest fieldRequest = new FieldCapabilitiesRequest().fields(
48-
Strings.splitStringByCommaToArray(request.param("fields"))
49-
).indices(indices);
47+
final String[] indices = Strings.splitStringByCommaToArray(request.param("index"));
48+
final FieldCapabilitiesRequest fieldRequest = new FieldCapabilitiesRequest();
49+
fieldRequest.indices(indices);
5050

5151
fieldRequest.indicesOptions(IndicesOptions.fromRequest(request, fieldRequest.indicesOptions()));
5252
fieldRequest.includeUnmapped(request.paramAsBoolean("include_unmapped", false));
@@ -57,16 +57,28 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
5757
PARSER.parse(parser, fieldRequest, null);
5858
}
5959
});
60+
if (request.hasParam("fields")) {
61+
if (fieldRequest.fields().length > 0) {
62+
throw new IllegalArgumentException(
63+
"can't specify a request body and [fields]"
64+
+ " request parameter, either specify a request body or the"
65+
+ " [fields] request parameter"
66+
);
67+
}
68+
fieldRequest.fields(Strings.splitStringByCommaToArray(request.param("fields")));
69+
}
6070
return channel -> client.fieldCaps(fieldRequest, new RestToXContentListener<>(channel));
6171
}
6272

6373
private static ParseField INDEX_FILTER_FIELD = new ParseField("index_filter");
6474
private static ParseField RUNTIME_MAPPINGS_FIELD = new ParseField("runtime_mappings");
75+
private static ParseField FIELDS_FIELD = new ParseField("fields");
6576

6677
private static final ObjectParser<FieldCapabilitiesRequest, Void> PARSER = new ObjectParser<>("field_caps_request");
6778

6879
static {
6980
PARSER.declareObject(FieldCapabilitiesRequest::indexFilter, (p, c) -> parseInnerQueryBuilder(p), INDEX_FILTER_FIELD);
7081
PARSER.declareObject(FieldCapabilitiesRequest::runtimeFields, (p, c) -> p.map(), RUNTIME_MAPPINGS_FIELD);
82+
PARSER.declareStringArray(fromList(String.class, FieldCapabilitiesRequest::fields), FIELDS_FIELD);
7183
}
7284
}

server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequestTests.java

+17
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
import org.elasticsearch.index.query.QueryBuilders;
2020
import org.elasticsearch.search.SearchModule;
2121
import org.elasticsearch.test.AbstractWireSerializingTestCase;
22+
import org.elasticsearch.xcontent.ObjectParser;
23+
import org.elasticsearch.xcontent.ParseField;
2224
import org.elasticsearch.xcontent.ToXContent;
2325
import org.elasticsearch.xcontent.XContentBuilder;
2426
import org.elasticsearch.xcontent.XContentFactory;
27+
import org.elasticsearch.xcontent.XContentParser;
2528
import org.elasticsearch.xcontent.XContentType;
29+
import org.elasticsearch.xcontent.json.JsonXContent;
2630

2731
import java.io.IOException;
2832
import java.util.ArrayList;
@@ -31,6 +35,7 @@
3135
import java.util.function.Consumer;
3236

3337
import static java.util.Collections.singletonMap;
38+
import static org.elasticsearch.xcontent.ObjectParser.fromList;
3439
import static org.hamcrest.Matchers.allOf;
3540
import static org.hamcrest.Matchers.anyOf;
3641
import static org.hamcrest.Matchers.containsString;
@@ -146,6 +151,18 @@ public void testToXContent() throws IOException {
146151
}""").replaceAll("\\s+", ""), xContent);
147152
}
148153

154+
public void testFromXContent() throws IOException {
155+
XContentParser parser = createParser(JsonXContent.jsonXContent, "{ \"fields\" : [\"FOO\"] }");
156+
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest();
157+
ObjectParser<FieldCapabilitiesRequest, Void> PARSER = new ObjectParser<>("field_caps_request");
158+
PARSER.declareStringArray(fromList(String.class, FieldCapabilitiesRequest::fields), new ParseField("fields"));
159+
160+
PARSER.parse(parser, request, null);
161+
162+
assertArrayEquals(request.fields(), new String[] { "FOO" });
163+
164+
}
165+
149166
public void testValidation() {
150167
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest().indices("index2");
151168
ActionRequestValidationException exception = request.validate();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.rest.action;
10+
11+
import org.elasticsearch.client.internal.node.NodeClient;
12+
import org.elasticsearch.common.bytes.BytesArray;
13+
import org.elasticsearch.rest.RestRequest;
14+
import org.elasticsearch.test.ESTestCase;
15+
import org.elasticsearch.test.rest.FakeRestRequest;
16+
import org.elasticsearch.xcontent.XContentType;
17+
import org.junit.Before;
18+
19+
import java.io.IOException;
20+
import java.util.HashMap;
21+
22+
import static org.mockito.Mockito.mock;
23+
24+
public class RestFieldCapabilitiesActionTests extends ESTestCase {
25+
26+
private RestFieldCapabilitiesAction action;
27+
28+
@Before
29+
public void setUpAction() {
30+
action = new RestFieldCapabilitiesAction();
31+
}
32+
33+
public void testRequestBodyAndParamsBothInput() throws IOException {
34+
String content = "{ \"fields\": [\"title\"] }";
35+
HashMap<String, String> paramsMap = new HashMap<>();
36+
paramsMap.put("fields", "title");
37+
RestRequest request = new FakeRestRequest.Builder(xContentRegistry()).withPath("/_field_caps")
38+
.withParams(paramsMap)
39+
.withContent(new BytesArray(content), XContentType.JSON)
40+
.build();
41+
try {
42+
action.prepareRequest(request, mock(NodeClient.class));
43+
fail("expected failure");
44+
} catch (IllegalArgumentException e) {
45+
assertEquals(
46+
e.getMessage(),
47+
"can't specify a request body and [fields]"
48+
+ " request parameter, either specify a request body or the"
49+
+ " [fields] request parameter"
50+
);
51+
}
52+
53+
}
54+
}

0 commit comments

Comments
 (0)