diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java index 9b851574f73..2e6b67dbc19 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java @@ -20,10 +20,22 @@ package org.elasticsearch.client; import org.apache.http.HttpEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.Strings; import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.VersionType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; @@ -35,6 +47,8 @@ import java.util.StringJoiner; final class Request { + private static final String DELIMITER = "/"; + final String method; final String endpoint; final Map params; @@ -61,53 +75,53 @@ final class Request { } static Request exists(GetRequest getRequest) { - return new Request("HEAD", getEndpoint(getRequest), getParams(getRequest), null); + Request request = get(getRequest); + return new Request(HttpHead.METHOD_NAME, request.endpoint, request.params, null); } static Request get(GetRequest getRequest) { - return new Request("GET", getEndpoint(getRequest), getParams(getRequest), null); + String endpoint = endpoint(getRequest.index(), getRequest.type(), getRequest.id()); + + Params parameters = Params.builder(); + parameters.withPreference(getRequest.preference()); + parameters.withRouting(getRequest.routing()); + parameters.withParent(getRequest.parent()); + parameters.withRefresh(getRequest.refresh()); + parameters.withRealtime(getRequest.realtime()); + parameters.withStoredFields(getRequest.storedFields()); + parameters.withVersion(getRequest.version()); + parameters.withVersionType(getRequest.versionType()); + parameters.withFetchSourceContext(getRequest.fetchSourceContext()); + + return new Request(HttpGet.METHOD_NAME, endpoint, parameters.getParams(), null); + } + + static Request index(IndexRequest indexRequest) { + String method = Strings.hasLength(indexRequest.id()) ? HttpPut.METHOD_NAME : HttpPost.METHOD_NAME; + + boolean isCreate = (indexRequest.opType() == DocWriteRequest.OpType.CREATE); + String endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(), isCreate ? "_create" : null); + + Params parameters = Params.builder(); + parameters.withRouting(indexRequest.routing()); + parameters.withParent(indexRequest.parent()); + parameters.withTimeout(indexRequest.timeout()); + parameters.withVersion(indexRequest.version()); + parameters.withVersionType(indexRequest.versionType()); + parameters.withPipeline(indexRequest.getPipeline()); + parameters.withRefreshPolicy(indexRequest.getRefreshPolicy()); + parameters.withWaitForActiveShards(indexRequest.waitForActiveShards()); + + BytesRef source = indexRequest.source().toBytesRef(); + ContentType contentType = ContentType.create(indexRequest.getContentType().mediaType()); + HttpEntity entity = new ByteArrayEntity(source.bytes, source.offset, source.length, contentType); + + return new Request(method, endpoint, parameters.getParams(), entity); } static Request delete(DeleteRequest deleteRequest) { return new Request("DELETE", deleteEndpoint(deleteRequest), deleteParams(deleteRequest), null); - } - private static Map getParams(GetRequest getRequest) { - Map params = new HashMap<>(); - putParam("preference", getRequest.preference(), params); - putParam("routing", getRequest.routing(), params); - putParam("parent", getRequest.parent(), params); - if (getRequest.refresh()) { - params.put("refresh", Boolean.TRUE.toString()); - } - if (getRequest.realtime() == false) { - params.put("realtime", Boolean.FALSE.toString()); - } - if (getRequest.storedFields() != null && getRequest.storedFields().length > 0) { - params.put("stored_fields", String.join(",", getRequest.storedFields())); - } - if (getRequest.version() != Versions.MATCH_ANY) { - params.put("version", Long.toString(getRequest.version())); - } - if (getRequest.versionType() != VersionType.INTERNAL) { - params.put("version_type", getRequest.versionType().name().toLowerCase(Locale.ROOT)); - } - if (getRequest.fetchSourceContext() != null) { - FetchSourceContext fetchSourceContext = getRequest.fetchSourceContext(); - if (fetchSourceContext.fetchSource() == false) { - params.put("_source", Boolean.FALSE.toString()); - } - if (fetchSourceContext.includes() != null && fetchSourceContext.includes().length > 0) { - params.put("_source_include", String.join(",", fetchSourceContext.includes())); - } - if (fetchSourceContext.excludes() != null && fetchSourceContext.excludes().length > 0) { - params.put("_source_exclude", String.join(",", fetchSourceContext.excludes())); - } - } - return Collections.unmodifiableMap(params); - } - - private static Map deleteParams(DeleteRequest deleteRequest) { Map params = new HashMap<>(); putParam("routing", deleteRequest.routing(), params); putParam("parent", deleteRequest.parent(), params); @@ -118,21 +132,140 @@ final class Request { params.put("version_type", deleteRequest.versionType().name().toLowerCase(Locale.ROOT)); } return Collections.unmodifiableMap(params); + } - private static String getEndpoint(GetRequest getRequest) { - StringJoiner pathJoiner = new StringJoiner("/", "/", ""); - return pathJoiner.add(getRequest.index()).add(getRequest.type()).add(getRequest.id()).toString(); + /** + * Utility method to build request's endpoint. + */ + static String endpoint(String... parts) { + if (parts == null || parts.length == 0) { + return DELIMITER; + + StringJoiner joiner = new StringJoiner(DELIMITER, DELIMITER, ""); + for (String part : parts) { + if (part != null) { + joiner.add(part); + } + } + return joiner.toString(); } - private static String deleteEndpoint(DeleteRequest deleteRequest) { - StringJoiner pathJoiner = new StringJoiner("/", "/", ""); - return pathJoiner.add(deleteRequest.index()).add(deleteRequest.type()).add(deleteRequest.id()).toString(); - } + /** + * Utility class to build request's parameters map and centralize all parameter names. + */ + static class Params { + private final Map params = new HashMap<>(); - private static void putParam(String key, String value, Map params) { - if (Strings.hasLength(value)) { - params.put(key, value); + private Params() { + } + + Params putParam(String key, String value) { + if (Strings.hasLength(value)) { + if (params.putIfAbsent(key, value) != null) { + throw new IllegalArgumentException("Request parameter [" + key + "] is already registered"); + } + } + return this; + } + + Params putParam(String key, TimeValue value) { + if (value != null) { + return putParam(key, value.getStringRep()); + } + return this; + } + + Params withFetchSourceContext(FetchSourceContext fetchSourceContext) { + if (fetchSourceContext != null) { + if (fetchSourceContext.fetchSource() == false) { + putParam("_source", Boolean.FALSE.toString()); + } + if (fetchSourceContext.includes() != null && fetchSourceContext.includes().length > 0) { + putParam("_source_include", String.join(",", fetchSourceContext.includes())); + } + if (fetchSourceContext.excludes() != null && fetchSourceContext.excludes().length > 0) { + putParam("_source_exclude", String.join(",", fetchSourceContext.excludes())); + } + } + return this; + } + + Params withParent(String parent) { + return putParam("parent", parent); + } + + Params withPipeline(String pipeline) { + return putParam("pipeline", pipeline); + } + + Params withPreference(String preference) { + return putParam("preference", preference); + } + + Params withRealtime(boolean realtime) { + if (realtime == false) { + return putParam("realtime", Boolean.FALSE.toString()); + } + return this; + } + + Params withRefresh(boolean refresh) { + if (refresh) { + return withRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + } + return this; + } + + Params withRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) { + if (refreshPolicy != WriteRequest.RefreshPolicy.NONE) { + putParam("refresh", refreshPolicy.getValue()); + } + return this; + } + + Params withRouting(String routing) { + return putParam("routing", routing); + } + + Params withStoredFields(String[] storedFields) { + if (storedFields != null && storedFields.length > 0) { + return putParam("stored_fields", String.join(",", storedFields)); + } + return this; + } + + Params withTimeout(TimeValue timeout) { + return putParam("timeout", timeout); + } + + Params withVersion(long version) { + if (version != Versions.MATCH_ANY) { + return putParam("version", Long.toString(version)); + } + return this; + } + + Params withVersionType(VersionType versionType) { + if (versionType != VersionType.INTERNAL) { + return putParam("version_type", versionType.name().toLowerCase(Locale.ROOT)); + } + return this; + } + + Params withWaitForActiveShards(ActiveShardCount activeShardCount) { + if (activeShardCount != null && activeShardCount != ActiveShardCount.DEFAULT) { + return putParam("wait_for_active_shards", activeShardCount.toString().toLowerCase(Locale.ROOT)); + } + return this; + } + + Map getParams() { + return Collections.unmodifiableMap(params); + } + + static Params builder() { + return new Params(); } } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index 241676d257c..e60c47e8773 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -30,6 +30,8 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.main.MainRequest; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.xcontent.NamedXContentRegistry; @@ -44,6 +46,9 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; + /** * High level REST client that wraps an instance of the low level {@link RestClient} and allows to build requests and read responses. * The provided {@link RestClient} is externally built and closed. @@ -61,37 +66,61 @@ public class RestHighLevelClient { */ public boolean ping(Header... headers) throws IOException { return performRequest(new MainRequest(), (request) -> Request.ping(), RestHighLevelClient::convertExistsResponse, - Collections.emptySet(), headers); + emptySet(), headers); } /** - * Retrieves a document by id using the get api + * Retrieves a document by id using the Get API + * + * See Get API on elastic.co */ public GetResponse get(GetRequest getRequest, Header... headers) throws IOException { - return performRequestAndParseEntity(getRequest, Request::get, GetResponse::fromXContent, Collections.singleton(404), headers); + return performRequestAndParseEntity(getRequest, Request::get, GetResponse::fromXContent, singleton(404), headers); } /** - * Asynchronously retrieves a document by id using the get api + * Asynchronously retrieves a document by id using the Get API + * + * See Get API on elastic.co */ public void getAsync(GetRequest getRequest, ActionListener listener, Header... headers) { - performRequestAsyncAndParseEntity(getRequest, Request::get, GetResponse::fromXContent, listener, - Collections.singleton(404), headers); + performRequestAsyncAndParseEntity(getRequest, Request::get, GetResponse::fromXContent, listener, singleton(404), headers); } /** * Checks for the existence of a document. Returns true if it exists, false otherwise + * + * See Get API on elastic.co */ public boolean exists(GetRequest getRequest, Header... headers) throws IOException { - return performRequest(getRequest, Request::exists, RestHighLevelClient::convertExistsResponse, Collections.emptySet(), headers); + return performRequest(getRequest, Request::exists, RestHighLevelClient::convertExistsResponse, emptySet(), headers); } /** * Asynchronously checks for the existence of a document. Returns true if it exists, false otherwise + * + * See Get API on elastic.co */ public void existsAsync(GetRequest getRequest, ActionListener listener, Header... headers) { - performRequestAsync(getRequest, Request::exists, RestHighLevelClient::convertExistsResponse, listener, - Collections.emptySet(), headers); + performRequestAsync(getRequest, Request::exists, RestHighLevelClient::convertExistsResponse, listener, emptySet(), headers); + } + + /** + * Index a document using the Index API + * + * See Index API on elastic.co + */ + public IndexResponse index(IndexRequest indexRequest, Header... headers) throws IOException { + return performRequestAndParseEntity(indexRequest, Request::index, IndexResponse::fromXContent, emptySet(), headers); + } + + /** + * Asynchronously index a document using the Index API + * + * See Index API on elastic.co + */ + public void indexAsync(IndexRequest indexRequest, ActionListener listener, Header... headers) { + performRequestAsyncAndParseEntity(indexRequest, Request::index, IndexResponse::fromXContent, listener, emptySet(), headers); } /** diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java index 07036c27e4a..7f643cf7827 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java @@ -22,12 +22,19 @@ package org.elasticsearch.client; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.VersionType; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; @@ -145,6 +152,122 @@ public class CrudIT extends ESRestHighLevelClientTestCase { } } + public void testIndex() throws IOException { + final XContentType xContentType = randomFrom(XContentType.values()); + { + IndexRequest indexRequest = new IndexRequest("index", "type"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("test", "test").endObject()); + + IndexResponse indexResponse = execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + assertEquals(RestStatus.CREATED, indexResponse.status()); + assertEquals(DocWriteResponse.Result.CREATED, indexResponse.getResult()); + assertEquals("index", indexResponse.getIndex()); + assertEquals("type", indexResponse.getType()); + assertTrue(Strings.hasLength(indexResponse.getId())); + assertEquals(1L, indexResponse.getVersion()); + assertNotNull(indexResponse.getShardId()); + assertEquals(-1, indexResponse.getShardId().getId()); + assertEquals("index", indexResponse.getShardId().getIndexName()); + assertEquals("index", indexResponse.getShardId().getIndex().getName()); + assertEquals("_na_", indexResponse.getShardId().getIndex().getUUID()); + assertNotNull(indexResponse.getShardInfo()); + assertEquals(0, indexResponse.getShardInfo().getFailed()); + assertTrue(indexResponse.getShardInfo().getSuccessful() > 0); + assertTrue(indexResponse.getShardInfo().getTotal() > 0); + } + { + IndexRequest indexRequest = new IndexRequest("index", "type", "id"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("version", 1).endObject()); + + IndexResponse indexResponse = execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + assertEquals(RestStatus.CREATED, indexResponse.status()); + assertEquals("index", indexResponse.getIndex()); + assertEquals("type", indexResponse.getType()); + assertEquals("id", indexResponse.getId()); + assertEquals(1L, indexResponse.getVersion()); + + indexRequest = new IndexRequest("index", "type", "id"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("version", 2).endObject()); + + indexResponse = execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + assertEquals(RestStatus.OK, indexResponse.status()); + assertEquals("index", indexResponse.getIndex()); + assertEquals("type", indexResponse.getType()); + assertEquals("id", indexResponse.getId()); + assertEquals(2L, indexResponse.getVersion()); + + ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class, () -> { + IndexRequest wrongRequest = new IndexRequest("index", "type", "id"); + wrongRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("field", "test").endObject()); + wrongRequest.version(5L); + + execute(wrongRequest, highLevelClient()::index, highLevelClient()::indexAsync); + }); + assertEquals(RestStatus.CONFLICT, exception.status()); + assertEquals("Elasticsearch exception [type=version_conflict_engine_exception, reason=[type][id]: " + + "version conflict, current version [2] is different than the one provided [5]]", exception.getMessage()); + assertEquals("index", exception.getMetadata("es.index").get(0)); + } + { + ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class, () -> { + IndexRequest indexRequest = new IndexRequest("index", "type", "missing_parent"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("field", "test").endObject()); + indexRequest.parent("missing"); + + execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + }); + + assertEquals(RestStatus.BAD_REQUEST, exception.status()); + assertEquals("Elasticsearch exception [type=illegal_argument_exception, " + + "reason=Can't specify parent if no parent field has been configured]", exception.getMessage()); + } + { + ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class, () -> { + IndexRequest indexRequest = new IndexRequest("index", "type", "missing_pipeline"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("field", "test").endObject()); + indexRequest.setPipeline("missing"); + + execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + }); + + assertEquals(RestStatus.BAD_REQUEST, exception.status()); + assertEquals("Elasticsearch exception [type=illegal_argument_exception, " + + "reason=pipeline with id [missing] does not exist]", exception.getMessage()); + } + { + IndexRequest indexRequest = new IndexRequest("index", "type", "external_version_type"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("field", "test").endObject()); + indexRequest.version(12L); + indexRequest.versionType(VersionType.EXTERNAL); + + IndexResponse indexResponse = execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + assertEquals(RestStatus.CREATED, indexResponse.status()); + assertEquals("index", indexResponse.getIndex()); + assertEquals("type", indexResponse.getType()); + assertEquals("external_version_type", indexResponse.getId()); + assertEquals(12L, indexResponse.getVersion()); + } + { + final IndexRequest indexRequest = new IndexRequest("index", "type", "with_create_op_type"); + indexRequest.source(XContentBuilder.builder(xContentType.xContent()).startObject().field("field", "test").endObject()); + indexRequest.opType(DocWriteRequest.OpType.CREATE); + + IndexResponse indexResponse = execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + assertEquals(RestStatus.CREATED, indexResponse.status()); + assertEquals("index", indexResponse.getIndex()); + assertEquals("type", indexResponse.getType()); + assertEquals("with_create_op_type", indexResponse.getId()); + + ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class, () -> { + execute(indexRequest, highLevelClient()::index, highLevelClient()::indexAsync); + }); + + assertEquals(RestStatus.CONFLICT, exception.status()); + assertEquals("Elasticsearch exception [type=version_conflict_engine_exception, reason=[type][with_create_op_type]: " + + "version conflict, document already exists (current version [1])]", exception.getMessage()); + } + } + public void testDelete() throws IOException { { DeleteRequest deleteRequest = new DeleteRequest("index", "type", "does_not_exist"); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java index 9c3a1adb16e..f9bf4cc1a39 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java @@ -19,12 +19,23 @@ package org.elasticsearch.client; +import org.apache.http.HttpEntity; +import org.apache.http.entity.ByteArrayEntity; +import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.replication.ReplicationRequest; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.VersionType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.test.ESTestCase; +import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -155,4 +166,145 @@ public class RequestTests extends ESTestCase { assertNull(request.entity); assertEquals(method, request.method); } + + public void testIndex() throws IOException { + String index = randomAsciiOfLengthBetween(3, 10); + String type = randomAsciiOfLengthBetween(3, 10); + IndexRequest indexRequest = new IndexRequest(index, type); + + String id = randomBoolean() ? randomAsciiOfLengthBetween(3, 10) : null; + indexRequest.id(id); + + Map expectedParams = new HashMap<>(); + + String method = "POST"; + if (id != null) { + method = "PUT"; + if (randomBoolean()) { + indexRequest.opType(DocWriteRequest.OpType.CREATE); + } + } + + // There is some logic around _create endpoint and version/version type + if (indexRequest.opType() == DocWriteRequest.OpType.CREATE) { + indexRequest.version(randomFrom(Versions.MATCH_ANY, Versions.MATCH_DELETED)); + expectedParams.put("version", Long.toString(Versions.MATCH_DELETED)); + } else { + if (randomBoolean()) { + long version = randomFrom(Versions.MATCH_ANY, Versions.MATCH_DELETED, Versions.NOT_FOUND, randomNonNegativeLong()); + indexRequest.version(version); + if (version != Versions.MATCH_ANY) { + expectedParams.put("version", Long.toString(version)); + } + } + if (randomBoolean()) { + VersionType versionType = randomFrom(VersionType.values()); + indexRequest.versionType(versionType); + if (versionType != VersionType.INTERNAL) { + expectedParams.put("version_type", versionType.name().toLowerCase(Locale.ROOT)); + } + } + } + + if (randomBoolean()) { + String timeout = randomTimeValue(); + indexRequest.timeout(timeout); + expectedParams.put("timeout", timeout); + } else { + expectedParams.put("timeout", ReplicationRequest.DEFAULT_TIMEOUT.getStringRep()); + } + + if (frequently()) { + if (randomBoolean()) { + String routing = randomAsciiOfLengthBetween(3, 10); + indexRequest.routing(routing); + expectedParams.put("routing", routing); + } + if (randomBoolean()) { + String parent = randomAsciiOfLengthBetween(3, 10); + indexRequest.parent(parent); + expectedParams.put("parent", parent); + } + if (randomBoolean()) { + String pipeline = randomAsciiOfLengthBetween(3, 10); + indexRequest.setPipeline(pipeline); + expectedParams.put("pipeline", pipeline); + } + + if (randomBoolean()) { + WriteRequest.RefreshPolicy refreshPolicy = randomFrom(WriteRequest.RefreshPolicy.values()); + indexRequest.setRefreshPolicy(refreshPolicy); + if (refreshPolicy != WriteRequest.RefreshPolicy.NONE) { + expectedParams.put("refresh", refreshPolicy.getValue()); + } + } + } + + XContentType xContentType = randomFrom(XContentType.values()); + int nbFields = randomIntBetween(0, 10); + try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) { + builder.startObject(); + for (int i = 0; i < nbFields; i++) { + builder.field("field_" + i, i); + } + builder.endObject(); + indexRequest.source(builder); + } + + Request request = Request.index(indexRequest); + if (indexRequest.opType() == DocWriteRequest.OpType.CREATE) { + assertEquals("/" + index + "/" + type + "/" + id + "/_create", request.endpoint); + } else if (id != null) { + assertEquals("/" + index + "/" + type + "/" + id, request.endpoint); + } else { + assertEquals("/" + index + "/" + type, request.endpoint); + } + assertEquals(expectedParams, request.params); + assertEquals(method, request.method); + + HttpEntity entity = request.entity; + assertNotNull(entity); + assertTrue(entity instanceof ByteArrayEntity); + + try (XContentParser parser = createParser(xContentType.xContent(), entity.getContent())) { + assertEquals(nbFields, parser.map().size()); + } + } + + public void testParams() { + final int nbParams = randomIntBetween(0, 10); + Request.Params params = Request.Params.builder(); + Map expectedParams = new HashMap<>(); + for (int i = 0; i < nbParams; i++) { + String paramName = "p_" + i; + String paramValue = randomAsciiOfLength(5); + params.putParam(paramName, paramValue); + expectedParams.put(paramName, paramValue); + } + + Map requestParams = params.getParams(); + assertEquals(nbParams, requestParams.size()); + assertEquals(expectedParams, requestParams); + } + + public void testParamsNoDuplicates() { + Request.Params params = Request.Params.builder(); + params.putParam("test", "1"); + + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> params.putParam("test", "2")); + assertEquals("Request parameter [test] is already registered", e.getMessage()); + + Map requestParams = params.getParams(); + assertEquals(1L, requestParams.size()); + assertEquals("1", requestParams.values().iterator().next()); + } + + public void testEndpoint() { + assertEquals("/", Request.endpoint()); + assertEquals("/", Request.endpoint(Strings.EMPTY_ARRAY)); + assertEquals("/", Request.endpoint("")); + assertEquals("/a/b", Request.endpoint("a", "b")); + assertEquals("/a/b/_create", Request.endpoint("a", "b", "_create")); + assertEquals("/a/b/c/_create", Request.endpoint("a", "b", "c", "_create")); + } } \ No newline at end of file diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/110_field_collapsing.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/110_field_collapsing.yaml index fac708a4dfc..dd17399d31a 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/110_field_collapsing.yaml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/110_field_collapsing.yaml @@ -46,7 +46,7 @@ setup: "field collapsing": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -82,7 +82,7 @@ setup: "field collapsing and from": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -107,7 +107,7 @@ setup: "field collapsing and inner_hits": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -152,7 +152,7 @@ setup: "field collapsing, inner_hits and maxConcurrentGroupRequests": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -197,7 +197,7 @@ setup: "field collapsing and scroll": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -213,7 +213,7 @@ setup: "field collapsing and search_after": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: @@ -230,7 +230,7 @@ setup: "field collapsing and rescore": - skip: - version: " - 5.99.99" + version: " - 5.2.99" reason: this uses a new API that has been added in 5.3 - do: