All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.algolia.api.SearchClient Maven / Gradle / Ivy

The newest version!
// Code generated by OpenAPI Generator (https://openapi-generator.tech), manual changes will be lost
// - read more on https://github.com/algolia/api-clients-automation. DO NOT EDIT.

package com.algolia.api;

import com.algolia.ApiClient;
import com.algolia.config.*;
import com.algolia.config.ClientOptions;
import com.algolia.exceptions.*;
import com.algolia.internal.JsonSerializer;
import com.algolia.model.search.*;
import com.algolia.utils.*;
import com.fasterxml.jackson.core.type.TypeReference;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.function.IntUnaryOperator;
import java.util.regex.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SearchClient extends ApiClient {

  public SearchClient(String appId, String apiKey) {
    this(appId, apiKey, null);
  }

  public SearchClient(String appId, String apiKey, ClientOptions options) {
    super(
      appId,
      apiKey,
      "Search",
      options,
      getDefaultHosts(appId),
      Duration.ofMillis(2000L),
      Duration.ofMillis(5000L),
      Duration.ofMillis(30000L)
    );
  }

  private static List getDefaultHosts(String appId) {
    List hosts = new ArrayList<>();
    hosts.add(new Host(appId + "-dsn.algolia.net", EnumSet.of(CallType.READ)));
    hosts.add(new Host(appId + ".algolia.net", EnumSet.of(CallType.WRITE)));

    List commonHosts = new ArrayList<>();
    commonHosts.add(new Host(appId + "-1.algolianet.com", EnumSet.of(CallType.READ, CallType.WRITE)));
    commonHosts.add(new Host(appId + "-2.algolianet.com", EnumSet.of(CallType.READ, CallType.WRITE)));
    commonHosts.add(new Host(appId + "-3.algolianet.com", EnumSet.of(CallType.READ, CallType.WRITE)));

    Collections.shuffle(commonHosts, new Random());

    return Stream.concat(hosts.stream(), commonHosts.stream()).collect(Collectors.toList());
  }

  /**
   * Creates a new API key with specific permissions and restrictions.
   *
   * @param apiKey (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public AddApiKeyResponse addApiKey(@Nonnull ApiKey apiKey, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(addApiKeyAsync(apiKey, requestOptions));
  }

  /**
   * Creates a new API key with specific permissions and restrictions.
   *
   * @param apiKey (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public AddApiKeyResponse addApiKey(@Nonnull ApiKey apiKey) throws AlgoliaRuntimeException {
    return this.addApiKey(apiKey, null);
  }

  /**
   * (asynchronously) Creates a new API key with specific permissions and restrictions.
   *
   * @param apiKey (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture addApiKeyAsync(@Nonnull ApiKey apiKey, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(apiKey, "Parameter `apiKey` is required when calling `addApiKey`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/keys").setMethod("POST").setBody(apiKey).build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Creates a new API key with specific permissions and restrictions.
   *
   * @param apiKey (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture addApiKeyAsync(@Nonnull ApiKey apiKey) throws AlgoliaRuntimeException {
    return this.addApiKeyAsync(apiKey, null);
  }

  /**
   * If a record with the specified object ID exists, the existing record is replaced. Otherwise, a
   * new record is added to the index. If you want to use auto-generated object IDs, use the
   * [`saveObject` operation](#tag/Records/operation/saveObject). To update _some_ attributes of an
   * existing record, use the [`partial` operation](#tag/Records/operation/partialUpdateObject)
   * instead. To add, update, or replace multiple records, use the [`batch`
   * operation](#tag/Records/operation/batch).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse addOrUpdateObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object body,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(addOrUpdateObjectAsync(indexName, objectID, body, requestOptions));
  }

  /**
   * If a record with the specified object ID exists, the existing record is replaced. Otherwise, a
   * new record is added to the index. If you want to use auto-generated object IDs, use the
   * [`saveObject` operation](#tag/Records/operation/saveObject). To update _some_ attributes of an
   * existing record, use the [`partial` operation](#tag/Records/operation/partialUpdateObject)
   * instead. To add, update, or replace multiple records, use the [`batch`
   * operation](#tag/Records/operation/batch).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse addOrUpdateObject(@Nonnull String indexName, @Nonnull String objectID, @Nonnull Object body)
    throws AlgoliaRuntimeException {
    return this.addOrUpdateObject(indexName, objectID, body, null);
  }

  /**
   * (asynchronously) If a record with the specified object ID exists, the existing record is
   * replaced. Otherwise, a new record is added to the index. If you want to use auto-generated
   * object IDs, use the [`saveObject` operation](#tag/Records/operation/saveObject). To update
   * _some_ attributes of an existing record, use the [`partial`
   * operation](#tag/Records/operation/partialUpdateObject) instead. To add, update, or replace
   * multiple records, use the [`batch` operation](#tag/Records/operation/batch).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture addOrUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object body,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `addOrUpdateObject`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `addOrUpdateObject`.");

    Parameters.requireNonNull(body, "Parameter `body` is required when calling `addOrUpdateObject`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/{objectID}", indexName, objectID)
      .setMethod("PUT")
      .setBody(body)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) If a record with the specified object ID exists, the existing record is
   * replaced. Otherwise, a new record is added to the index. If you want to use auto-generated
   * object IDs, use the [`saveObject` operation](#tag/Records/operation/saveObject). To update
   * _some_ attributes of an existing record, use the [`partial`
   * operation](#tag/Records/operation/partialUpdateObject) instead. To add, update, or replace
   * multiple records, use the [`batch` operation](#tag/Records/operation/batch).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture addOrUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object body
  ) throws AlgoliaRuntimeException {
    return this.addOrUpdateObjectAsync(indexName, objectID, body, null);
  }

  /**
   * Adds a source to the list of allowed sources.
   *
   * @param source Source to add. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse appendSource(@Nonnull Source source, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(appendSourceAsync(source, requestOptions));
  }

  /**
   * Adds a source to the list of allowed sources.
   *
   * @param source Source to add. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse appendSource(@Nonnull Source source) throws AlgoliaRuntimeException {
    return this.appendSource(source, null);
  }

  /**
   * (asynchronously) Adds a source to the list of allowed sources.
   *
   * @param source Source to add. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture appendSourceAsync(@Nonnull Source source, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(source, "Parameter `source` is required when calling `appendSource`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/security/sources/append").setMethod("POST").setBody(source).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds a source to the list of allowed sources.
   *
   * @param source Source to add. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture appendSourceAsync(@Nonnull Source source) throws AlgoliaRuntimeException {
    return this.appendSourceAsync(source, null);
  }

  /**
   * Assigns or moves a user ID to a cluster. The time it takes to move a user is proportional to
   * the amount of data linked to the user ID.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param assignUserIdParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse assignUserId(
    @Nonnull String xAlgoliaUserID,
    @Nonnull AssignUserIdParams assignUserIdParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(assignUserIdAsync(xAlgoliaUserID, assignUserIdParams, requestOptions));
  }

  /**
   * Assigns or moves a user ID to a cluster. The time it takes to move a user is proportional to
   * the amount of data linked to the user ID.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param assignUserIdParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse assignUserId(@Nonnull String xAlgoliaUserID, @Nonnull AssignUserIdParams assignUserIdParams)
    throws AlgoliaRuntimeException {
    return this.assignUserId(xAlgoliaUserID, assignUserIdParams, null);
  }

  /**
   * (asynchronously) Assigns or moves a user ID to a cluster. The time it takes to move a user is
   * proportional to the amount of data linked to the user ID.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param assignUserIdParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture assignUserIdAsync(
    @Nonnull String xAlgoliaUserID,
    @Nonnull AssignUserIdParams assignUserIdParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(xAlgoliaUserID, "Parameter `xAlgoliaUserID` is required when calling `assignUserId`.");

    Parameters.requireNonNull(assignUserIdParams, "Parameter `assignUserIdParams` is required when calling `assignUserId`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/clusters/mapping")
      .setMethod("POST")
      .setBody(assignUserIdParams)
      .addHeader("X-Algolia-User-ID", xAlgoliaUserID)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Assigns or moves a user ID to a cluster. The time it takes to move a user is
   * proportional to the amount of data linked to the user ID.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param assignUserIdParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture assignUserIdAsync(
    @Nonnull String xAlgoliaUserID,
    @Nonnull AssignUserIdParams assignUserIdParams
  ) throws AlgoliaRuntimeException {
    return this.assignUserIdAsync(xAlgoliaUserID, assignUserIdParams, null);
  }

  /**
   * Adds, updates, or deletes records in one index with a single API request. Batching index
   * updates reduces latency and increases data integrity. - Actions are applied in the order
   * they're specified. - Actions are equivalent to the individual API requests of the same name.
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param batchWriteParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public BatchResponse batch(@Nonnull String indexName, @Nonnull BatchWriteParams batchWriteParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(batchAsync(indexName, batchWriteParams, requestOptions));
  }

  /**
   * Adds, updates, or deletes records in one index with a single API request. Batching index
   * updates reduces latency and increases data integrity. - Actions are applied in the order
   * they're specified. - Actions are equivalent to the individual API requests of the same name.
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param batchWriteParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public BatchResponse batch(@Nonnull String indexName, @Nonnull BatchWriteParams batchWriteParams) throws AlgoliaRuntimeException {
    return this.batch(indexName, batchWriteParams, null);
  }

  /**
   * (asynchronously) Adds, updates, or deletes records in one index with a single API request.
   * Batching index updates reduces latency and increases data integrity. - Actions are applied in
   * the order they're specified. - Actions are equivalent to the individual API requests of the
   * same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param batchWriteParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchAsync(
    @Nonnull String indexName,
    @Nonnull BatchWriteParams batchWriteParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `batch`.");

    Parameters.requireNonNull(batchWriteParams, "Parameter `batchWriteParams` is required when calling `batch`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/batch", indexName)
      .setMethod("POST")
      .setBody(batchWriteParams)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds, updates, or deletes records in one index with a single API request.
   * Batching index updates reduces latency and increases data integrity. - Actions are applied in
   * the order they're specified. - Actions are equivalent to the individual API requests of the
   * same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param batchWriteParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchAsync(@Nonnull String indexName, @Nonnull BatchWriteParams batchWriteParams)
    throws AlgoliaRuntimeException {
    return this.batchAsync(indexName, batchWriteParams, null);
  }

  /**
   * Assigns multiple user IDs to a cluster. **You can't move users with this operation**.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param batchAssignUserIdsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse batchAssignUserIds(
    @Nonnull String xAlgoliaUserID,
    @Nonnull BatchAssignUserIdsParams batchAssignUserIdsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(batchAssignUserIdsAsync(xAlgoliaUserID, batchAssignUserIdsParams, requestOptions));
  }

  /**
   * Assigns multiple user IDs to a cluster. **You can't move users with this operation**.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param batchAssignUserIdsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CreatedAtResponse batchAssignUserIds(@Nonnull String xAlgoliaUserID, @Nonnull BatchAssignUserIdsParams batchAssignUserIdsParams)
    throws AlgoliaRuntimeException {
    return this.batchAssignUserIds(xAlgoliaUserID, batchAssignUserIdsParams, null);
  }

  /**
   * (asynchronously) Assigns multiple user IDs to a cluster. **You can't move users with this
   * operation**.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param batchAssignUserIdsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchAssignUserIdsAsync(
    @Nonnull String xAlgoliaUserID,
    @Nonnull BatchAssignUserIdsParams batchAssignUserIdsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(xAlgoliaUserID, "Parameter `xAlgoliaUserID` is required when calling `batchAssignUserIds`.");

    Parameters.requireNonNull(
      batchAssignUserIdsParams,
      "Parameter `batchAssignUserIdsParams` is required when calling `batchAssignUserIds`."
    );

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/clusters/mapping/batch")
      .setMethod("POST")
      .setBody(batchAssignUserIdsParams)
      .addHeader("X-Algolia-User-ID", xAlgoliaUserID)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Assigns multiple user IDs to a cluster. **You can't move users with this
   * operation**.
   *
   * @param xAlgoliaUserID Unique identifier of the user who makes the search request. (required)
   * @param batchAssignUserIdsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchAssignUserIdsAsync(
    @Nonnull String xAlgoliaUserID,
    @Nonnull BatchAssignUserIdsParams batchAssignUserIdsParams
  ) throws AlgoliaRuntimeException {
    return this.batchAssignUserIdsAsync(xAlgoliaUserID, batchAssignUserIdsParams, null);
  }

  /**
   * Adds or deletes multiple entries from your plurals, segmentation, or stop word dictionaries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param batchDictionaryEntriesParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse batchDictionaryEntries(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull BatchDictionaryEntriesParams batchDictionaryEntriesParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(batchDictionaryEntriesAsync(dictionaryName, batchDictionaryEntriesParams, requestOptions));
  }

  /**
   * Adds or deletes multiple entries from your plurals, segmentation, or stop word dictionaries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param batchDictionaryEntriesParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse batchDictionaryEntries(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull BatchDictionaryEntriesParams batchDictionaryEntriesParams
  ) throws AlgoliaRuntimeException {
    return this.batchDictionaryEntries(dictionaryName, batchDictionaryEntriesParams, null);
  }

  /**
   * (asynchronously) Adds or deletes multiple entries from your plurals, segmentation, or stop word
   * dictionaries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param batchDictionaryEntriesParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchDictionaryEntriesAsync(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull BatchDictionaryEntriesParams batchDictionaryEntriesParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(dictionaryName, "Parameter `dictionaryName` is required when calling `batchDictionaryEntries`.");

    Parameters.requireNonNull(
      batchDictionaryEntriesParams,
      "Parameter `batchDictionaryEntriesParams` is required when calling" + " `batchDictionaryEntries`."
    );

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/dictionaries/{dictionaryName}/batch", dictionaryName)
      .setMethod("POST")
      .setBody(batchDictionaryEntriesParams)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds or deletes multiple entries from your plurals, segmentation, or stop word
   * dictionaries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param batchDictionaryEntriesParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture batchDictionaryEntriesAsync(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull BatchDictionaryEntriesParams batchDictionaryEntriesParams
  ) throws AlgoliaRuntimeException {
    return this.batchDictionaryEntriesAsync(dictionaryName, batchDictionaryEntriesParams, null);
  }

  /**
   * Retrieves records from an index, up to 1,000 per request. While searching retrieves _hits_
   * (records augmented with attributes for highlighting and ranking details), browsing _just_
   * returns matching records. This can be useful if you want to export your indices. - The
   * Analytics API doesn't collect data when using `browse`. - Records are ranked by attributes and
   * custom ranking. - There's no ranking for: typo-tolerance, number of matched words, proximity,
   * geo distance. Browse requests automatically apply these settings: - `advancedSyntax`: `false` -
   * `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` - `distinct`: `false` -
   * `enablePersonalization`: `false` - `enableRules`: `false` - `facets`: `[]` - `getRankingInfo`:
   * `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` - `typoTolerance`: `true` or
   * `false` (`min` and `strict` evaluate to `true`) If you send these parameters with your browse
   * requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param browseParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  BrowseResponse browse(
    @Nonnull String indexName,
    BrowseParams browseParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(browseAsync(indexName, browseParams, innerType, requestOptions));
  }

  /**
   * Retrieves records from an index, up to 1,000 per request. While searching retrieves _hits_
   * (records augmented with attributes for highlighting and ranking details), browsing _just_
   * returns matching records. This can be useful if you want to export your indices. - The
   * Analytics API doesn't collect data when using `browse`. - Records are ranked by attributes and
   * custom ranking. - There's no ranking for: typo-tolerance, number of matched words, proximity,
   * geo distance. Browse requests automatically apply these settings: - `advancedSyntax`: `false` -
   * `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` - `distinct`: `false` -
   * `enablePersonalization`: `false` - `enableRules`: `false` - `facets`: `[]` - `getRankingInfo`:
   * `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` - `typoTolerance`: `true` or
   * `false` (`min` and `strict` evaluate to `true`) If you send these parameters with your browse
   * requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param browseParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  BrowseResponse browse(@Nonnull String indexName, BrowseParams browseParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.browse(indexName, browseParams, innerType, null);
  }

  /**
   * Retrieves records from an index, up to 1,000 per request. While searching retrieves _hits_
   * (records augmented with attributes for highlighting and ranking details), browsing _just_
   * returns matching records. This can be useful if you want to export your indices. - The
   * Analytics API doesn't collect data when using `browse`. - Records are ranked by attributes and
   * custom ranking. - There's no ranking for: typo-tolerance, number of matched words, proximity,
   * geo distance. Browse requests automatically apply these settings: - `advancedSyntax`: `false` -
   * `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` - `distinct`: `false` -
   * `enablePersonalization`: `false` - `enableRules`: `false` - `facets`: `[]` - `getRankingInfo`:
   * `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` - `typoTolerance`: `true` or
   * `false` (`min` and `strict` evaluate to `true`) If you send these parameters with your browse
   * requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  BrowseResponse browse(@Nonnull String indexName, Class innerType, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.browse(indexName, null, innerType, requestOptions);
  }

  /**
   * Retrieves records from an index, up to 1,000 per request. While searching retrieves _hits_
   * (records augmented with attributes for highlighting and ranking details), browsing _just_
   * returns matching records. This can be useful if you want to export your indices. - The
   * Analytics API doesn't collect data when using `browse`. - Records are ranked by attributes and
   * custom ranking. - There's no ranking for: typo-tolerance, number of matched words, proximity,
   * geo distance. Browse requests automatically apply these settings: - `advancedSyntax`: `false` -
   * `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` - `distinct`: `false` -
   * `enablePersonalization`: `false` - `enableRules`: `false` - `facets`: `[]` - `getRankingInfo`:
   * `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` - `typoTolerance`: `true` or
   * `false` (`min` and `strict` evaluate to `true`) If you send these parameters with your browse
   * requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  BrowseResponse browse(@Nonnull String indexName, Class innerType) throws AlgoliaRuntimeException {
    return this.browse(indexName, null, innerType, null);
  }

  /**
   * (asynchronously) Retrieves records from an index, up to 1,000 per request. While searching
   * retrieves _hits_ (records augmented with attributes for highlighting and ranking details),
   * browsing _just_ returns matching records. This can be useful if you want to export your
   * indices. - The Analytics API doesn't collect data when using `browse`. - Records are ranked by
   * attributes and custom ranking. - There's no ranking for: typo-tolerance, number of matched
   * words, proximity, geo distance. Browse requests automatically apply these settings: -
   * `advancedSyntax`: `false` - `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` -
   * `distinct`: `false` - `enablePersonalization`: `false` - `enableRules`: `false` - `facets`:
   * `[]` - `getRankingInfo`: `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` -
   * `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) If you send these
   * parameters with your browse requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param browseParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> browseAsync(
    @Nonnull String indexName,
    BrowseParams browseParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `browse`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/browse", indexName)
      .setMethod("POST")
      .setBody(browseParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, BrowseResponse.class, innerType);
  }

  /**
   * (asynchronously) Retrieves records from an index, up to 1,000 per request. While searching
   * retrieves _hits_ (records augmented with attributes for highlighting and ranking details),
   * browsing _just_ returns matching records. This can be useful if you want to export your
   * indices. - The Analytics API doesn't collect data when using `browse`. - Records are ranked by
   * attributes and custom ranking. - There's no ranking for: typo-tolerance, number of matched
   * words, proximity, geo distance. Browse requests automatically apply these settings: -
   * `advancedSyntax`: `false` - `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` -
   * `distinct`: `false` - `enablePersonalization`: `false` - `enableRules`: `false` - `facets`:
   * `[]` - `getRankingInfo`: `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` -
   * `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) If you send these
   * parameters with your browse requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param browseParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> browseAsync(@Nonnull String indexName, BrowseParams browseParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.browseAsync(indexName, browseParams, innerType, null);
  }

  /**
   * (asynchronously) Retrieves records from an index, up to 1,000 per request. While searching
   * retrieves _hits_ (records augmented with attributes for highlighting and ranking details),
   * browsing _just_ returns matching records. This can be useful if you want to export your
   * indices. - The Analytics API doesn't collect data when using `browse`. - Records are ranked by
   * attributes and custom ranking. - There's no ranking for: typo-tolerance, number of matched
   * words, proximity, geo distance. Browse requests automatically apply these settings: -
   * `advancedSyntax`: `false` - `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` -
   * `distinct`: `false` - `enablePersonalization`: `false` - `enableRules`: `false` - `facets`:
   * `[]` - `getRankingInfo`: `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` -
   * `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) If you send these
   * parameters with your browse requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> browseAsync(@Nonnull String indexName, Class innerType, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.browseAsync(indexName, null, innerType, requestOptions);
  }

  /**
   * (asynchronously) Retrieves records from an index, up to 1,000 per request. While searching
   * retrieves _hits_ (records augmented with attributes for highlighting and ranking details),
   * browsing _just_ returns matching records. This can be useful if you want to export your
   * indices. - The Analytics API doesn't collect data when using `browse`. - Records are ranked by
   * attributes and custom ranking. - There's no ranking for: typo-tolerance, number of matched
   * words, proximity, geo distance. Browse requests automatically apply these settings: -
   * `advancedSyntax`: `false` - `attributesToHighlight`: `[]` - `attributesToSnippet`: `[]` -
   * `distinct`: `false` - `enablePersonalization`: `false` - `enableRules`: `false` - `facets`:
   * `[]` - `getRankingInfo`: `false` - `ignorePlurals`: `false` - `optionalFilters`: `[]` -
   * `typoTolerance`: `true` or `false` (`min` and `strict` evaluate to `true`) If you send these
   * parameters with your browse requests, they'll be ignored.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> browseAsync(@Nonnull String indexName, Class innerType)
    throws AlgoliaRuntimeException {
    return this.browseAsync(indexName, null, innerType, null);
  }

  /**
   * Deletes only the records from an index while keeping settings, synonyms, and rules. This
   * operation is resource-intensive and subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearObjects(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(clearObjectsAsync(indexName, requestOptions));
  }

  /**
   * Deletes only the records from an index while keeping settings, synonyms, and rules. This
   * operation is resource-intensive and subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearObjects(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearObjects(indexName, null);
  }

  /**
   * (asynchronously) Deletes only the records from an index while keeping settings, synonyms, and
   * rules. This operation is resource-intensive and subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearObjectsAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `clearObjects`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/{indexName}/clear", indexName).setMethod("POST").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes only the records from an index while keeping settings, synonyms, and
   * rules. This operation is resource-intensive and subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearObjectsAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearObjectsAsync(indexName, null);
  }

  /**
   * Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearRules(@Nonnull String indexName, Boolean forwardToReplicas, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(clearRulesAsync(indexName, forwardToReplicas, requestOptions));
  }

  /**
   * Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearRules(@Nonnull String indexName, Boolean forwardToReplicas) throws AlgoliaRuntimeException {
    return this.clearRules(indexName, forwardToReplicas, null);
  }

  /**
   * Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearRules(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.clearRules(indexName, null, requestOptions);
  }

  /**
   * Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearRules(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearRules(indexName, null, null);
  }

  /**
   * (asynchronously) Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearRulesAsync(
    @Nonnull String indexName,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `clearRules`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/clear", indexName)
      .setMethod("POST")
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearRulesAsync(@Nonnull String indexName, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.clearRulesAsync(indexName, forwardToReplicas, null);
  }

  /**
   * (asynchronously) Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearRulesAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.clearRulesAsync(indexName, null, requestOptions);
  }

  /**
   * (asynchronously) Deletes all rules from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearRulesAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearRulesAsync(indexName, null, null);
  }

  /**
   * Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearSynonyms(@Nonnull String indexName, Boolean forwardToReplicas, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(clearSynonymsAsync(indexName, forwardToReplicas, requestOptions));
  }

  /**
   * Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearSynonyms(@Nonnull String indexName, Boolean forwardToReplicas) throws AlgoliaRuntimeException {
    return this.clearSynonyms(indexName, forwardToReplicas, null);
  }

  /**
   * Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearSynonyms(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.clearSynonyms(indexName, null, requestOptions);
  }

  /**
   * Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse clearSynonyms(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearSynonyms(indexName, null, null);
  }

  /**
   * (asynchronously) Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearSynonymsAsync(
    @Nonnull String indexName,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `clearSynonyms`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/clear", indexName)
      .setMethod("POST")
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearSynonymsAsync(@Nonnull String indexName, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.clearSynonymsAsync(indexName, forwardToReplicas, null);
  }

  /**
   * (asynchronously) Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearSynonymsAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.clearSynonymsAsync(indexName, null, requestOptions);
  }

  /**
   * (asynchronously) Deletes all synonyms from the index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture clearSynonymsAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.clearSynonymsAsync(indexName, null, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customDelete(@Nonnull String path, Map parameters, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(customDeleteAsync(path, parameters, requestOptions));
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customDelete(@Nonnull String path, Map parameters) throws AlgoliaRuntimeException {
    return this.customDelete(path, parameters, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customDelete(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customDelete(path, null, requestOptions);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customDelete(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customDelete(path, null, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customDeleteAsync(@Nonnull String path, Map parameters, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(path, "Parameter `path` is required when calling `customDelete`.");

    HttpRequest request = HttpRequest.builder().setPathEncoded("/{path}", path).setMethod("DELETE").addQueryParameters(parameters).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customDeleteAsync(@Nonnull String path, Map parameters) throws AlgoliaRuntimeException {
    return this.customDeleteAsync(path, parameters, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customDeleteAsync(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customDeleteAsync(path, null, requestOptions);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customDeleteAsync(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customDeleteAsync(path, null, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customGet(@Nonnull String path, Map parameters, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(customGetAsync(path, parameters, requestOptions));
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customGet(@Nonnull String path, Map parameters) throws AlgoliaRuntimeException {
    return this.customGet(path, parameters, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customGet(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customGet(path, null, requestOptions);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customGet(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customGet(path, null, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customGetAsync(@Nonnull String path, Map parameters, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(path, "Parameter `path` is required when calling `customGet`.");

    HttpRequest request = HttpRequest.builder().setPathEncoded("/{path}", path).setMethod("GET").addQueryParameters(parameters).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customGetAsync(@Nonnull String path, Map parameters) throws AlgoliaRuntimeException {
    return this.customGetAsync(path, parameters, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customGetAsync(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customGetAsync(path, null, requestOptions);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customGetAsync(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customGetAsync(path, null, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPost(@Nonnull String path, Map parameters, Object body, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(customPostAsync(path, parameters, body, requestOptions));
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPost(@Nonnull String path, Map parameters, Object body) throws AlgoliaRuntimeException {
    return this.customPost(path, parameters, body, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPost(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customPost(path, null, null, requestOptions);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPost(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customPost(path, null, null, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPostAsync(
    @Nonnull String path,
    Map parameters,
    Object body,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(path, "Parameter `path` is required when calling `customPost`.");

    HttpRequest request = HttpRequest.builder()
      .setPathEncoded("/{path}", path)
      .setMethod("POST")
      .setBody(body)
      .addQueryParameters(parameters)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPostAsync(@Nonnull String path, Map parameters, Object body)
    throws AlgoliaRuntimeException {
    return this.customPostAsync(path, parameters, body, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPostAsync(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customPostAsync(path, null, null, requestOptions);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPostAsync(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customPostAsync(path, null, null, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPut(@Nonnull String path, Map parameters, Object body, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(customPutAsync(path, parameters, body, requestOptions));
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPut(@Nonnull String path, Map parameters, Object body) throws AlgoliaRuntimeException {
    return this.customPut(path, parameters, body, null);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPut(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customPut(path, null, null, requestOptions);
  }

  /**
   * This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object customPut(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customPut(path, null, null, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPutAsync(
    @Nonnull String path,
    Map parameters,
    Object body,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(path, "Parameter `path` is required when calling `customPut`.");

    HttpRequest request = HttpRequest.builder()
      .setPathEncoded("/{path}", path)
      .setMethod("PUT")
      .setBody(body)
      .addQueryParameters(parameters)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param parameters Query parameters to apply to the current query. (optional)
   * @param body Parameters to send with the custom request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPutAsync(@Nonnull String path, Map parameters, Object body)
    throws AlgoliaRuntimeException {
    return this.customPutAsync(path, parameters, body, null);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPutAsync(@Nonnull String path, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.customPutAsync(path, null, null, requestOptions);
  }

  /**
   * (asynchronously) This method allow you to send requests to the Algolia REST API.
   *
   * @param path Path of the endpoint, anything after \"/1\" must be specified. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture customPutAsync(@Nonnull String path) throws AlgoliaRuntimeException {
    return this.customPutAsync(path, null, null, null);
  }

  /**
   * Deletes the API key.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeleteApiKeyResponse deleteApiKey(@Nonnull String key, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteApiKeyAsync(key, requestOptions));
  }

  /**
   * Deletes the API key.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeleteApiKeyResponse deleteApiKey(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.deleteApiKey(key, null);
  }

  /**
   * (asynchronously) Deletes the API key.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteApiKeyAsync(@Nonnull String key, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(key, "Parameter `key` is required when calling `deleteApiKey`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/keys/{key}", key).setMethod("DELETE").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes the API key.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteApiKeyAsync(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.deleteApiKeyAsync(key, null);
  }

  /**
   * This operation doesn't accept empty filters. This operation is resource-intensive. You should
   * only use it if you can't get the object IDs of the records you want to delete. It's more
   * efficient to get a list of object IDs with the [`browse`
   * operation](#tag/Search/operation/browse), and then delete the records using the [`batch`
   * operation](#tag/Records/operation/batch). This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param deleteByParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteBy(@Nonnull String indexName, @Nonnull DeleteByParams deleteByParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteByAsync(indexName, deleteByParams, requestOptions));
  }

  /**
   * This operation doesn't accept empty filters. This operation is resource-intensive. You should
   * only use it if you can't get the object IDs of the records you want to delete. It's more
   * efficient to get a list of object IDs with the [`browse`
   * operation](#tag/Search/operation/browse), and then delete the records using the [`batch`
   * operation](#tag/Records/operation/batch). This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param deleteByParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteBy(@Nonnull String indexName, @Nonnull DeleteByParams deleteByParams) throws AlgoliaRuntimeException {
    return this.deleteBy(indexName, deleteByParams, null);
  }

  /**
   * (asynchronously) This operation doesn't accept empty filters. This operation is
   * resource-intensive. You should only use it if you can't get the object IDs of the records you
   * want to delete. It's more efficient to get a list of object IDs with the [`browse`
   * operation](#tag/Search/operation/browse), and then delete the records using the [`batch`
   * operation](#tag/Records/operation/batch). This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param deleteByParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteByAsync(
    @Nonnull String indexName,
    @Nonnull DeleteByParams deleteByParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `deleteBy`.");

    Parameters.requireNonNull(deleteByParams, "Parameter `deleteByParams` is required when calling `deleteBy`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/deleteByQuery", indexName)
      .setMethod("POST")
      .setBody(deleteByParams)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) This operation doesn't accept empty filters. This operation is
   * resource-intensive. You should only use it if you can't get the object IDs of the records you
   * want to delete. It's more efficient to get a list of object IDs with the [`browse`
   * operation](#tag/Search/operation/browse), and then delete the records using the [`batch`
   * operation](#tag/Records/operation/batch). This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param deleteByParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteByAsync(@Nonnull String indexName, @Nonnull DeleteByParams deleteByParams)
    throws AlgoliaRuntimeException {
    return this.deleteByAsync(indexName, deleteByParams, null);
  }

  /**
   * Deletes an index and all its settings. - Deleting an index doesn't delete its analytics data. -
   * If you try to delete a non-existing index, the operation is ignored without warning. - If the
   * index you want to delete has replica indices, the replicas become independent indices. - If the
   * index you want to delete is a replica index, you must first unlink it from its primary index
   * before you can delete it. For more information, see [Delete replica
   * indices](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/deleting-replicas/).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteIndex(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteIndexAsync(indexName, requestOptions));
  }

  /**
   * Deletes an index and all its settings. - Deleting an index doesn't delete its analytics data. -
   * If you try to delete a non-existing index, the operation is ignored without warning. - If the
   * index you want to delete has replica indices, the replicas become independent indices. - If the
   * index you want to delete is a replica index, you must first unlink it from its primary index
   * before you can delete it. For more information, see [Delete replica
   * indices](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/deleting-replicas/).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteIndex(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.deleteIndex(indexName, null);
  }

  /**
   * (asynchronously) Deletes an index and all its settings. - Deleting an index doesn't delete its
   * analytics data. - If you try to delete a non-existing index, the operation is ignored without
   * warning. - If the index you want to delete has replica indices, the replicas become independent
   * indices. - If the index you want to delete is a replica index, you must first unlink it from
   * its primary index before you can delete it. For more information, see [Delete replica
   * indices](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/deleting-replicas/).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteIndexAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `deleteIndex`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/{indexName}", indexName).setMethod("DELETE").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes an index and all its settings. - Deleting an index doesn't delete its
   * analytics data. - If you try to delete a non-existing index, the operation is ignored without
   * warning. - If the index you want to delete has replica indices, the replicas become independent
   * indices. - If the index you want to delete is a replica index, you must first unlink it from
   * its primary index before you can delete it. For more information, see [Delete replica
   * indices](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/deleting-replicas/).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteIndexAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.deleteIndexAsync(indexName, null);
  }

  /**
   * Deletes a record by its object ID. To delete more than one record, use the [`batch`
   * operation](#tag/Records/operation/batch). To delete records matching a query, use the
   * [`deleteByQuery` operation](#tag/Records/operation/deleteBy).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteObject(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteObjectAsync(indexName, objectID, requestOptions));
  }

  /**
   * Deletes a record by its object ID. To delete more than one record, use the [`batch`
   * operation](#tag/Records/operation/batch). To delete records matching a query, use the
   * [`deleteByQuery` operation](#tag/Records/operation/deleteBy).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteObject(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.deleteObject(indexName, objectID, null);
  }

  /**
   * (asynchronously) Deletes a record by its object ID. To delete more than one record, use the
   * [`batch` operation](#tag/Records/operation/batch). To delete records matching a query, use the
   * [`deleteByQuery` operation](#tag/Records/operation/deleteBy).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `deleteObject`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `deleteObject`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/{objectID}", indexName, objectID)
      .setMethod("DELETE")
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes a record by its object ID. To delete more than one record, use the
   * [`batch` operation](#tag/Records/operation/batch). To delete records matching a query, use the
   * [`deleteByQuery` operation](#tag/Records/operation/deleteBy).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteObjectAsync(@Nonnull String indexName, @Nonnull String objectID)
    throws AlgoliaRuntimeException {
    return this.deleteObjectAsync(indexName, objectID, null);
  }

  /**
   * Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteRule(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteRuleAsync(indexName, objectID, forwardToReplicas, requestOptions));
  }

  /**
   * Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteRule(@Nonnull String indexName, @Nonnull String objectID, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.deleteRule(indexName, objectID, forwardToReplicas, null);
  }

  /**
   * Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteRule(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.deleteRule(indexName, objectID, null, requestOptions);
  }

  /**
   * Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse deleteRule(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.deleteRule(indexName, objectID, null, null);
  }

  /**
   * (asynchronously) Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `deleteRule`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `deleteRule`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/{objectID}", indexName, objectID)
      .setMethod("DELETE")
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.deleteRuleAsync(indexName, objectID, forwardToReplicas, null);
  }

  /**
   * (asynchronously) Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.deleteRuleAsync(indexName, objectID, null, requestOptions);
  }

  /**
   * (asynchronously) Deletes a rule by its ID. To find the object ID for rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteRuleAsync(@Nonnull String indexName, @Nonnull String objectID)
    throws AlgoliaRuntimeException {
    return this.deleteRuleAsync(indexName, objectID, null, null);
  }

  /**
   * Deletes a source from the list of allowed sources.
   *
   * @param source IP address range of the source. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeleteSourceResponse deleteSource(@Nonnull String source, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteSourceAsync(source, requestOptions));
  }

  /**
   * Deletes a source from the list of allowed sources.
   *
   * @param source IP address range of the source. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeleteSourceResponse deleteSource(@Nonnull String source) throws AlgoliaRuntimeException {
    return this.deleteSource(source, null);
  }

  /**
   * (asynchronously) Deletes a source from the list of allowed sources.
   *
   * @param source IP address range of the source. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSourceAsync(@Nonnull String source, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(source, "Parameter `source` is required when calling `deleteSource`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/security/sources/{source}", source).setMethod("DELETE").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes a source from the list of allowed sources.
   *
   * @param source IP address range of the source. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSourceAsync(@Nonnull String source) throws AlgoliaRuntimeException {
    return this.deleteSourceAsync(source, null);
  }

  /**
   * Deletes a synonym by its ID. To find the object IDs of your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteSynonym(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(deleteSynonymAsync(indexName, objectID, forwardToReplicas, requestOptions));
  }

  /**
   * Deletes a synonym by its ID. To find the object IDs of your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteSynonym(@Nonnull String indexName, @Nonnull String objectID, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.deleteSynonym(indexName, objectID, forwardToReplicas, null);
  }

  /**
   * Deletes a synonym by its ID. To find the object IDs of your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteSynonym(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.deleteSynonym(indexName, objectID, null, requestOptions);
  }

  /**
   * Deletes a synonym by its ID. To find the object IDs of your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public DeletedAtResponse deleteSynonym(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.deleteSynonym(indexName, objectID, null, null);
  }

  /**
   * (asynchronously) Deletes a synonym by its ID. To find the object IDs of your synonyms, use the
   * [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `deleteSynonym`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `deleteSynonym`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/{objectID}", indexName, objectID)
      .setMethod("DELETE")
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes a synonym by its ID. To find the object IDs of your synonyms, use the
   * [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.deleteSynonymAsync(indexName, objectID, forwardToReplicas, null);
  }

  /**
   * (asynchronously) Deletes a synonym by its ID. To find the object IDs of your synonyms, use the
   * [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.deleteSynonymAsync(indexName, objectID, null, requestOptions);
  }

  /**
   * (asynchronously) Deletes a synonym by its ID. To find the object IDs of your synonyms, use the
   * [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture deleteSynonymAsync(@Nonnull String indexName, @Nonnull String objectID)
    throws AlgoliaRuntimeException {
    return this.deleteSynonymAsync(indexName, objectID, null, null);
  }

  /**
   * Gets the permissions and restrictions of an API key. When authenticating with the admin API
   * key, you can request information for any of your application's keys. When authenticating with
   * other API keys, you can only retrieve information for that key, with the description replaced
   * by ``.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetApiKeyResponse getApiKey(@Nonnull String key, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getApiKeyAsync(key, requestOptions));
  }

  /**
   * Gets the permissions and restrictions of an API key. When authenticating with the admin API
   * key, you can request information for any of your application's keys. When authenticating with
   * other API keys, you can only retrieve information for that key, with the description replaced
   * by ``.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetApiKeyResponse getApiKey(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.getApiKey(key, null);
  }

  /**
   * (asynchronously) Gets the permissions and restrictions of an API key. When authenticating with
   * the admin API key, you can request information for any of your application's keys. When
   * authenticating with other API keys, you can only retrieve information for that key, with the
   * description replaced by ``.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getApiKeyAsync(@Nonnull String key, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(key, "Parameter `key` is required when calling `getApiKey`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/keys/{key}", key).setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Gets the permissions and restrictions of an API key. When authenticating with
   * the admin API key, you can request information for any of your application's keys. When
   * authenticating with other API keys, you can only retrieve information for that key, with the
   * description replaced by ``.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getApiKeyAsync(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.getApiKeyAsync(key, null);
  }

  /**
   * Checks the status of a given application task.
   *
   * @param taskID Unique task identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTaskResponse getAppTask(@Nonnull Long taskID, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getAppTaskAsync(taskID, requestOptions));
  }

  /**
   * Checks the status of a given application task.
   *
   * @param taskID Unique task identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTaskResponse getAppTask(@Nonnull Long taskID) throws AlgoliaRuntimeException {
    return this.getAppTask(taskID, null);
  }

  /**
   * (asynchronously) Checks the status of a given application task.
   *
   * @param taskID Unique task identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getAppTaskAsync(@Nonnull Long taskID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(taskID, "Parameter `taskID` is required when calling `getAppTask`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/task/{taskID}", taskID).setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Checks the status of a given application task.
   *
   * @param taskID Unique task identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getAppTaskAsync(@Nonnull Long taskID) throws AlgoliaRuntimeException {
    return this.getAppTaskAsync(taskID, null);
  }

  /**
   * Lists supported languages with their supported dictionary types and number of custom entries.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Map getDictionaryLanguages(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getDictionaryLanguagesAsync(requestOptions));
  }

  /**
   * Lists supported languages with their supported dictionary types and number of custom entries.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Map getDictionaryLanguages() throws AlgoliaRuntimeException {
    return this.getDictionaryLanguages(null);
  }

  /**
   * (asynchronously) Lists supported languages with their supported dictionary types and number of
   * custom entries.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture> getDictionaryLanguagesAsync(RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/dictionaries/*/languages").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference>() {});
  }

  /**
   * (asynchronously) Lists supported languages with their supported dictionary types and number of
   * custom entries.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture> getDictionaryLanguagesAsync() throws AlgoliaRuntimeException {
    return this.getDictionaryLanguagesAsync(null);
  }

  /**
   * Retrieves the languages for which standard dictionary entries are turned off.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetDictionarySettingsResponse getDictionarySettings(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getDictionarySettingsAsync(requestOptions));
  }

  /**
   * Retrieves the languages for which standard dictionary entries are turned off.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetDictionarySettingsResponse getDictionarySettings() throws AlgoliaRuntimeException {
    return this.getDictionarySettings(null);
  }

  /**
   * (asynchronously) Retrieves the languages for which standard dictionary entries are turned off.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getDictionarySettingsAsync(RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/dictionaries/*/settings").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Retrieves the languages for which standard dictionary entries are turned off.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getDictionarySettingsAsync() throws AlgoliaRuntimeException {
    return this.getDictionarySettingsAsync(null);
  }

  /**
   * The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param offset First log entry to retrieve. The most recent entries are listed first. (optional,
   *     default to 0)
   * @param length Maximum number of entries to retrieve. (optional, default to 10)
   * @param indexName Index for which to retrieve log entries. By default, log entries are retrieved
   *     for all indices. (optional)
   * @param type Type of log entries to retrieve. By default, all log entries are retrieved.
   *     (optional, default to all)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetLogsResponse getLogs(Integer offset, Integer length, String indexName, LogType type, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getLogsAsync(offset, length, indexName, type, requestOptions));
  }

  /**
   * The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param offset First log entry to retrieve. The most recent entries are listed first. (optional,
   *     default to 0)
   * @param length Maximum number of entries to retrieve. (optional, default to 10)
   * @param indexName Index for which to retrieve log entries. By default, log entries are retrieved
   *     for all indices. (optional)
   * @param type Type of log entries to retrieve. By default, all log entries are retrieved.
   *     (optional, default to all)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetLogsResponse getLogs(Integer offset, Integer length, String indexName, LogType type) throws AlgoliaRuntimeException {
    return this.getLogs(offset, length, indexName, type, null);
  }

  /**
   * The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetLogsResponse getLogs(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.getLogs(null, null, null, null, requestOptions);
  }

  /**
   * The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetLogsResponse getLogs() throws AlgoliaRuntimeException {
    return this.getLogs(null, null, null, null, null);
  }

  /**
   * (asynchronously) The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param offset First log entry to retrieve. The most recent entries are listed first. (optional,
   *     default to 0)
   * @param length Maximum number of entries to retrieve. (optional, default to 10)
   * @param indexName Index for which to retrieve log entries. By default, log entries are retrieved
   *     for all indices. (optional)
   * @param type Type of log entries to retrieve. By default, all log entries are retrieved.
   *     (optional, default to all)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getLogsAsync(
    Integer offset,
    Integer length,
    String indexName,
    LogType type,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder()
      .setPath("/1/logs")
      .setMethod("GET")
      .addQueryParameter("offset", offset)
      .addQueryParameter("length", length)
      .addQueryParameter("indexName", indexName)
      .addQueryParameter("type", type)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param offset First log entry to retrieve. The most recent entries are listed first. (optional,
   *     default to 0)
   * @param length Maximum number of entries to retrieve. (optional, default to 10)
   * @param indexName Index for which to retrieve log entries. By default, log entries are retrieved
   *     for all indices. (optional)
   * @param type Type of log entries to retrieve. By default, all log entries are retrieved.
   *     (optional, default to all)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getLogsAsync(Integer offset, Integer length, String indexName, LogType type)
    throws AlgoliaRuntimeException {
    return this.getLogsAsync(offset, length, indexName, type, null);
  }

  /**
   * (asynchronously) The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getLogsAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.getLogsAsync(null, null, null, null, requestOptions);
  }

  /**
   * (asynchronously) The request must be authenticated by an API key with the [`logs`
   * ACL](https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl). - Logs are
   * held for the last seven days. - Up to 1,000 API requests per server are logged. - This request
   * counts towards your [operations
   * quota](https://support.algolia.com/hc/en-us/articles/4406981829777-How-does-Algolia-count-records-and-operations-)
   * but doesn't appear in the logs itself.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getLogsAsync() throws AlgoliaRuntimeException {
    return this.getLogsAsync(null, null, null, null, null);
  }

  /**
   * Retrieves one record by its object ID. To retrieve more than one record, use the [`objects`
   * operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToRetrieve Attributes to include with the records in the response. This is
   *     useful to reduce the size of the API response. By default, all retrievable attributes are
   *     returned. `objectID` is always retrieved. Attributes included in `unretrievableAttributes`
   *     won't be retrieved unless the request is authenticated with the admin API key. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object getObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    List attributesToRetrieve,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getObjectAsync(indexName, objectID, attributesToRetrieve, requestOptions));
  }

  /**
   * Retrieves one record by its object ID. To retrieve more than one record, use the [`objects`
   * operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToRetrieve Attributes to include with the records in the response. This is
   *     useful to reduce the size of the API response. By default, all retrievable attributes are
   *     returned. `objectID` is always retrieved. Attributes included in `unretrievableAttributes`
   *     won't be retrieved unless the request is authenticated with the admin API key. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object getObject(@Nonnull String indexName, @Nonnull String objectID, List attributesToRetrieve)
    throws AlgoliaRuntimeException {
    return this.getObject(indexName, objectID, attributesToRetrieve, null);
  }

  /**
   * Retrieves one record by its object ID. To retrieve more than one record, use the [`objects`
   * operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object getObject(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.getObject(indexName, objectID, null, requestOptions);
  }

  /**
   * Retrieves one record by its object ID. To retrieve more than one record, use the [`objects`
   * operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Object getObject(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getObject(indexName, objectID, null, null);
  }

  /**
   * (asynchronously) Retrieves one record by its object ID. To retrieve more than one record, use
   * the [`objects` operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToRetrieve Attributes to include with the records in the response. This is
   *     useful to reduce the size of the API response. By default, all retrievable attributes are
   *     returned. `objectID` is always retrieved. Attributes included in `unretrievableAttributes`
   *     won't be retrieved unless the request is authenticated with the admin API key. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    List attributesToRetrieve,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `getObject`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `getObject`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/{objectID}", indexName, objectID)
      .setMethod("GET")
      .addQueryParameter("attributesToRetrieve", attributesToRetrieve)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Retrieves one record by its object ID. To retrieve more than one record, use
   * the [`objects` operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToRetrieve Attributes to include with the records in the response. This is
   *     useful to reduce the size of the API response. By default, all retrievable attributes are
   *     returned. `objectID` is always retrieved. Attributes included in `unretrievableAttributes`
   *     won't be retrieved unless the request is authenticated with the admin API key. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getObjectAsync(@Nonnull String indexName, @Nonnull String objectID, List attributesToRetrieve)
    throws AlgoliaRuntimeException {
    return this.getObjectAsync(indexName, objectID, attributesToRetrieve, null);
  }

  /**
   * (asynchronously) Retrieves one record by its object ID. To retrieve more than one record, use
   * the [`objects` operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getObjectAsync(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.getObjectAsync(indexName, objectID, null, requestOptions);
  }

  /**
   * (asynchronously) Retrieves one record by its object ID. To retrieve more than one record, use
   * the [`objects` operation](#tag/Records/operation/getObjects).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getObjectAsync(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getObjectAsync(indexName, objectID, null, null);
  }

  /**
   * Retrieves one or more records, potentially from different indices. Records are returned in the
   * same order as the requests.
   *
   * @param getObjectsParams Request object. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  GetObjectsResponse getObjects(
    @Nonnull GetObjectsParams getObjectsParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getObjectsAsync(getObjectsParams, innerType, requestOptions));
  }

  /**
   * Retrieves one or more records, potentially from different indices. Records are returned in the
   * same order as the requests.
   *
   * @param getObjectsParams Request object. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  GetObjectsResponse getObjects(@Nonnull GetObjectsParams getObjectsParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.getObjects(getObjectsParams, innerType, null);
  }

  /**
   * (asynchronously) Retrieves one or more records, potentially from different indices. Records are
   * returned in the same order as the requests.
   *
   * @param getObjectsParams Request object. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> getObjectsAsync(
    @Nonnull GetObjectsParams getObjectsParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(getObjectsParams, "Parameter `getObjectsParams` is required when calling `getObjects`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/*/objects")
      .setMethod("POST")
      .setBody(getObjectsParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, GetObjectsResponse.class, innerType);
  }

  /**
   * (asynchronously) Retrieves one or more records, potentially from different indices. Records are
   * returned in the same order as the requests.
   *
   * @param getObjectsParams Request object. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> getObjectsAsync(@Nonnull GetObjectsParams getObjectsParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.getObjectsAsync(getObjectsParams, innerType, null);
  }

  /**
   * Retrieves a rule by its ID. To find the object ID of rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Rule getRule(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getRuleAsync(indexName, objectID, requestOptions));
  }

  /**
   * Retrieves a rule by its ID. To find the object ID of rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public Rule getRule(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getRule(indexName, objectID, null);
  }

  /**
   * (asynchronously) Retrieves a rule by its ID. To find the object ID of rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getRuleAsync(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `getRule`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `getRule`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/{objectID}", indexName, objectID)
      .setMethod("GET")
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Retrieves a rule by its ID. To find the object ID of rules, use the [`search`
   * operation](#tag/Rules/operation/searchRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getRuleAsync(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getRuleAsync(indexName, objectID, null);
  }

  /**
   * Retrieves an object with non-null index settings.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SettingsResponse getSettings(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getSettingsAsync(indexName, requestOptions));
  }

  /**
   * Retrieves an object with non-null index settings.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SettingsResponse getSettings(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.getSettings(indexName, null);
  }

  /**
   * (asynchronously) Retrieves an object with non-null index settings.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getSettingsAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `getSettings`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/{indexName}/settings", indexName).setMethod("GET").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Retrieves an object with non-null index settings.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getSettingsAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.getSettingsAsync(indexName, null);
  }

  /**
   * Retrieves all allowed IP addresses with access to your application.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public List getSources(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getSourcesAsync(requestOptions));
  }

  /**
   * Retrieves all allowed IP addresses with access to your application.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public List getSources() throws AlgoliaRuntimeException {
    return this.getSources(null);
  }

  /**
   * (asynchronously) Retrieves all allowed IP addresses with access to your application.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture> getSourcesAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/security/sources").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference>() {});
  }

  /**
   * (asynchronously) Retrieves all allowed IP addresses with access to your application.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture> getSourcesAsync() throws AlgoliaRuntimeException {
    return this.getSourcesAsync(null);
  }

  /**
   * Retrieves a synonym by its ID. To find the object IDs for your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SynonymHit getSynonym(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getSynonymAsync(indexName, objectID, requestOptions));
  }

  /**
   * Retrieves a synonym by its ID. To find the object IDs for your synonyms, use the [`search`
   * operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SynonymHit getSynonym(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getSynonym(indexName, objectID, null);
  }

  /**
   * (asynchronously) Retrieves a synonym by its ID. To find the object IDs for your synonyms, use
   * the [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getSynonymAsync(@Nonnull String indexName, @Nonnull String objectID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `getSynonym`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `getSynonym`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/{objectID}", indexName, objectID)
      .setMethod("GET")
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Retrieves a synonym by its ID. To find the object IDs for your synonyms, use
   * the [`search` operation](#tag/Synonyms/operation/searchSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getSynonymAsync(@Nonnull String indexName, @Nonnull String objectID) throws AlgoliaRuntimeException {
    return this.getSynonymAsync(indexName, objectID, null);
  }

  /**
   * Checks the status of a given task. Indexing tasks are asynchronous. When you add, update, or
   * delete records or indices, a task is created on a queue and completed depending on the load on
   * the server. The indexing tasks' responses include a task ID that you can use to check the
   * status.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param taskID Unique task identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTaskResponse getTask(@Nonnull String indexName, @Nonnull Long taskID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getTaskAsync(indexName, taskID, requestOptions));
  }

  /**
   * Checks the status of a given task. Indexing tasks are asynchronous. When you add, update, or
   * delete records or indices, a task is created on a queue and completed depending on the load on
   * the server. The indexing tasks' responses include a task ID that you can use to check the
   * status.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param taskID Unique task identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTaskResponse getTask(@Nonnull String indexName, @Nonnull Long taskID) throws AlgoliaRuntimeException {
    return this.getTask(indexName, taskID, null);
  }

  /**
   * (asynchronously) Checks the status of a given task. Indexing tasks are asynchronous. When you
   * add, update, or delete records or indices, a task is created on a queue and completed depending
   * on the load on the server. The indexing tasks' responses include a task ID that you can use to
   * check the status.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param taskID Unique task identifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getTaskAsync(@Nonnull String indexName, @Nonnull Long taskID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `getTask`.");

    Parameters.requireNonNull(taskID, "Parameter `taskID` is required when calling `getTask`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/{indexName}/task/{taskID}", indexName, taskID).setMethod("GET").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Checks the status of a given task. Indexing tasks are asynchronous. When you
   * add, update, or delete records or indices, a task is created on a queue and completed depending
   * on the load on the server. The indexing tasks' responses include a task ID that you can use to
   * check the status.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param taskID Unique task identifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getTaskAsync(@Nonnull String indexName, @Nonnull Long taskID) throws AlgoliaRuntimeException {
    return this.getTaskAsync(indexName, taskID, null);
  }

  /**
   * Get the IDs of the 10 users with the highest number of records per cluster. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTopUserIdsResponse getTopUserIds(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getTopUserIdsAsync(requestOptions));
  }

  /**
   * Get the IDs of the 10 users with the highest number of records per cluster. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public GetTopUserIdsResponse getTopUserIds() throws AlgoliaRuntimeException {
    return this.getTopUserIds(null);
  }

  /**
   * (asynchronously) Get the IDs of the 10 users with the highest number of records per cluster.
   * Since it can take a few seconds to get the data from the different clusters, the response isn't
   * real-time.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getTopUserIdsAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/clusters/mapping/top").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Get the IDs of the 10 users with the highest number of records per cluster.
   * Since it can take a few seconds to get the data from the different clusters, the response isn't
   * real-time.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getTopUserIdsAsync() throws AlgoliaRuntimeException {
    return this.getTopUserIdsAsync(null);
  }

  /**
   * Returns the user ID data stored in the mapping. Since it can take a few seconds to get the data
   * from the different clusters, the response isn't real-time.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UserId getUserId(@Nonnull String userID, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(getUserIdAsync(userID, requestOptions));
  }

  /**
   * Returns the user ID data stored in the mapping. Since it can take a few seconds to get the data
   * from the different clusters, the response isn't real-time.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UserId getUserId(@Nonnull String userID) throws AlgoliaRuntimeException {
    return this.getUserId(userID, null);
  }

  /**
   * (asynchronously) Returns the user ID data stored in the mapping. Since it can take a few
   * seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getUserIdAsync(@Nonnull String userID, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(userID, "Parameter `userID` is required when calling `getUserId`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/clusters/mapping/{userID}", userID).setMethod("GET").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Returns the user ID data stored in the mapping. Since it can take a few
   * seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture getUserIdAsync(@Nonnull String userID) throws AlgoliaRuntimeException {
    return this.getUserIdAsync(userID, null);
  }

  /**
   * To determine when the time-consuming process of creating a large batch of users or migrating
   * users from one cluster to another is complete, this operation retrieves the status of the
   * process.
   *
   * @param getClusters Whether to include the cluster's pending mapping state in the response.
   *     (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public HasPendingMappingsResponse hasPendingMappings(Boolean getClusters, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(hasPendingMappingsAsync(getClusters, requestOptions));
  }

  /**
   * To determine when the time-consuming process of creating a large batch of users or migrating
   * users from one cluster to another is complete, this operation retrieves the status of the
   * process.
   *
   * @param getClusters Whether to include the cluster's pending mapping state in the response.
   *     (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public HasPendingMappingsResponse hasPendingMappings(Boolean getClusters) throws AlgoliaRuntimeException {
    return this.hasPendingMappings(getClusters, null);
  }

  /**
   * To determine when the time-consuming process of creating a large batch of users or migrating
   * users from one cluster to another is complete, this operation retrieves the status of the
   * process.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public HasPendingMappingsResponse hasPendingMappings(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.hasPendingMappings(null, requestOptions);
  }

  /**
   * To determine when the time-consuming process of creating a large batch of users or migrating
   * users from one cluster to another is complete, this operation retrieves the status of the
   * process.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public HasPendingMappingsResponse hasPendingMappings() throws AlgoliaRuntimeException {
    return this.hasPendingMappings(null, null);
  }

  /**
   * (asynchronously) To determine when the time-consuming process of creating a large batch of
   * users or migrating users from one cluster to another is complete, this operation retrieves the
   * status of the process.
   *
   * @param getClusters Whether to include the cluster's pending mapping state in the response.
   *     (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture hasPendingMappingsAsync(Boolean getClusters, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder()
      .setPath("/1/clusters/mapping/pending")
      .setMethod("GET")
      .addQueryParameter("getClusters", getClusters)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) To determine when the time-consuming process of creating a large batch of
   * users or migrating users from one cluster to another is complete, this operation retrieves the
   * status of the process.
   *
   * @param getClusters Whether to include the cluster's pending mapping state in the response.
   *     (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture hasPendingMappingsAsync(Boolean getClusters) throws AlgoliaRuntimeException {
    return this.hasPendingMappingsAsync(getClusters, null);
  }

  /**
   * (asynchronously) To determine when the time-consuming process of creating a large batch of
   * users or migrating users from one cluster to another is complete, this operation retrieves the
   * status of the process.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture hasPendingMappingsAsync(RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.hasPendingMappingsAsync(null, requestOptions);
  }

  /**
   * (asynchronously) To determine when the time-consuming process of creating a large batch of
   * users or migrating users from one cluster to another is complete, this operation retrieves the
   * status of the process.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture hasPendingMappingsAsync() throws AlgoliaRuntimeException {
    return this.hasPendingMappingsAsync(null, null);
  }

  /**
   * Lists all API keys associated with your Algolia application, including their permissions and
   * restrictions.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListApiKeysResponse listApiKeys(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(listApiKeysAsync(requestOptions));
  }

  /**
   * Lists all API keys associated with your Algolia application, including their permissions and
   * restrictions.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListApiKeysResponse listApiKeys() throws AlgoliaRuntimeException {
    return this.listApiKeys(null);
  }

  /**
   * (asynchronously) Lists all API keys associated with your Algolia application, including their
   * permissions and restrictions.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listApiKeysAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/keys").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Lists all API keys associated with your Algolia application, including their
   * permissions and restrictions.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listApiKeysAsync() throws AlgoliaRuntimeException {
    return this.listApiKeysAsync(null);
  }

  /**
   * Lists the available clusters in a multi-cluster setup.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListClustersResponse listClusters(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(listClustersAsync(requestOptions));
  }

  /**
   * Lists the available clusters in a multi-cluster setup.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListClustersResponse listClusters() throws AlgoliaRuntimeException {
    return this.listClusters(null);
  }

  /**
   * (asynchronously) Lists the available clusters in a multi-cluster setup.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listClustersAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder().setPath("/1/clusters").setMethod("GET").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Lists the available clusters in a multi-cluster setup.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listClustersAsync() throws AlgoliaRuntimeException {
    return this.listClustersAsync(null);
  }

  /**
   * Lists all indices in the current Algolia application. The request follows any index
   * restrictions of the API key you use to make the request.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListIndicesResponse listIndices(Integer page, Integer hitsPerPage, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(listIndicesAsync(page, hitsPerPage, requestOptions));
  }

  /**
   * Lists all indices in the current Algolia application. The request follows any index
   * restrictions of the API key you use to make the request.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListIndicesResponse listIndices(Integer page, Integer hitsPerPage) throws AlgoliaRuntimeException {
    return this.listIndices(page, hitsPerPage, null);
  }

  /**
   * Lists all indices in the current Algolia application. The request follows any index
   * restrictions of the API key you use to make the request.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListIndicesResponse listIndices(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.listIndices(null, null, requestOptions);
  }

  /**
   * Lists all indices in the current Algolia application. The request follows any index
   * restrictions of the API key you use to make the request.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListIndicesResponse listIndices() throws AlgoliaRuntimeException {
    return this.listIndices(null, null, null);
  }

  /**
   * (asynchronously) Lists all indices in the current Algolia application. The request follows any
   * index restrictions of the API key you use to make the request.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listIndicesAsync(Integer page, Integer hitsPerPage, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes")
      .setMethod("GET")
      .addQueryParameter("page", page)
      .addQueryParameter("hitsPerPage", hitsPerPage)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Lists all indices in the current Algolia application. The request follows any
   * index restrictions of the API key you use to make the request.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listIndicesAsync(Integer page, Integer hitsPerPage) throws AlgoliaRuntimeException {
    return this.listIndicesAsync(page, hitsPerPage, null);
  }

  /**
   * (asynchronously) Lists all indices in the current Algolia application. The request follows any
   * index restrictions of the API key you use to make the request.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listIndicesAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.listIndicesAsync(null, null, requestOptions);
  }

  /**
   * (asynchronously) Lists all indices in the current Algolia application. The request follows any
   * index restrictions of the API key you use to make the request.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listIndicesAsync() throws AlgoliaRuntimeException {
    return this.listIndicesAsync(null, null, null);
  }

  /**
   * Lists the userIDs assigned to a multi-cluster application. Since it can take a few seconds to
   * get the data from the different clusters, the response isn't real-time.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListUserIdsResponse listUserIds(Integer page, Integer hitsPerPage, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(listUserIdsAsync(page, hitsPerPage, requestOptions));
  }

  /**
   * Lists the userIDs assigned to a multi-cluster application. Since it can take a few seconds to
   * get the data from the different clusters, the response isn't real-time.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListUserIdsResponse listUserIds(Integer page, Integer hitsPerPage) throws AlgoliaRuntimeException {
    return this.listUserIds(page, hitsPerPage, null);
  }

  /**
   * Lists the userIDs assigned to a multi-cluster application. Since it can take a few seconds to
   * get the data from the different clusters, the response isn't real-time.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListUserIdsResponse listUserIds(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.listUserIds(null, null, requestOptions);
  }

  /**
   * Lists the userIDs assigned to a multi-cluster application. Since it can take a few seconds to
   * get the data from the different clusters, the response isn't real-time.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ListUserIdsResponse listUserIds() throws AlgoliaRuntimeException {
    return this.listUserIds(null, null, null);
  }

  /**
   * (asynchronously) Lists the userIDs assigned to a multi-cluster application. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listUserIdsAsync(Integer page, Integer hitsPerPage, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    HttpRequest request = HttpRequest.builder()
      .setPath("/1/clusters/mapping")
      .setMethod("GET")
      .addQueryParameter("page", page)
      .addQueryParameter("hitsPerPage", hitsPerPage)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Lists the userIDs assigned to a multi-cluster application. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param page Requested page of the API response. If `null`, the API response is not paginated.
   *     (optional)
   * @param hitsPerPage Number of hits per page. (optional, default to 100)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listUserIdsAsync(Integer page, Integer hitsPerPage) throws AlgoliaRuntimeException {
    return this.listUserIdsAsync(page, hitsPerPage, null);
  }

  /**
   * (asynchronously) Lists the userIDs assigned to a multi-cluster application. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listUserIdsAsync(RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.listUserIdsAsync(null, null, requestOptions);
  }

  /**
   * (asynchronously) Lists the userIDs assigned to a multi-cluster application. Since it can take a
   * few seconds to get the data from the different clusters, the response isn't real-time.
   *
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture listUserIdsAsync() throws AlgoliaRuntimeException {
    return this.listUserIdsAsync(null, null, null);
  }

  /**
   * Adds, updates, or deletes records in multiple indices with a single API request. - Actions are
   * applied in the order they are specified. - Actions are equivalent to the individual API
   * requests of the same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param batchParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public MultipleBatchResponse multipleBatch(@Nonnull BatchParams batchParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(multipleBatchAsync(batchParams, requestOptions));
  }

  /**
   * Adds, updates, or deletes records in multiple indices with a single API request. - Actions are
   * applied in the order they are specified. - Actions are equivalent to the individual API
   * requests of the same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param batchParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public MultipleBatchResponse multipleBatch(@Nonnull BatchParams batchParams) throws AlgoliaRuntimeException {
    return this.multipleBatch(batchParams, null);
  }

  /**
   * (asynchronously) Adds, updates, or deletes records in multiple indices with a single API
   * request. - Actions are applied in the order they are specified. - Actions are equivalent to the
   * individual API requests of the same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param batchParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture multipleBatchAsync(@Nonnull BatchParams batchParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(batchParams, "Parameter `batchParams` is required when calling `multipleBatch`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/*/batch").setMethod("POST").setBody(batchParams).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds, updates, or deletes records in multiple indices with a single API
   * request. - Actions are applied in the order they are specified. - Actions are equivalent to the
   * individual API requests of the same name. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param batchParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture multipleBatchAsync(@Nonnull BatchParams batchParams) throws AlgoliaRuntimeException {
    return this.multipleBatchAsync(batchParams, null);
  }

  /**
   * Copies or moves (renames) an index within the same Algolia application. - Existing destination
   * indices are overwritten, except for their analytics data. - If the destination index doesn't
   * exist yet, it'll be created. - This operation is resource-intensive. **Copy** - Copying a
   * source index that doesn't exist creates a new index with 0 records and default settings. - The
   * API keys of the source index are merged with the existing keys in the destination index. - You
   * can't copy the `enableReRanking`, `mode`, and `replicas` settings. - You can't copy to a
   * destination index that already has replicas. - Be aware of the [size
   * limits](https://www.algolia.com/doc/guides/scaling/algolia-service-limits/#application-record-and-index-limits).
   * - Related guide: [Copy
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-indices/)
   * **Move** - Moving a source index that doesn't exist is ignored without returning an error. -
   * When moving an index, the analytics data keeps its original name, and a new set of analytics
   * data is started for the new name. To access the original analytics in the dashboard, create an
   * index with the original name. - If the destination index has replicas, moving will overwrite
   * the existing index and copy the data to the replica indices. - Related guide: [Move
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/move-indices/).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param operationIndexParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse operationIndex(
    @Nonnull String indexName,
    @Nonnull OperationIndexParams operationIndexParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(operationIndexAsync(indexName, operationIndexParams, requestOptions));
  }

  /**
   * Copies or moves (renames) an index within the same Algolia application. - Existing destination
   * indices are overwritten, except for their analytics data. - If the destination index doesn't
   * exist yet, it'll be created. - This operation is resource-intensive. **Copy** - Copying a
   * source index that doesn't exist creates a new index with 0 records and default settings. - The
   * API keys of the source index are merged with the existing keys in the destination index. - You
   * can't copy the `enableReRanking`, `mode`, and `replicas` settings. - You can't copy to a
   * destination index that already has replicas. - Be aware of the [size
   * limits](https://www.algolia.com/doc/guides/scaling/algolia-service-limits/#application-record-and-index-limits).
   * - Related guide: [Copy
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-indices/)
   * **Move** - Moving a source index that doesn't exist is ignored without returning an error. -
   * When moving an index, the analytics data keeps its original name, and a new set of analytics
   * data is started for the new name. To access the original analytics in the dashboard, create an
   * index with the original name. - If the destination index has replicas, moving will overwrite
   * the existing index and copy the data to the replica indices. - Related guide: [Move
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/move-indices/).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param operationIndexParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse operationIndex(@Nonnull String indexName, @Nonnull OperationIndexParams operationIndexParams)
    throws AlgoliaRuntimeException {
    return this.operationIndex(indexName, operationIndexParams, null);
  }

  /**
   * (asynchronously) Copies or moves (renames) an index within the same Algolia application. -
   * Existing destination indices are overwritten, except for their analytics data. - If the
   * destination index doesn't exist yet, it'll be created. - This operation is resource-intensive.
   * **Copy** - Copying a source index that doesn't exist creates a new index with 0 records and
   * default settings. - The API keys of the source index are merged with the existing keys in the
   * destination index. - You can't copy the `enableReRanking`, `mode`, and `replicas` settings. -
   * You can't copy to a destination index that already has replicas. - Be aware of the [size
   * limits](https://www.algolia.com/doc/guides/scaling/algolia-service-limits/#application-record-and-index-limits).
   * - Related guide: [Copy
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-indices/)
   * **Move** - Moving a source index that doesn't exist is ignored without returning an error. -
   * When moving an index, the analytics data keeps its original name, and a new set of analytics
   * data is started for the new name. To access the original analytics in the dashboard, create an
   * index with the original name. - If the destination index has replicas, moving will overwrite
   * the existing index and copy the data to the replica indices. - Related guide: [Move
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/move-indices/).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param operationIndexParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture operationIndexAsync(
    @Nonnull String indexName,
    @Nonnull OperationIndexParams operationIndexParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `operationIndex`.");

    Parameters.requireNonNull(operationIndexParams, "Parameter `operationIndexParams` is required when calling `operationIndex`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/operation", indexName)
      .setMethod("POST")
      .setBody(operationIndexParams)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Copies or moves (renames) an index within the same Algolia application. -
   * Existing destination indices are overwritten, except for their analytics data. - If the
   * destination index doesn't exist yet, it'll be created. - This operation is resource-intensive.
   * **Copy** - Copying a source index that doesn't exist creates a new index with 0 records and
   * default settings. - The API keys of the source index are merged with the existing keys in the
   * destination index. - You can't copy the `enableReRanking`, `mode`, and `replicas` settings. -
   * You can't copy to a destination index that already has replicas. - Be aware of the [size
   * limits](https://www.algolia.com/doc/guides/scaling/algolia-service-limits/#application-record-and-index-limits).
   * - Related guide: [Copy
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-indices/)
   * **Move** - Moving a source index that doesn't exist is ignored without returning an error. -
   * When moving an index, the analytics data keeps its original name, and a new set of analytics
   * data is started for the new name. To access the original analytics in the dashboard, create an
   * index with the original name. - If the destination index has replicas, moving will overwrite
   * the existing index and copy the data to the replica indices. - Related guide: [Move
   * indices](https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/move-indices/).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param operationIndexParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture operationIndexAsync(
    @Nonnull String indexName,
    @Nonnull OperationIndexParams operationIndexParams
  ) throws AlgoliaRuntimeException {
    return this.operationIndexAsync(indexName, operationIndexParams, null);
  }

  /**
   * Adds new attributes to a record, or updates existing ones. - If a record with the specified
   * object ID doesn't exist, a new record is added to the index **if** `createIfNotExists` is true.
   * - If the index doesn't exist yet, this method creates a new index. - You can use any
   * first-level attribute but not nested attributes. If you specify a nested attribute, this
   * operation replaces its first-level ancestor. To update an attribute without pushing the entire
   * record, you can use these built-in operations. These operations can be helpful if you don't
   * have access to your initial data. - Increment: increment a numeric attribute - Decrement:
   * decrement a numeric attribute - Add: append a number or string element to an array attribute -
   * Remove: remove all matching number or string elements from an array attribute made of numbers
   * or strings - AddUnique: add a number or string element to an array attribute made of numbers or
   * strings only if it's not already present - IncrementFrom: increment a numeric integer attribute
   * only if the provided value matches the current value, and otherwise ignore the whole object
   * update. For example, if you pass an IncrementFrom value of 2 for the version attribute, but the
   * current value of the attribute is 1, the engine ignores the update. If the object doesn't
   * exist, the engine only creates it if you pass an IncrementFrom value of 0. - IncrementSet:
   * increment a numeric integer attribute only if the provided value is greater than the current
   * value, and otherwise ignore the whole object update. For example, if you pass an IncrementSet
   * value of 2 for the version attribute, and the current value of the attribute is 1, the engine
   * updates the object. If the object doesn't exist yet, the engine only creates it if you pass an
   * IncrementSet value greater than 0. You can specify an operation by providing an object with the
   * attribute to update as the key and its value being an object with the following properties: -
   * _operation: the operation to apply on the attribute - value: the right-hand side argument to
   * the operation, for example, increment or decrement step, value to add or remove. This operation
   * is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param createIfNotExists Whether to create a new record if it doesn't exist. (optional, default
   *     to true)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse partialUpdateObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    Boolean createIfNotExists,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(partialUpdateObjectAsync(indexName, objectID, attributesToUpdate, createIfNotExists, requestOptions));
  }

  /**
   * Adds new attributes to a record, or updates existing ones. - If a record with the specified
   * object ID doesn't exist, a new record is added to the index **if** `createIfNotExists` is true.
   * - If the index doesn't exist yet, this method creates a new index. - You can use any
   * first-level attribute but not nested attributes. If you specify a nested attribute, this
   * operation replaces its first-level ancestor. To update an attribute without pushing the entire
   * record, you can use these built-in operations. These operations can be helpful if you don't
   * have access to your initial data. - Increment: increment a numeric attribute - Decrement:
   * decrement a numeric attribute - Add: append a number or string element to an array attribute -
   * Remove: remove all matching number or string elements from an array attribute made of numbers
   * or strings - AddUnique: add a number or string element to an array attribute made of numbers or
   * strings only if it's not already present - IncrementFrom: increment a numeric integer attribute
   * only if the provided value matches the current value, and otherwise ignore the whole object
   * update. For example, if you pass an IncrementFrom value of 2 for the version attribute, but the
   * current value of the attribute is 1, the engine ignores the update. If the object doesn't
   * exist, the engine only creates it if you pass an IncrementFrom value of 0. - IncrementSet:
   * increment a numeric integer attribute only if the provided value is greater than the current
   * value, and otherwise ignore the whole object update. For example, if you pass an IncrementSet
   * value of 2 for the version attribute, and the current value of the attribute is 1, the engine
   * updates the object. If the object doesn't exist yet, the engine only creates it if you pass an
   * IncrementSet value greater than 0. You can specify an operation by providing an object with the
   * attribute to update as the key and its value being an object with the following properties: -
   * _operation: the operation to apply on the attribute - value: the right-hand side argument to
   * the operation, for example, increment or decrement step, value to add or remove. This operation
   * is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param createIfNotExists Whether to create a new record if it doesn't exist. (optional, default
   *     to true)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse partialUpdateObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    Boolean createIfNotExists
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObject(indexName, objectID, attributesToUpdate, createIfNotExists, null);
  }

  /**
   * Adds new attributes to a record, or updates existing ones. - If a record with the specified
   * object ID doesn't exist, a new record is added to the index **if** `createIfNotExists` is true.
   * - If the index doesn't exist yet, this method creates a new index. - You can use any
   * first-level attribute but not nested attributes. If you specify a nested attribute, this
   * operation replaces its first-level ancestor. To update an attribute without pushing the entire
   * record, you can use these built-in operations. These operations can be helpful if you don't
   * have access to your initial data. - Increment: increment a numeric attribute - Decrement:
   * decrement a numeric attribute - Add: append a number or string element to an array attribute -
   * Remove: remove all matching number or string elements from an array attribute made of numbers
   * or strings - AddUnique: add a number or string element to an array attribute made of numbers or
   * strings only if it's not already present - IncrementFrom: increment a numeric integer attribute
   * only if the provided value matches the current value, and otherwise ignore the whole object
   * update. For example, if you pass an IncrementFrom value of 2 for the version attribute, but the
   * current value of the attribute is 1, the engine ignores the update. If the object doesn't
   * exist, the engine only creates it if you pass an IncrementFrom value of 0. - IncrementSet:
   * increment a numeric integer attribute only if the provided value is greater than the current
   * value, and otherwise ignore the whole object update. For example, if you pass an IncrementSet
   * value of 2 for the version attribute, and the current value of the attribute is 1, the engine
   * updates the object. If the object doesn't exist yet, the engine only creates it if you pass an
   * IncrementSet value greater than 0. You can specify an operation by providing an object with the
   * attribute to update as the key and its value being an object with the following properties: -
   * _operation: the operation to apply on the attribute - value: the right-hand side argument to
   * the operation, for example, increment or decrement step, value to add or remove. This operation
   * is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse partialUpdateObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObject(indexName, objectID, attributesToUpdate, null, requestOptions);
  }

  /**
   * Adds new attributes to a record, or updates existing ones. - If a record with the specified
   * object ID doesn't exist, a new record is added to the index **if** `createIfNotExists` is true.
   * - If the index doesn't exist yet, this method creates a new index. - You can use any
   * first-level attribute but not nested attributes. If you specify a nested attribute, this
   * operation replaces its first-level ancestor. To update an attribute without pushing the entire
   * record, you can use these built-in operations. These operations can be helpful if you don't
   * have access to your initial data. - Increment: increment a numeric attribute - Decrement:
   * decrement a numeric attribute - Add: append a number or string element to an array attribute -
   * Remove: remove all matching number or string elements from an array attribute made of numbers
   * or strings - AddUnique: add a number or string element to an array attribute made of numbers or
   * strings only if it's not already present - IncrementFrom: increment a numeric integer attribute
   * only if the provided value matches the current value, and otherwise ignore the whole object
   * update. For example, if you pass an IncrementFrom value of 2 for the version attribute, but the
   * current value of the attribute is 1, the engine ignores the update. If the object doesn't
   * exist, the engine only creates it if you pass an IncrementFrom value of 0. - IncrementSet:
   * increment a numeric integer attribute only if the provided value is greater than the current
   * value, and otherwise ignore the whole object update. For example, if you pass an IncrementSet
   * value of 2 for the version attribute, and the current value of the attribute is 1, the engine
   * updates the object. If the object doesn't exist yet, the engine only creates it if you pass an
   * IncrementSet value greater than 0. You can specify an operation by providing an object with the
   * attribute to update as the key and its value being an object with the following properties: -
   * _operation: the operation to apply on the attribute - value: the right-hand side argument to
   * the operation, for example, increment or decrement step, value to add or remove. This operation
   * is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtWithObjectIdResponse partialUpdateObject(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObject(indexName, objectID, attributesToUpdate, null, null);
  }

  /**
   * (asynchronously) Adds new attributes to a record, or updates existing ones. - If a record with
   * the specified object ID doesn't exist, a new record is added to the index **if**
   * `createIfNotExists` is true. - If the index doesn't exist yet, this method creates a new index.
   * - You can use any first-level attribute but not nested attributes. If you specify a nested
   * attribute, this operation replaces its first-level ancestor. To update an attribute without
   * pushing the entire record, you can use these built-in operations. These operations can be
   * helpful if you don't have access to your initial data. - Increment: increment a numeric
   * attribute - Decrement: decrement a numeric attribute - Add: append a number or string element
   * to an array attribute - Remove: remove all matching number or string elements from an array
   * attribute made of numbers or strings - AddUnique: add a number or string element to an array
   * attribute made of numbers or strings only if it's not already present - IncrementFrom:
   * increment a numeric integer attribute only if the provided value matches the current value, and
   * otherwise ignore the whole object update. For example, if you pass an IncrementFrom value of 2
   * for the version attribute, but the current value of the attribute is 1, the engine ignores the
   * update. If the object doesn't exist, the engine only creates it if you pass an IncrementFrom
   * value of 0. - IncrementSet: increment a numeric integer attribute only if the provided value is
   * greater than the current value, and otherwise ignore the whole object update. For example, if
   * you pass an IncrementSet value of 2 for the version attribute, and the current value of the
   * attribute is 1, the engine updates the object. If the object doesn't exist yet, the engine only
   * creates it if you pass an IncrementSet value greater than 0. You can specify an operation by
   * providing an object with the attribute to update as the key and its value being an object with
   * the following properties: - _operation: the operation to apply on the attribute - value: the
   * right-hand side argument to the operation, for example, increment or decrement step, value to
   * add or remove. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param createIfNotExists Whether to create a new record if it doesn't exist. (optional, default
   *     to true)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture partialUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    Boolean createIfNotExists,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `partialUpdateObject`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `partialUpdateObject`.");

    Parameters.requireNonNull(attributesToUpdate, "Parameter `attributesToUpdate` is required when calling `partialUpdateObject`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/{objectID}/partial", indexName, objectID)
      .setMethod("POST")
      .setBody(attributesToUpdate)
      .addQueryParameter("createIfNotExists", createIfNotExists)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds new attributes to a record, or updates existing ones. - If a record with
   * the specified object ID doesn't exist, a new record is added to the index **if**
   * `createIfNotExists` is true. - If the index doesn't exist yet, this method creates a new index.
   * - You can use any first-level attribute but not nested attributes. If you specify a nested
   * attribute, this operation replaces its first-level ancestor. To update an attribute without
   * pushing the entire record, you can use these built-in operations. These operations can be
   * helpful if you don't have access to your initial data. - Increment: increment a numeric
   * attribute - Decrement: decrement a numeric attribute - Add: append a number or string element
   * to an array attribute - Remove: remove all matching number or string elements from an array
   * attribute made of numbers or strings - AddUnique: add a number or string element to an array
   * attribute made of numbers or strings only if it's not already present - IncrementFrom:
   * increment a numeric integer attribute only if the provided value matches the current value, and
   * otherwise ignore the whole object update. For example, if you pass an IncrementFrom value of 2
   * for the version attribute, but the current value of the attribute is 1, the engine ignores the
   * update. If the object doesn't exist, the engine only creates it if you pass an IncrementFrom
   * value of 0. - IncrementSet: increment a numeric integer attribute only if the provided value is
   * greater than the current value, and otherwise ignore the whole object update. For example, if
   * you pass an IncrementSet value of 2 for the version attribute, and the current value of the
   * attribute is 1, the engine updates the object. If the object doesn't exist yet, the engine only
   * creates it if you pass an IncrementSet value greater than 0. You can specify an operation by
   * providing an object with the attribute to update as the key and its value being an object with
   * the following properties: - _operation: the operation to apply on the attribute - value: the
   * right-hand side argument to the operation, for example, increment or decrement step, value to
   * add or remove. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param createIfNotExists Whether to create a new record if it doesn't exist. (optional, default
   *     to true)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture partialUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    Boolean createIfNotExists
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObjectAsync(indexName, objectID, attributesToUpdate, createIfNotExists, null);
  }

  /**
   * (asynchronously) Adds new attributes to a record, or updates existing ones. - If a record with
   * the specified object ID doesn't exist, a new record is added to the index **if**
   * `createIfNotExists` is true. - If the index doesn't exist yet, this method creates a new index.
   * - You can use any first-level attribute but not nested attributes. If you specify a nested
   * attribute, this operation replaces its first-level ancestor. To update an attribute without
   * pushing the entire record, you can use these built-in operations. These operations can be
   * helpful if you don't have access to your initial data. - Increment: increment a numeric
   * attribute - Decrement: decrement a numeric attribute - Add: append a number or string element
   * to an array attribute - Remove: remove all matching number or string elements from an array
   * attribute made of numbers or strings - AddUnique: add a number or string element to an array
   * attribute made of numbers or strings only if it's not already present - IncrementFrom:
   * increment a numeric integer attribute only if the provided value matches the current value, and
   * otherwise ignore the whole object update. For example, if you pass an IncrementFrom value of 2
   * for the version attribute, but the current value of the attribute is 1, the engine ignores the
   * update. If the object doesn't exist, the engine only creates it if you pass an IncrementFrom
   * value of 0. - IncrementSet: increment a numeric integer attribute only if the provided value is
   * greater than the current value, and otherwise ignore the whole object update. For example, if
   * you pass an IncrementSet value of 2 for the version attribute, and the current value of the
   * attribute is 1, the engine updates the object. If the object doesn't exist yet, the engine only
   * creates it if you pass an IncrementSet value greater than 0. You can specify an operation by
   * providing an object with the attribute to update as the key and its value being an object with
   * the following properties: - _operation: the operation to apply on the attribute - value: the
   * right-hand side argument to the operation, for example, increment or decrement step, value to
   * add or remove. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture partialUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObjectAsync(indexName, objectID, attributesToUpdate, null, requestOptions);
  }

  /**
   * (asynchronously) Adds new attributes to a record, or updates existing ones. - If a record with
   * the specified object ID doesn't exist, a new record is added to the index **if**
   * `createIfNotExists` is true. - If the index doesn't exist yet, this method creates a new index.
   * - You can use any first-level attribute but not nested attributes. If you specify a nested
   * attribute, this operation replaces its first-level ancestor. To update an attribute without
   * pushing the entire record, you can use these built-in operations. These operations can be
   * helpful if you don't have access to your initial data. - Increment: increment a numeric
   * attribute - Decrement: decrement a numeric attribute - Add: append a number or string element
   * to an array attribute - Remove: remove all matching number or string elements from an array
   * attribute made of numbers or strings - AddUnique: add a number or string element to an array
   * attribute made of numbers or strings only if it's not already present - IncrementFrom:
   * increment a numeric integer attribute only if the provided value matches the current value, and
   * otherwise ignore the whole object update. For example, if you pass an IncrementFrom value of 2
   * for the version attribute, but the current value of the attribute is 1, the engine ignores the
   * update. If the object doesn't exist, the engine only creates it if you pass an IncrementFrom
   * value of 0. - IncrementSet: increment a numeric integer attribute only if the provided value is
   * greater than the current value, and otherwise ignore the whole object update. For example, if
   * you pass an IncrementSet value of 2 for the version attribute, and the current value of the
   * attribute is 1, the engine updates the object. If the object doesn't exist yet, the engine only
   * creates it if you pass an IncrementSet value greater than 0. You can specify an operation by
   * providing an object with the attribute to update as the key and its value being an object with
   * the following properties: - _operation: the operation to apply on the attribute - value: the
   * right-hand side argument to the operation, for example, increment or decrement step, value to
   * add or remove. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique record identifier. (required)
   * @param attributesToUpdate Attributes with their values. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture partialUpdateObjectAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Object attributesToUpdate
  ) throws AlgoliaRuntimeException {
    return this.partialUpdateObjectAsync(indexName, objectID, attributesToUpdate, null, null);
  }

  /**
   * Deletes a user ID and its associated data from the clusters.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public RemoveUserIdResponse removeUserId(@Nonnull String userID, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(removeUserIdAsync(userID, requestOptions));
  }

  /**
   * Deletes a user ID and its associated data from the clusters.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public RemoveUserIdResponse removeUserId(@Nonnull String userID) throws AlgoliaRuntimeException {
    return this.removeUserId(userID, null);
  }

  /**
   * (asynchronously) Deletes a user ID and its associated data from the clusters.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture removeUserIdAsync(@Nonnull String userID, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(userID, "Parameter `userID` is required when calling `removeUserId`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/clusters/mapping/{userID}", userID).setMethod("DELETE").build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Deletes a user ID and its associated data from the clusters.
   *
   * @param userID Unique identifier of the user who makes the search request. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture removeUserIdAsync(@Nonnull String userID) throws AlgoliaRuntimeException {
    return this.removeUserIdAsync(userID, null);
  }

  /**
   * Replaces the list of allowed sources.
   *
   * @param source Allowed sources. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ReplaceSourceResponse replaceSources(@Nonnull List source, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(replaceSourcesAsync(source, requestOptions));
  }

  /**
   * Replaces the list of allowed sources.
   *
   * @param source Allowed sources. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public ReplaceSourceResponse replaceSources(@Nonnull List source) throws AlgoliaRuntimeException {
    return this.replaceSources(source, null);
  }

  /**
   * (asynchronously) Replaces the list of allowed sources.
   *
   * @param source Allowed sources. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture replaceSourcesAsync(@Nonnull List source, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(source, "Parameter `source` is required when calling `replaceSources`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/security/sources").setMethod("PUT").setBody(source).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Replaces the list of allowed sources.
   *
   * @param source Allowed sources. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture replaceSourcesAsync(@Nonnull List source) throws AlgoliaRuntimeException {
    return this.replaceSourcesAsync(source, null);
  }

  /**
   * Restores a deleted API key. Restoring resets the `validity` attribute to `0`. Algolia stores up
   * to 1,000 API keys per application. If you create more, the oldest API keys are deleted and
   * can't be restored.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public AddApiKeyResponse restoreApiKey(@Nonnull String key, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(restoreApiKeyAsync(key, requestOptions));
  }

  /**
   * Restores a deleted API key. Restoring resets the `validity` attribute to `0`. Algolia stores up
   * to 1,000 API keys per application. If you create more, the oldest API keys are deleted and
   * can't be restored.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public AddApiKeyResponse restoreApiKey(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.restoreApiKey(key, null);
  }

  /**
   * (asynchronously) Restores a deleted API key. Restoring resets the `validity` attribute to `0`.
   * Algolia stores up to 1,000 API keys per application. If you create more, the oldest API keys
   * are deleted and can't be restored.
   *
   * @param key API key. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture restoreApiKeyAsync(@Nonnull String key, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    Parameters.requireNonNull(key, "Parameter `key` is required when calling `restoreApiKey`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/keys/{key}/restore", key).setMethod("POST").build();

    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Restores a deleted API key. Restoring resets the `validity` attribute to `0`.
   * Algolia stores up to 1,000 API keys per application. If you create more, the oldest API keys
   * are deleted and can't be restored.
   *
   * @param key API key. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture restoreApiKeyAsync(@Nonnull String key) throws AlgoliaRuntimeException {
    return this.restoreApiKeyAsync(key, null);
  }

  /**
   * Adds a record to an index or replaces it. - If the record doesn't have an object ID, a new
   * record with an auto-generated object ID is added to your index. - If a record with the
   * specified object ID exists, the existing record is replaced. - If a record with the specified
   * object ID doesn't exist, a new record is added to your index. - If you add a record to an index
   * that doesn't exist yet, a new index is created. To update _some_ attributes of a record, use
   * the [`partial` operation](#tag/Records/operation/partialUpdateObject). To add, update, or
   * replace multiple records, use the [`batch` operation](#tag/Records/operation/batch). This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveObjectResponse saveObject(@Nonnull String indexName, @Nonnull Object body, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(saveObjectAsync(indexName, body, requestOptions));
  }

  /**
   * Adds a record to an index or replaces it. - If the record doesn't have an object ID, a new
   * record with an auto-generated object ID is added to your index. - If a record with the
   * specified object ID exists, the existing record is replaced. - If a record with the specified
   * object ID doesn't exist, a new record is added to your index. - If you add a record to an index
   * that doesn't exist yet, a new index is created. To update _some_ attributes of a record, use
   * the [`partial` operation](#tag/Records/operation/partialUpdateObject). To add, update, or
   * replace multiple records, use the [`batch` operation](#tag/Records/operation/batch). This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveObjectResponse saveObject(@Nonnull String indexName, @Nonnull Object body) throws AlgoliaRuntimeException {
    return this.saveObject(indexName, body, null);
  }

  /**
   * (asynchronously) Adds a record to an index or replaces it. - If the record doesn't have an
   * object ID, a new record with an auto-generated object ID is added to your index. - If a record
   * with the specified object ID exists, the existing record is replaced. - If a record with the
   * specified object ID doesn't exist, a new record is added to your index. - If you add a record
   * to an index that doesn't exist yet, a new index is created. To update _some_ attributes of a
   * record, use the [`partial` operation](#tag/Records/operation/partialUpdateObject). To add,
   * update, or replace multiple records, use the [`batch` operation](#tag/Records/operation/batch).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveObjectAsync(
    @Nonnull String indexName,
    @Nonnull Object body,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `saveObject`.");

    Parameters.requireNonNull(body, "Parameter `body` is required when calling `saveObject`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/indexes/{indexName}", indexName).setMethod("POST").setBody(body).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Adds a record to an index or replaces it. - If the record doesn't have an
   * object ID, a new record with an auto-generated object ID is added to your index. - If a record
   * with the specified object ID exists, the existing record is replaced. - If a record with the
   * specified object ID doesn't exist, a new record is added to your index. - If you add a record
   * to an index that doesn't exist yet, a new index is created. To update _some_ attributes of a
   * record, use the [`partial` operation](#tag/Records/operation/partialUpdateObject). To add,
   * update, or replace multiple records, use the [`batch` operation](#tag/Records/operation/batch).
   * This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param body The record. A schemaless object with attributes that are useful in the context of
   *     search and discovery. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveObjectAsync(@Nonnull String indexName, @Nonnull Object body)
    throws AlgoliaRuntimeException {
    return this.saveObjectAsync(indexName, body, null);
  }

  /**
   * If a rule with the specified object ID doesn't exist, it's created. Otherwise, the existing
   * rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRule(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Rule rule,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(saveRuleAsync(indexName, objectID, rule, forwardToReplicas, requestOptions));
  }

  /**
   * If a rule with the specified object ID doesn't exist, it's created. Otherwise, the existing
   * rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRule(@Nonnull String indexName, @Nonnull String objectID, @Nonnull Rule rule, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.saveRule(indexName, objectID, rule, forwardToReplicas, null);
  }

  /**
   * If a rule with the specified object ID doesn't exist, it's created. Otherwise, the existing
   * rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRule(@Nonnull String indexName, @Nonnull String objectID, @Nonnull Rule rule, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.saveRule(indexName, objectID, rule, null, requestOptions);
  }

  /**
   * If a rule with the specified object ID doesn't exist, it's created. Otherwise, the existing
   * rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRule(@Nonnull String indexName, @Nonnull String objectID, @Nonnull Rule rule)
    throws AlgoliaRuntimeException {
    return this.saveRule(indexName, objectID, rule, null, null);
  }

  /**
   * (asynchronously) If a rule with the specified object ID doesn't exist, it's created. Otherwise,
   * the existing rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Rule rule,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `saveRule`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `saveRule`.");

    Parameters.requireNonNull(rule, "Parameter `rule` is required when calling `saveRule`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/{objectID}", indexName, objectID)
      .setMethod("PUT")
      .setBody(rule)
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) If a rule with the specified object ID doesn't exist, it's created. Otherwise,
   * the existing rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Rule rule,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.saveRuleAsync(indexName, objectID, rule, forwardToReplicas, null);
  }

  /**
   * (asynchronously) If a rule with the specified object ID doesn't exist, it's created. Otherwise,
   * the existing rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRuleAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull Rule rule,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.saveRuleAsync(indexName, objectID, rule, null, requestOptions);
  }

  /**
   * (asynchronously) If a rule with the specified object ID doesn't exist, it's created. Otherwise,
   * the existing rule is replaced. To create or update more than one rule, use the [`batch`
   * operation](#tag/Rules/operation/saveRules).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a rule object. (required)
   * @param rule (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRuleAsync(@Nonnull String indexName, @Nonnull String objectID, @Nonnull Rule rule)
    throws AlgoliaRuntimeException {
    return this.saveRuleAsync(indexName, objectID, rule, null, null);
  }

  /**
   * Create or update multiple rules. If a rule with the specified object ID doesn't exist, Algolia
   * creates a new one. Otherwise, existing rules are replaced. This operation is subject to
   * [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param clearExistingRules Whether existing rules should be deleted before adding this batch.
   *     (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRules(
    @Nonnull String indexName,
    @Nonnull List rules,
    Boolean forwardToReplicas,
    Boolean clearExistingRules,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(saveRulesAsync(indexName, rules, forwardToReplicas, clearExistingRules, requestOptions));
  }

  /**
   * Create or update multiple rules. If a rule with the specified object ID doesn't exist, Algolia
   * creates a new one. Otherwise, existing rules are replaced. This operation is subject to
   * [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param clearExistingRules Whether existing rules should be deleted before adding this batch.
   *     (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRules(
    @Nonnull String indexName,
    @Nonnull List rules,
    Boolean forwardToReplicas,
    Boolean clearExistingRules
  ) throws AlgoliaRuntimeException {
    return this.saveRules(indexName, rules, forwardToReplicas, clearExistingRules, null);
  }

  /**
   * Create or update multiple rules. If a rule with the specified object ID doesn't exist, Algolia
   * creates a new one. Otherwise, existing rules are replaced. This operation is subject to
   * [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRules(@Nonnull String indexName, @Nonnull List rules, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.saveRules(indexName, rules, null, null, requestOptions);
  }

  /**
   * Create or update multiple rules. If a rule with the specified object ID doesn't exist, Algolia
   * creates a new one. Otherwise, existing rules are replaced. This operation is subject to
   * [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveRules(@Nonnull String indexName, @Nonnull List rules) throws AlgoliaRuntimeException {
    return this.saveRules(indexName, rules, null, null, null);
  }

  /**
   * (asynchronously) Create or update multiple rules. If a rule with the specified object ID
   * doesn't exist, Algolia creates a new one. Otherwise, existing rules are replaced. This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param clearExistingRules Whether existing rules should be deleted before adding this batch.
   *     (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRulesAsync(
    @Nonnull String indexName,
    @Nonnull List rules,
    Boolean forwardToReplicas,
    Boolean clearExistingRules,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `saveRules`.");

    Parameters.requireNonNull(rules, "Parameter `rules` is required when calling `saveRules`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/batch", indexName)
      .setMethod("POST")
      .setBody(rules)
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .addQueryParameter("clearExistingRules", clearExistingRules)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Create or update multiple rules. If a rule with the specified object ID
   * doesn't exist, Algolia creates a new one. Otherwise, existing rules are replaced. This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param clearExistingRules Whether existing rules should be deleted before adding this batch.
   *     (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRulesAsync(
    @Nonnull String indexName,
    @Nonnull List rules,
    Boolean forwardToReplicas,
    Boolean clearExistingRules
  ) throws AlgoliaRuntimeException {
    return this.saveRulesAsync(indexName, rules, forwardToReplicas, clearExistingRules, null);
  }

  /**
   * (asynchronously) Create or update multiple rules. If a rule with the specified object ID
   * doesn't exist, Algolia creates a new one. Otherwise, existing rules are replaced. This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRulesAsync(
    @Nonnull String indexName,
    @Nonnull List rules,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.saveRulesAsync(indexName, rules, null, null, requestOptions);
  }

  /**
   * (asynchronously) Create or update multiple rules. If a rule with the specified object ID
   * doesn't exist, Algolia creates a new one. Otherwise, existing rules are replaced. This
   * operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param rules (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveRulesAsync(@Nonnull String indexName, @Nonnull List rules)
    throws AlgoliaRuntimeException {
    return this.saveRulesAsync(indexName, rules, null, null, null);
  }

  /**
   * If a synonym with the specified object ID doesn't exist, Algolia adds a new one. Otherwise, the
   * existing synonym is replaced. To add multiple synonyms in a single API request, use the
   * [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveSynonymResponse saveSynonym(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(saveSynonymAsync(indexName, objectID, synonymHit, forwardToReplicas, requestOptions));
  }

  /**
   * If a synonym with the specified object ID doesn't exist, Algolia adds a new one. Otherwise, the
   * existing synonym is replaced. To add multiple synonyms in a single API request, use the
   * [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveSynonymResponse saveSynonym(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.saveSynonym(indexName, objectID, synonymHit, forwardToReplicas, null);
  }

  /**
   * If a synonym with the specified object ID doesn't exist, Algolia adds a new one. Otherwise, the
   * existing synonym is replaced. To add multiple synonyms in a single API request, use the
   * [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveSynonymResponse saveSynonym(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.saveSynonym(indexName, objectID, synonymHit, null, requestOptions);
  }

  /**
   * If a synonym with the specified object ID doesn't exist, Algolia adds a new one. Otherwise, the
   * existing synonym is replaced. To add multiple synonyms in a single API request, use the
   * [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SaveSynonymResponse saveSynonym(@Nonnull String indexName, @Nonnull String objectID, @Nonnull SynonymHit synonymHit)
    throws AlgoliaRuntimeException {
    return this.saveSynonym(indexName, objectID, synonymHit, null, null);
  }

  /**
   * (asynchronously) If a synonym with the specified object ID doesn't exist, Algolia adds a new
   * one. Otherwise, the existing synonym is replaced. To add multiple synonyms in a single API
   * request, use the [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `saveSynonym`.");

    Parameters.requireNonNull(objectID, "Parameter `objectID` is required when calling `saveSynonym`.");

    Parameters.requireNonNull(synonymHit, "Parameter `synonymHit` is required when calling `saveSynonym`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/{objectID}", indexName, objectID)
      .setMethod("PUT")
      .setBody(synonymHit)
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) If a synonym with the specified object ID doesn't exist, Algolia adds a new
   * one. Otherwise, the existing synonym is replaced. To add multiple synonyms in a single API
   * request, use the [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.saveSynonymAsync(indexName, objectID, synonymHit, forwardToReplicas, null);
  }

  /**
   * (asynchronously) If a synonym with the specified object ID doesn't exist, Algolia adds a new
   * one. Otherwise, the existing synonym is replaced. To add multiple synonyms in a single API
   * request, use the [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.saveSynonymAsync(indexName, objectID, synonymHit, null, requestOptions);
  }

  /**
   * (asynchronously) If a synonym with the specified object ID doesn't exist, Algolia adds a new
   * one. Otherwise, the existing synonym is replaced. To add multiple synonyms in a single API
   * request, use the [`batch` operation](#tag/Synonyms/operation/saveSynonyms).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param objectID Unique identifier of a synonym object. (required)
   * @param synonymHit (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymAsync(
    @Nonnull String indexName,
    @Nonnull String objectID,
    @Nonnull SynonymHit synonymHit
  ) throws AlgoliaRuntimeException {
    return this.saveSynonymAsync(indexName, objectID, synonymHit, null, null);
  }

  /**
   * If a synonym with the `objectID` doesn't exist, Algolia adds a new one. Otherwise, existing
   * synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param replaceExistingSynonyms Whether to replace all synonyms in the index with the ones sent
   *     with this request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveSynonyms(
    @Nonnull String indexName,
    @Nonnull List synonymHit,
    Boolean forwardToReplicas,
    Boolean replaceExistingSynonyms,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(saveSynonymsAsync(indexName, synonymHit, forwardToReplicas, replaceExistingSynonyms, requestOptions));
  }

  /**
   * If a synonym with the `objectID` doesn't exist, Algolia adds a new one. Otherwise, existing
   * synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param replaceExistingSynonyms Whether to replace all synonyms in the index with the ones sent
   *     with this request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveSynonyms(
    @Nonnull String indexName,
    @Nonnull List synonymHit,
    Boolean forwardToReplicas,
    Boolean replaceExistingSynonyms
  ) throws AlgoliaRuntimeException {
    return this.saveSynonyms(indexName, synonymHit, forwardToReplicas, replaceExistingSynonyms, null);
  }

  /**
   * If a synonym with the `objectID` doesn't exist, Algolia adds a new one. Otherwise, existing
   * synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveSynonyms(@Nonnull String indexName, @Nonnull List synonymHit, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.saveSynonyms(indexName, synonymHit, null, null, requestOptions);
  }

  /**
   * If a synonym with the `objectID` doesn't exist, Algolia adds a new one. Otherwise, existing
   * synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse saveSynonyms(@Nonnull String indexName, @Nonnull List synonymHit) throws AlgoliaRuntimeException {
    return this.saveSynonyms(indexName, synonymHit, null, null, null);
  }

  /**
   * (asynchronously) If a synonym with the `objectID` doesn't exist, Algolia adds a new one.
   * Otherwise, existing synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param replaceExistingSynonyms Whether to replace all synonyms in the index with the ones sent
   *     with this request. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymsAsync(
    @Nonnull String indexName,
    @Nonnull List synonymHit,
    Boolean forwardToReplicas,
    Boolean replaceExistingSynonyms,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `saveSynonyms`.");

    Parameters.requireNonNull(synonymHit, "Parameter `synonymHit` is required when calling `saveSynonyms`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/batch", indexName)
      .setMethod("POST")
      .setBody(synonymHit)
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .addQueryParameter("replaceExistingSynonyms", replaceExistingSynonyms)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) If a synonym with the `objectID` doesn't exist, Algolia adds a new one.
   * Otherwise, existing synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param replaceExistingSynonyms Whether to replace all synonyms in the index with the ones sent
   *     with this request. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymsAsync(
    @Nonnull String indexName,
    @Nonnull List synonymHit,
    Boolean forwardToReplicas,
    Boolean replaceExistingSynonyms
  ) throws AlgoliaRuntimeException {
    return this.saveSynonymsAsync(indexName, synonymHit, forwardToReplicas, replaceExistingSynonyms, null);
  }

  /**
   * (asynchronously) If a synonym with the `objectID` doesn't exist, Algolia adds a new one.
   * Otherwise, existing synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymsAsync(
    @Nonnull String indexName,
    @Nonnull List synonymHit,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.saveSynonymsAsync(indexName, synonymHit, null, null, requestOptions);
  }

  /**
   * (asynchronously) If a synonym with the `objectID` doesn't exist, Algolia adds a new one.
   * Otherwise, existing synonyms are replaced. This operation is subject to [indexing rate
   * limits](https://support.algolia.com/hc/en-us/articles/4406975251089-Is-there-a-rate-limit-for-indexing-on-Algolia).
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param synonymHit (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture saveSynonymsAsync(@Nonnull String indexName, @Nonnull List synonymHit)
    throws AlgoliaRuntimeException {
    return this.saveSynonymsAsync(indexName, synonymHit, null, null, null);
  }

  /**
   * Sends multiple search requests to one or more indices. This can be useful in these cases: -
   * Different indices for different purposes, such as, one index for products, another one for
   * marketing content. - Multiple searches to the same index—for example, with different filters.
   *
   * @param searchMethodParams Muli-search request body. Results are returned in the same order as
   *     the requests. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponses search(@Nonnull SearchMethodParams searchMethodParams, Class innerType, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchAsync(searchMethodParams, innerType, requestOptions));
  }

  /**
   * Sends multiple search requests to one or more indices. This can be useful in these cases: -
   * Different indices for different purposes, such as, one index for products, another one for
   * marketing content. - Multiple searches to the same index—for example, with different filters.
   *
   * @param searchMethodParams Muli-search request body. Results are returned in the same order as
   *     the requests. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponses search(@Nonnull SearchMethodParams searchMethodParams, Class innerType) throws AlgoliaRuntimeException {
    return this.search(searchMethodParams, innerType, null);
  }

  /**
   * (asynchronously) Sends multiple search requests to one or more indices. This can be useful in
   * these cases: - Different indices for different purposes, such as, one index for products,
   * another one for marketing content. - Multiple searches to the same index—for example, with
   * different filters.
   *
   * @param searchMethodParams Muli-search request body. Results are returned in the same order as
   *     the requests. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchAsync(
    @Nonnull SearchMethodParams searchMethodParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(searchMethodParams, "Parameter `searchMethodParams` is required when calling `search`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/*/queries")
      .setMethod("POST")
      .setBody(searchMethodParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, SearchResponses.class, innerType);
  }

  /**
   * (asynchronously) Sends multiple search requests to one or more indices. This can be useful in
   * these cases: - Different indices for different purposes, such as, one index for products,
   * another one for marketing content. - Multiple searches to the same index—for example, with
   * different filters.
   *
   * @param searchMethodParams Muli-search request body. Results are returned in the same order as
   *     the requests. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchAsync(@Nonnull SearchMethodParams searchMethodParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.searchAsync(searchMethodParams, innerType, null);
  }

  /**
   * Searches for standard and custom dictionary entries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param searchDictionaryEntriesParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchDictionaryEntriesResponse searchDictionaryEntries(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull SearchDictionaryEntriesParams searchDictionaryEntriesParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchDictionaryEntriesAsync(dictionaryName, searchDictionaryEntriesParams, requestOptions));
  }

  /**
   * Searches for standard and custom dictionary entries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param searchDictionaryEntriesParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchDictionaryEntriesResponse searchDictionaryEntries(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull SearchDictionaryEntriesParams searchDictionaryEntriesParams
  ) throws AlgoliaRuntimeException {
    return this.searchDictionaryEntries(dictionaryName, searchDictionaryEntriesParams, null);
  }

  /**
   * (asynchronously) Searches for standard and custom dictionary entries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param searchDictionaryEntriesParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchDictionaryEntriesAsync(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull SearchDictionaryEntriesParams searchDictionaryEntriesParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(dictionaryName, "Parameter `dictionaryName` is required when calling `searchDictionaryEntries`.");

    Parameters.requireNonNull(
      searchDictionaryEntriesParams,
      "Parameter `searchDictionaryEntriesParams` is required when calling" + " `searchDictionaryEntries`."
    );

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/dictionaries/{dictionaryName}/search", dictionaryName)
      .setMethod("POST")
      .setBody(searchDictionaryEntriesParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Searches for standard and custom dictionary entries.
   *
   * @param dictionaryName Dictionary type in which to search. (required)
   * @param searchDictionaryEntriesParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchDictionaryEntriesAsync(
    @Nonnull DictionaryType dictionaryName,
    @Nonnull SearchDictionaryEntriesParams searchDictionaryEntriesParams
  ) throws AlgoliaRuntimeException {
    return this.searchDictionaryEntriesAsync(dictionaryName, searchDictionaryEntriesParams, null);
  }

  /**
   * Searches for values of a specified facet attribute. - By default, facet values are sorted by
   * decreasing count. You can adjust this with the `sortFacetValueBy` parameter. - Searching for
   * facet values doesn't work if you have **more than 65 searchable facets and searchable
   * attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param searchForFacetValuesRequest (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchForFacetValuesResponse searchForFacetValues(
    @Nonnull String indexName,
    @Nonnull String facetName,
    SearchForFacetValuesRequest searchForFacetValuesRequest,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchForFacetValuesAsync(indexName, facetName, searchForFacetValuesRequest, requestOptions));
  }

  /**
   * Searches for values of a specified facet attribute. - By default, facet values are sorted by
   * decreasing count. You can adjust this with the `sortFacetValueBy` parameter. - Searching for
   * facet values doesn't work if you have **more than 65 searchable facets and searchable
   * attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param searchForFacetValuesRequest (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchForFacetValuesResponse searchForFacetValues(
    @Nonnull String indexName,
    @Nonnull String facetName,
    SearchForFacetValuesRequest searchForFacetValuesRequest
  ) throws AlgoliaRuntimeException {
    return this.searchForFacetValues(indexName, facetName, searchForFacetValuesRequest, null);
  }

  /**
   * Searches for values of a specified facet attribute. - By default, facet values are sorted by
   * decreasing count. You can adjust this with the `sortFacetValueBy` parameter. - Searching for
   * facet values doesn't work if you have **more than 65 searchable facets and searchable
   * attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchForFacetValuesResponse searchForFacetValues(
    @Nonnull String indexName,
    @Nonnull String facetName,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.searchForFacetValues(indexName, facetName, null, requestOptions);
  }

  /**
   * Searches for values of a specified facet attribute. - By default, facet values are sorted by
   * decreasing count. You can adjust this with the `sortFacetValueBy` parameter. - Searching for
   * facet values doesn't work if you have **more than 65 searchable facets and searchable
   * attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchForFacetValuesResponse searchForFacetValues(@Nonnull String indexName, @Nonnull String facetName)
    throws AlgoliaRuntimeException {
    return this.searchForFacetValues(indexName, facetName, null, null);
  }

  /**
   * (asynchronously) Searches for values of a specified facet attribute. - By default, facet values
   * are sorted by decreasing count. You can adjust this with the `sortFacetValueBy` parameter. -
   * Searching for facet values doesn't work if you have **more than 65 searchable facets and
   * searchable attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param searchForFacetValuesRequest (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchForFacetValuesAsync(
    @Nonnull String indexName,
    @Nonnull String facetName,
    SearchForFacetValuesRequest searchForFacetValuesRequest,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `searchForFacetValues`.");

    Parameters.requireNonNull(facetName, "Parameter `facetName` is required when calling `searchForFacetValues`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/facets/{facetName}/query", indexName, facetName)
      .setMethod("POST")
      .setBody(searchForFacetValuesRequest)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Searches for values of a specified facet attribute. - By default, facet values
   * are sorted by decreasing count. You can adjust this with the `sortFacetValueBy` parameter. -
   * Searching for facet values doesn't work if you have **more than 65 searchable facets and
   * searchable attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param searchForFacetValuesRequest (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchForFacetValuesAsync(
    @Nonnull String indexName,
    @Nonnull String facetName,
    SearchForFacetValuesRequest searchForFacetValuesRequest
  ) throws AlgoliaRuntimeException {
    return this.searchForFacetValuesAsync(indexName, facetName, searchForFacetValuesRequest, null);
  }

  /**
   * (asynchronously) Searches for values of a specified facet attribute. - By default, facet values
   * are sorted by decreasing count. You can adjust this with the `sortFacetValueBy` parameter. -
   * Searching for facet values doesn't work if you have **more than 65 searchable facets and
   * searchable attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchForFacetValuesAsync(
    @Nonnull String indexName,
    @Nonnull String facetName,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.searchForFacetValuesAsync(indexName, facetName, null, requestOptions);
  }

  /**
   * (asynchronously) Searches for values of a specified facet attribute. - By default, facet values
   * are sorted by decreasing count. You can adjust this with the `sortFacetValueBy` parameter. -
   * Searching for facet values doesn't work if you have **more than 65 searchable facets and
   * searchable attributes combined**.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param facetName Facet attribute in which to search for values. This attribute must be included
   *     in the `attributesForFaceting` index setting with the `searchable()` modifier. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchForFacetValuesAsync(@Nonnull String indexName, @Nonnull String facetName)
    throws AlgoliaRuntimeException {
    return this.searchForFacetValuesAsync(indexName, facetName, null, null);
  }

  /**
   * Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchRulesParams (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchRulesResponse searchRules(@Nonnull String indexName, SearchRulesParams searchRulesParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchRulesAsync(indexName, searchRulesParams, requestOptions));
  }

  /**
   * Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchRulesParams (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchRulesResponse searchRules(@Nonnull String indexName, SearchRulesParams searchRulesParams) throws AlgoliaRuntimeException {
    return this.searchRules(indexName, searchRulesParams, null);
  }

  /**
   * Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchRulesResponse searchRules(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.searchRules(indexName, null, requestOptions);
  }

  /**
   * Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchRulesResponse searchRules(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.searchRules(indexName, null, null);
  }

  /**
   * (asynchronously) Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchRulesParams (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchRulesAsync(
    @Nonnull String indexName,
    SearchRulesParams searchRulesParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `searchRules`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/rules/search", indexName)
      .setMethod("POST")
      .setBody(searchRulesParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchRulesParams (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchRulesAsync(@Nonnull String indexName, SearchRulesParams searchRulesParams)
    throws AlgoliaRuntimeException {
    return this.searchRulesAsync(indexName, searchRulesParams, null);
  }

  /**
   * (asynchronously) Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchRulesAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.searchRulesAsync(indexName, null, requestOptions);
  }

  /**
   * (asynchronously) Searches for rules in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchRulesAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.searchRulesAsync(indexName, null, null);
  }

  /**
   * Searches a single index and returns matching search results (_hits_). This method lets you
   * retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponse searchSingleIndex(
    @Nonnull String indexName,
    SearchParams searchParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchSingleIndexAsync(indexName, searchParams, innerType, requestOptions));
  }

  /**
   * Searches a single index and returns matching search results (_hits_). This method lets you
   * retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponse searchSingleIndex(@Nonnull String indexName, SearchParams searchParams, Class innerType)
    throws AlgoliaRuntimeException {
    return this.searchSingleIndex(indexName, searchParams, innerType, null);
  }

  /**
   * Searches a single index and returns matching search results (_hits_). This method lets you
   * retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponse searchSingleIndex(@Nonnull String indexName, Class innerType, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.searchSingleIndex(indexName, null, innerType, requestOptions);
  }

  /**
   * Searches a single index and returns matching search results (_hits_). This method lets you
   * retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  SearchResponse searchSingleIndex(@Nonnull String indexName, Class innerType) throws AlgoliaRuntimeException {
    return this.searchSingleIndex(indexName, null, innerType, null);
  }

  /**
   * (asynchronously) Searches a single index and returns matching search results (_hits_). This
   * method lets you retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchSingleIndexAsync(
    @Nonnull String indexName,
    SearchParams searchParams,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `searchSingleIndex`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/query", indexName)
      .setMethod("POST")
      .setBody(searchParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, SearchResponse.class, innerType);
  }

  /**
   * (asynchronously) Searches a single index and returns matching search results (_hits_). This
   * method lets you retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchParams (optional)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchSingleIndexAsync(
    @Nonnull String indexName,
    SearchParams searchParams,
    Class innerType
  ) throws AlgoliaRuntimeException {
    return this.searchSingleIndexAsync(indexName, searchParams, innerType, null);
  }

  /**
   * (asynchronously) Searches a single index and returns matching search results (_hits_). This
   * method lets you retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchSingleIndexAsync(
    @Nonnull String indexName,
    Class innerType,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.searchSingleIndexAsync(indexName, null, innerType, requestOptions);
  }

  /**
   * (asynchronously) Searches a single index and returns matching search results (_hits_). This
   * method lets you retrieve up to 1,000 hits. If you need more, use the [`browse`
   * operation](#tag/Search/operation/browse) or increase the `paginatedLimitedTo` index setting.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public  CompletableFuture> searchSingleIndexAsync(@Nonnull String indexName, Class innerType)
    throws AlgoliaRuntimeException {
    return this.searchSingleIndexAsync(indexName, null, innerType, null);
  }

  /**
   * Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchSynonymsParams Body of the `searchSynonyms` operation. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchSynonymsResponse searchSynonyms(
    @Nonnull String indexName,
    SearchSynonymsParams searchSynonymsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchSynonymsAsync(indexName, searchSynonymsParams, requestOptions));
  }

  /**
   * Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchSynonymsParams Body of the `searchSynonyms` operation. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchSynonymsResponse searchSynonyms(@Nonnull String indexName, SearchSynonymsParams searchSynonymsParams)
    throws AlgoliaRuntimeException {
    return this.searchSynonyms(indexName, searchSynonymsParams, null);
  }

  /**
   * Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchSynonymsResponse searchSynonyms(@Nonnull String indexName, RequestOptions requestOptions) throws AlgoliaRuntimeException {
    return this.searchSynonyms(indexName, null, requestOptions);
  }

  /**
   * Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchSynonymsResponse searchSynonyms(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.searchSynonyms(indexName, null, null);
  }

  /**
   * (asynchronously) Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchSynonymsParams Body of the `searchSynonyms` operation. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchSynonymsAsync(
    @Nonnull String indexName,
    SearchSynonymsParams searchSynonymsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `searchSynonyms`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/synonyms/search", indexName)
      .setMethod("POST")
      .setBody(searchSynonymsParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param searchSynonymsParams Body of the `searchSynonyms` operation. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchSynonymsAsync(
    @Nonnull String indexName,
    SearchSynonymsParams searchSynonymsParams
  ) throws AlgoliaRuntimeException {
    return this.searchSynonymsAsync(indexName, searchSynonymsParams, null);
  }

  /**
   * (asynchronously) Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchSynonymsAsync(@Nonnull String indexName, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.searchSynonymsAsync(indexName, null, requestOptions);
  }

  /**
   * (asynchronously) Searches for synonyms in your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchSynonymsAsync(@Nonnull String indexName) throws AlgoliaRuntimeException {
    return this.searchSynonymsAsync(indexName, null, null);
  }

  /**
   * Since it can take a few seconds to get the data from the different clusters, the response isn't
   * real-time. To ensure rapid updates, the user IDs index isn't built at the same time as the
   * mapping. Instead, it's built every 12 hours, at the same time as the update of user ID usage.
   * For example, if you add or move a user ID, the search will show an old value until the next
   * time the mapping is rebuilt (every 12 hours).
   *
   * @param searchUserIdsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchUserIdsResponse searchUserIds(@Nonnull SearchUserIdsParams searchUserIdsParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(searchUserIdsAsync(searchUserIdsParams, requestOptions));
  }

  /**
   * Since it can take a few seconds to get the data from the different clusters, the response isn't
   * real-time. To ensure rapid updates, the user IDs index isn't built at the same time as the
   * mapping. Instead, it's built every 12 hours, at the same time as the update of user ID usage.
   * For example, if you add or move a user ID, the search will show an old value until the next
   * time the mapping is rebuilt (every 12 hours).
   *
   * @param searchUserIdsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public SearchUserIdsResponse searchUserIds(@Nonnull SearchUserIdsParams searchUserIdsParams) throws AlgoliaRuntimeException {
    return this.searchUserIds(searchUserIdsParams, null);
  }

  /**
   * (asynchronously) Since it can take a few seconds to get the data from the different clusters,
   * the response isn't real-time. To ensure rapid updates, the user IDs index isn't built at the
   * same time as the mapping. Instead, it's built every 12 hours, at the same time as the update of
   * user ID usage. For example, if you add or move a user ID, the search will show an old value
   * until the next time the mapping is rebuilt (every 12 hours).
   *
   * @param searchUserIdsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchUserIdsAsync(
    @Nonnull SearchUserIdsParams searchUserIdsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(searchUserIdsParams, "Parameter `searchUserIdsParams` is required when calling `searchUserIds`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/clusters/mapping/search")
      .setMethod("POST")
      .setBody(searchUserIdsParams)
      .setRead(true)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Since it can take a few seconds to get the data from the different clusters,
   * the response isn't real-time. To ensure rapid updates, the user IDs index isn't built at the
   * same time as the mapping. Instead, it's built every 12 hours, at the same time as the update of
   * user ID usage. For example, if you add or move a user ID, the search will show an old value
   * until the next time the mapping is rebuilt (every 12 hours).
   *
   * @param searchUserIdsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture searchUserIdsAsync(@Nonnull SearchUserIdsParams searchUserIdsParams)
    throws AlgoliaRuntimeException {
    return this.searchUserIdsAsync(searchUserIdsParams, null);
  }

  /**
   * Turns standard stop word dictionary entries on or off for a given language.
   *
   * @param dictionarySettingsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setDictionarySettings(@Nonnull DictionarySettingsParams dictionarySettingsParams, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(setDictionarySettingsAsync(dictionarySettingsParams, requestOptions));
  }

  /**
   * Turns standard stop word dictionary entries on or off for a given language.
   *
   * @param dictionarySettingsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setDictionarySettings(@Nonnull DictionarySettingsParams dictionarySettingsParams)
    throws AlgoliaRuntimeException {
    return this.setDictionarySettings(dictionarySettingsParams, null);
  }

  /**
   * (asynchronously) Turns standard stop word dictionary entries on or off for a given language.
   *
   * @param dictionarySettingsParams (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setDictionarySettingsAsync(
    @Nonnull DictionarySettingsParams dictionarySettingsParams,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(
      dictionarySettingsParams,
      "Parameter `dictionarySettingsParams` is required when calling `setDictionarySettings`."
    );

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/dictionaries/*/settings")
      .setMethod("PUT")
      .setBody(dictionarySettingsParams)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Turns standard stop word dictionary entries on or off for a given language.
   *
   * @param dictionarySettingsParams (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setDictionarySettingsAsync(@Nonnull DictionarySettingsParams dictionarySettingsParams)
    throws AlgoliaRuntimeException {
    return this.setDictionarySettingsAsync(dictionarySettingsParams, null);
  }

  /**
   * Update the specified index settings. Index settings that you don't specify are left unchanged.
   * Specify `null` to reset a setting to its default value. For best performance, update the index
   * settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setSettings(
    @Nonnull String indexName,
    @Nonnull IndexSettings indexSettings,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return LaunderThrowable.await(setSettingsAsync(indexName, indexSettings, forwardToReplicas, requestOptions));
  }

  /**
   * Update the specified index settings. Index settings that you don't specify are left unchanged.
   * Specify `null` to reset a setting to its default value. For best performance, update the index
   * settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setSettings(@Nonnull String indexName, @Nonnull IndexSettings indexSettings, Boolean forwardToReplicas)
    throws AlgoliaRuntimeException {
    return this.setSettings(indexName, indexSettings, forwardToReplicas, null);
  }

  /**
   * Update the specified index settings. Index settings that you don't specify are left unchanged.
   * Specify `null` to reset a setting to its default value. For best performance, update the index
   * settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setSettings(@Nonnull String indexName, @Nonnull IndexSettings indexSettings, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return this.setSettings(indexName, indexSettings, null, requestOptions);
  }

  /**
   * Update the specified index settings. Index settings that you don't specify are left unchanged.
   * Specify `null` to reset a setting to its default value. For best performance, update the index
   * settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdatedAtResponse setSettings(@Nonnull String indexName, @Nonnull IndexSettings indexSettings) throws AlgoliaRuntimeException {
    return this.setSettings(indexName, indexSettings, null, null);
  }

  /**
   * (asynchronously) Update the specified index settings. Index settings that you don't specify are
   * left unchanged. Specify `null` to reset a setting to its default value. For best performance,
   * update the index settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setSettingsAsync(
    @Nonnull String indexName,
    @Nonnull IndexSettings indexSettings,
    Boolean forwardToReplicas,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(indexName, "Parameter `indexName` is required when calling `setSettings`.");

    Parameters.requireNonNull(indexSettings, "Parameter `indexSettings` is required when calling `setSettings`.");

    HttpRequest request = HttpRequest.builder()
      .setPath("/1/indexes/{indexName}/settings", indexName)
      .setMethod("PUT")
      .setBody(indexSettings)
      .addQueryParameter("forwardToReplicas", forwardToReplicas)
      .build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Update the specified index settings. Index settings that you don't specify are
   * left unchanged. Specify `null` to reset a setting to its default value. For best performance,
   * update the index settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param forwardToReplicas Whether changes are applied to replica indices. (optional)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setSettingsAsync(
    @Nonnull String indexName,
    @Nonnull IndexSettings indexSettings,
    Boolean forwardToReplicas
  ) throws AlgoliaRuntimeException {
    return this.setSettingsAsync(indexName, indexSettings, forwardToReplicas, null);
  }

  /**
   * (asynchronously) Update the specified index settings. Index settings that you don't specify are
   * left unchanged. Specify `null` to reset a setting to its default value. For best performance,
   * update the index settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setSettingsAsync(
    @Nonnull String indexName,
    @Nonnull IndexSettings indexSettings,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    return this.setSettingsAsync(indexName, indexSettings, null, requestOptions);
  }

  /**
   * (asynchronously) Update the specified index settings. Index settings that you don't specify are
   * left unchanged. Specify `null` to reset a setting to its default value. For best performance,
   * update the index settings before you add new records to your index.
   *
   * @param indexName Name of the index on which to perform the operation. (required)
   * @param indexSettings (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture setSettingsAsync(@Nonnull String indexName, @Nonnull IndexSettings indexSettings)
    throws AlgoliaRuntimeException {
    return this.setSettingsAsync(indexName, indexSettings, null, null);
  }

  /**
   * Replaces the permissions of an existing API key. Any unspecified attribute resets that
   * attribute to its default value.
   *
   * @param key API key. (required)
   * @param apiKey (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdateApiKeyResponse updateApiKey(@Nonnull String key, @Nonnull ApiKey apiKey, RequestOptions requestOptions)
    throws AlgoliaRuntimeException {
    return LaunderThrowable.await(updateApiKeyAsync(key, apiKey, requestOptions));
  }

  /**
   * Replaces the permissions of an existing API key. Any unspecified attribute resets that
   * attribute to its default value.
   *
   * @param key API key. (required)
   * @param apiKey (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public UpdateApiKeyResponse updateApiKey(@Nonnull String key, @Nonnull ApiKey apiKey) throws AlgoliaRuntimeException {
    return this.updateApiKey(key, apiKey, null);
  }

  /**
   * (asynchronously) Replaces the permissions of an existing API key. Any unspecified attribute
   * resets that attribute to its default value.
   *
   * @param key API key. (required)
   * @param apiKey (required)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions.
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture updateApiKeyAsync(
    @Nonnull String key,
    @Nonnull ApiKey apiKey,
    RequestOptions requestOptions
  ) throws AlgoliaRuntimeException {
    Parameters.requireNonNull(key, "Parameter `key` is required when calling `updateApiKey`.");

    Parameters.requireNonNull(apiKey, "Parameter `apiKey` is required when calling `updateApiKey`.");

    HttpRequest request = HttpRequest.builder().setPath("/1/keys/{key}", key).setMethod("PUT").setBody(apiKey).build();
    return executeAsync(request, requestOptions, new TypeReference() {});
  }

  /**
   * (asynchronously) Replaces the permissions of an existing API key. Any unspecified attribute
   * resets that attribute to its default value.
   *
   * @param key API key. (required)
   * @param apiKey (required)
   * @throws AlgoliaRuntimeException If it fails to process the API call
   */
  public CompletableFuture updateApiKeyAsync(@Nonnull String key, @Nonnull ApiKey apiKey)
    throws AlgoliaRuntimeException {
    return this.updateApiKeyAsync(key, apiKey, null);
  }

  /**
   * Helper: Wait for a task to complete with `indexName` and `taskID`.
   *
   * @param indexName The `indexName` where the operation was performed.
   * @param taskID The `taskID` returned in the method response.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetTaskResponse waitForTask(
    String indexName,
    Long taskID,
    int maxRetries,
    IntUnaryOperator timeout,
    RequestOptions requestOptions
  ) {
    return TaskUtils.retryUntil(
      () -> this.getTask(indexName, taskID, requestOptions),
      (GetTaskResponse task) -> task.getStatus() == TaskStatus.PUBLISHED,
      maxRetries,
      timeout
    );
  }

  /**
   * Helper: Wait for a task to complete with `indexName` and `taskID`.
   *
   * @param indexName The `indexName` where the operation was performed.
   * @param taskID The `taskID` returned in the method response.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetTaskResponse waitForTask(String indexName, Long taskID, RequestOptions requestOptions) {
    return this.waitForTask(indexName, taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions);
  }

  /**
   * Helper: Wait for a task to complete with `indexName` and `taskID`.
   *
   * @param indexName The `indexName` where the operation was performed.
   * @param taskID The `taskID` returned in the method response.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   */
  public GetTaskResponse waitForTask(String indexName, Long taskID, int maxRetries, IntUnaryOperator timeout) {
    return this.waitForTask(indexName, taskID, maxRetries, timeout, null);
  }

  /**
   * Helper: Wait for a task to complete with `indexName` and `taskID`.
   *
   * @param indexName The `indexName` where the operation was performed.
   * @param taskID The `taskID` returned in the method response.
   */
  public GetTaskResponse waitForTask(String indexName, Long taskID) {
    return this.waitForTask(indexName, taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null);
  }

  /**
   * Helper: Wait for a application-level task to complete with `taskID`.
   *
   * @param taskID The `taskID` returned in the method response.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetTaskResponse waitForAppTask(Long taskID, int maxRetries, IntUnaryOperator timeout, RequestOptions requestOptions) {
    return TaskUtils.retryUntil(
      () -> this.getAppTask(taskID, requestOptions),
      (GetTaskResponse task) -> task.getStatus() == TaskStatus.PUBLISHED,
      maxRetries,
      timeout
    );
  }

  /**
   * Helper: Wait for an application-level task to complete with `taskID`.
   *
   * @param taskID The `taskID` returned in the method response.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetTaskResponse waitForAppTask(Long taskID, RequestOptions requestOptions) {
    return this.waitForAppTask(taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions);
  }

  /**
   * Helper: Wait for an application-level task to complete with `taskID`.
   *
   * @param taskID The `taskID` returned in the method response.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   */
  public GetTaskResponse waitForAppTask(Long taskID, int maxRetries, IntUnaryOperator timeout) {
    return this.waitForAppTask(taskID, maxRetries, timeout, null);
  }

  /**
   * Helper: Wait for an application-level task to complete with `taskID`.
   *
   * @param taskID The `taskID` returned in the method response.
   */
  public GetTaskResponse waitForAppTask(Long taskID) {
    return this.waitForAppTask(taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null);
  }

  /**
   * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
   *
   * @param operation The `operation` that was done on a `key`.
   * @param key The `key` that has been added, deleted or updated.
   * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of
   *     the response with it.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetApiKeyResponse waitForApiKey(
    String key,
    ApiKeyOperation operation,
    ApiKey apiKey,
    int maxRetries,
    IntUnaryOperator timeout,
    RequestOptions requestOptions
  ) {
    if (operation == ApiKeyOperation.UPDATE) {
      if (apiKey == null) {
        throw new AlgoliaRuntimeException("`apiKey` is required when waiting for an `update` operation.");
      }

      // when updating an api key, we poll the api until we receive a different key
      return TaskUtils.retryUntil(
        () -> this.getApiKey(key, requestOptions),
        (GetApiKeyResponse respKey) -> {
          // we need to convert to an ApiKey object to use the `equals` method
          ApiKey sameType = new ApiKey()
            .setAcl(respKey.getAcl())
            .setDescription(respKey.getDescription())
            .setIndexes(respKey.getIndexes())
            .setMaxHitsPerQuery(respKey.getMaxHitsPerQuery())
            .setMaxQueriesPerIPPerHour(respKey.getMaxQueriesPerIPPerHour())
            .setQueryParameters(respKey.getQueryParameters())
            .setReferers(respKey.getReferers())
            .setValidity(respKey.getValidity());

          return apiKey.equals(sameType);
        },
        maxRetries,
        timeout
      );
    }

    return TaskUtils.retryUntil(
      () -> {
        try {
          return this.getApiKey(key, requestOptions);
        } catch (AlgoliaApiException e) {
          if (e.getStatusCode() == 404) {
            return null;
          }

          throw e;
        }
      },
      (GetApiKeyResponse response) -> {
        switch (operation) {
          case ADD:
            // stop when we don't receive 404 meaning the key is created
            return response != null;
          case DELETE:
            // stop when the key is not found
            return response == null;
          default:
            // continue
            return false;
        }
      },
      maxRetries,
      timeout
    );
  }

  /**
   * Helper: Wait for an API key to be added or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added or deleted.
   * @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetApiKeyResponse waitForApiKey(
    String key,
    ApiKeyOperation operation,
    int maxRetries,
    IntUnaryOperator timeout,
    RequestOptions requestOptions
  ) {
    return this.waitForApiKey(key, operation, null, maxRetries, timeout, requestOptions);
  }

  /**
   * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added, deleted or updated.
   * @param operation The `operation` that was done on a `key`.
   * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of
   *     the response with it.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation, ApiKey apiKey, RequestOptions requestOptions) {
    return this.waitForApiKey(key, operation, apiKey, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions);
  }

  /**
   * Helper: Wait for an API key to be added or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added or deleted.
   * @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation, RequestOptions requestOptions) {
    return this.waitForApiKey(key, operation, null, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions);
  }

  /**
   * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added, deleted or updated.
   * @param operation The `operation` that was done on a `key`.
   * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of
   *     the response with it.
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation, ApiKey apiKey, int maxRetries, IntUnaryOperator timeout) {
    return this.waitForApiKey(key, operation, apiKey, maxRetries, timeout, null);
  }

  /**
   * Helper: Wait for an API key to be added or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added or deleted.
   * @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
   * @param maxRetries The maximum number of retry. 50 by default. (optional)
   * @param timeout The function to decide how long to wait between retries. min(retries * 200,
   *     5000) by default. (optional)
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation, int maxRetries, IntUnaryOperator timeout) {
    return this.waitForApiKey(key, operation, null, maxRetries, timeout, null);
  }

  /**
   * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added, deleted or updated.
   * @param operation The `operation` that was done on a `key`.
   * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of
   *     the response with it.
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation, ApiKey apiKey) {
    return this.waitForApiKey(key, operation, apiKey, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null);
  }

  /**
   * Helper: Wait for an API key to be added or deleted based on a given `operation`.
   *
   * @param key The `key` that has been added or deleted.
   * @param operation The `operation` that was done on a `key`. (ADD or DELETE only)
   */
  public GetApiKeyResponse waitForApiKey(String key, ApiKeyOperation operation) {
    return this.waitForApiKey(key, operation, null, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null);
  }

  /**
   * Helper: Returns an iterator on top of the `browse` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `browse` parameters.
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  Iterable browseObjects(String indexName, BrowseParamsObject params, Class innerType, RequestOptions requestOptions) {
    final Holder currentCursor = new Holder<>();

    if (params.getHitsPerPage() == null) {
      params.setHitsPerPage(1000);
    }

    return AlgoliaIterableHelper.createIterable(
      () -> {
        BrowseResponse response = this.browse(indexName, params, innerType, requestOptions);
        params.setCursor(response.getCursor());
        currentCursor.value = response.getCursor();
        return response.getHits().iterator();
      },
      () -> currentCursor.value != null
    );
  }

  /**
   * Helper: Returns an iterator on top of the `browse` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `browse` parameters.
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   */
  public  Iterable browseObjects(String indexName, BrowseParamsObject params, Class innerType) {
    return browseObjects(indexName, params, innerType, null);
  }

  /**
   * Helper: Returns an iterator on top of the `browse` method.
   *
   * @param indexName The index in which to perform the request.
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   */
  public  Iterable browseObjects(String indexName, Class innerType) {
    return browseObjects(indexName, new BrowseParamsObject(), innerType, null);
  }

  /**
   * Helper: Returns an iterator on top of the `searchSynonyms` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `searchSynonyms` parameters. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public Iterable browseSynonyms(String indexName, SearchSynonymsParams params, RequestOptions requestOptions) {
    final Holder currentPage = new Holder<>(0);

    params.setPage(0);
    params.setHitsPerPage(1000);

    return AlgoliaIterableHelper.createIterable(
      () -> {
        SearchSynonymsResponse response = this.searchSynonyms(indexName, params, requestOptions);
        currentPage.value = response.getHits().size() < params.getHitsPerPage() ? null : currentPage.value + 1;
        return response.getHits().iterator();
      },
      () -> currentPage.value != null
    );
  }

  /**
   * Helper: Returns an iterator on top of the `searchSynonyms` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `searchSynonyms` parameters .(optional)
   */
  public Iterable browseSynonyms(String indexName, SearchSynonymsParams params) {
    return browseSynonyms(indexName, params, null);
  }

  /**
   * Helper: Returns an iterator on top of the `searchSynonyms` method.
   *
   * @param indexName The index in which to perform the request.
   */
  public Iterable browseSynonyms(String indexName) {
    return browseSynonyms(indexName, null, null);
  }

  /**
   * Helper: Returns an iterator on top of the `searchRules` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `searchRules` parameters. (optional)
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public Iterable browseRules(String indexName, SearchRulesParams params, RequestOptions requestOptions) {
    final Holder currentPage = new Holder<>(0);
    final int hitsPerPage = 1000;
    params.setHitsPerPage(hitsPerPage);

    return AlgoliaIterableHelper.createIterable(
      () -> {
        SearchRulesResponse response = this.searchRules(indexName, params.setPage(currentPage.value), requestOptions);
        currentPage.value = response.getHits().size() < hitsPerPage ? null : currentPage.value + 1;
        return response.getHits().iterator();
      },
      () -> currentPage.value != null
    );
  }

  /**
   * Helper: Returns an iterator on top of the `searchRules` method.
   *
   * @param indexName The index in which to perform the request.
   * @param params The `searchRules` parameters. (optional)
   */
  public Iterable browseRules(String indexName, SearchRulesParams params) {
    return browseRules(indexName, params, null);
  }

  /**
   * Helper: Returns an iterator on top of the `searchRules` method.
   *
   * @param indexName The index in which to perform the request.
   */
  public Iterable browseRules(String indexName) {
    return browseRules(indexName, new SearchRulesParams(), null);
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   */
  public  List> searchForHits(@Nonnull List requests, Class innerType) {
    return LaunderThrowable.await(searchForHitsAsync(requests, null, innerType, null));
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   */
  public  List> searchForHits(@Nonnull List requests, SearchStrategy strategy, Class innerType) {
    return LaunderThrowable.await(searchForHitsAsync(requests, strategy, innerType, null));
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   * @param requestOptions Additional options for the search request.
   */
  public  List> searchForHits(
    @Nonnull List requests,
    SearchStrategy strategy,
    Class innerType,
    RequestOptions requestOptions
  ) {
    return LaunderThrowable.await(searchForHitsAsync(requests, strategy, innerType, requestOptions));
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   */
  public  CompletableFuture>> searchForHitsAsync(@Nonnull List requests, Class innerType) {
    return searchForHitsAsync(requests, null, innerType, null);
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   */
  public  CompletableFuture>> searchForHitsAsync(
    @Nonnull List requests,
    SearchStrategy strategy,
    Class innerType
  ) {
    return searchForHitsAsync(requests, strategy, innerType, null);
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia records (hits). Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param innerType The class held by the index, could be your custom class or {@link Object}.
   * @param strategy The search strategy to be employed during the search.
   * @param requestOptions Additional options for the search request.
   */
  public  CompletableFuture>> searchForHitsAsync(
    @Nonnull List requests,
    SearchStrategy strategy,
    Class innerType,
    RequestOptions requestOptions
  ) {
    final List searchQueries = new ArrayList<>(requests); // Upcast the list
    final SearchMethodParams params = new SearchMethodParams().setRequests(searchQueries).setStrategy(strategy);
    return searchAsync(params, innerType).thenApply(searchResponses ->
      searchResponses.getResults().stream().map(res -> (SearchResponse) res).collect(Collectors.toList())
    );
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   */
  public List searchForFacets(@Nonnull List requests) {
    return LaunderThrowable.await(searchForFacetsAsync(requests, null, null));
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   */
  public List searchForFacets(@Nonnull List requests, SearchStrategy strategy) {
    return LaunderThrowable.await(searchForFacetsAsync(requests, strategy, null));
  }

  /**
   * Executes a synchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   * @param requestOptions Additional options for the search request.
   */
  public List searchForFacets(
    @Nonnull List requests,
    SearchStrategy strategy,
    RequestOptions requestOptions
  ) {
    return LaunderThrowable.await(searchForFacetsAsync(requests, strategy, requestOptions));
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   */
  public CompletableFuture> searchForFacetsAsync(@Nonnull List requests) {
    return searchForFacetsAsync(requests, null, null);
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   */
  public CompletableFuture> searchForFacetsAsync(
    @Nonnull List requests,
    SearchStrategy strategy
  ) {
    return searchForFacetsAsync(requests, strategy, null);
  }

  /**
   * Executes an asynchronous search for the provided search requests, with certainty that we will
   * only request Algolia facets. Results will be received in the same order as the queries.
   *
   * @param requests A list of search requests to be executed.
   * @param strategy The search strategy to be employed during the search.
   * @param requestOptions Additional options for the search request.
   */
  public CompletableFuture> searchForFacetsAsync(
    @Nonnull List requests,
    SearchStrategy strategy,
    RequestOptions requestOptions
  ) {
    final List searchQueries = new ArrayList<>(requests); // Upcast the list
    final SearchMethodParams params = new SearchMethodParams().setRequests(searchQueries).setStrategy(strategy);
    return searchAsync(params, Hit.class).thenApply(searchResponses ->
      searchResponses.getResults().stream().map(res -> (SearchForFacetValuesResponse) res).collect(Collectors.toList())
    );
  }

  /**
   * Helper: Chunks the given `objects` list in subset of 1000 elements max in order to make it fit
   * in `batch` requests.
   *
   * @summary Helper: Chunks the given `objects` list in subset of 1000 elements max in order to
   *     make it fit in `batch` requests.
   * @param indexName - The `indexName` to replace `objects` in.
   * @param objects - The array of `objects` to store in the given Algolia `indexName`.
   * @param action - The `batch` `action` to perform on the given array of `objects`.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param batchSize - The size of the chunk of `objects`. The number of `batch` calls will be
   *     equal to `length(objects) / batchSize`. Defaults to 1000.
   * @param requestOptions - The requestOptions to send along with the query, they will be forwarded
   *     to the `getTask` method and merged with the transporter requestOptions.
   */
  public  List chunkedBatch(
    String indexName,
    Iterable objects,
    Action action,
    boolean waitForTasks,
    int batchSize,
    RequestOptions requestOptions
  ) {
    List responses = new ArrayList<>();
    List requests = new ArrayList<>();

    for (T item : objects) {
      if (requests.size() == batchSize) {
        BatchResponse batch = batch(indexName, new BatchWriteParams().setRequests(requests), requestOptions);
        responses.add(batch);
        requests.clear();
      }

      requests.add(new BatchRequest().setAction(action).setBody(item));
    }

    if (requests.size() > 0) {
      BatchResponse batch = batch(indexName, new BatchWriteParams().setRequests(requests), requestOptions);
      responses.add(batch);
    }

    if (waitForTasks) {
      responses.forEach(response -> waitForTask(indexName, response.getTaskID(), requestOptions));
    }

    return responses;
  }

  public  List chunkedBatch(String indexName, Iterable objects, Action action, boolean waitForTasks) {
    return chunkedBatch(indexName, objects, action, waitForTasks, 1000, null);
  }

  public  List chunkedBatch(String indexName, Iterable objects, Action action, boolean waitForTasks, int batchSize) {
    return chunkedBatch(indexName, objects, action, waitForTasks, batchSize, null);
  }

  public  List chunkedBatch(
    String indexName,
    Iterable objects,
    Action action,
    boolean waitForTasks,
    RequestOptions requestOptions
  ) {
    return chunkedBatch(indexName, objects, action, waitForTasks, 1000, requestOptions);
  }

  /**
   * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are
   * untouched. Replace all records in an index without any downtime. See
   * https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation
   * details.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
   *     to `length(objects) / batchSize`.
   * @throws AlgoliaRetryException When the retry has failed on all hosts
   * @throws AlgoliaApiException When the API sends an http error code
   * @throws AlgoliaRuntimeException When an error occurred during the serialization
   */
  public  ReplaceAllObjectsResponse replaceAllObjects(String indexName, Iterable objects, int batchSize) {
    return replaceAllObjects(indexName, objects, batchSize, null);
  }

  /**
   * Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used
   * under the hood, which creates a `batch` requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   */
  public  List saveObjects(String indexName, Iterable objects) {
    return saveObjects(indexName, objects, null);
  }

  /**
   * Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used
   * under the hood, which creates a `batch` requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  List saveObjects(String indexName, Iterable objects, RequestOptions requestOptions) {
    return saveObjects(indexName, objects, false, requestOptions);
  }

  /**
   * Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used
   * under the hood, which creates a `batch` requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  List saveObjects(String indexName, Iterable objects, boolean waitForTasks, RequestOptions requestOptions) {
    return saveObjects(indexName, objects, false, 1000, requestOptions);
  }

  /**
   * Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used
   * under the hood, which creates a `batch` requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
   *     to `length(objects) / batchSize`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  List saveObjects(
    String indexName,
    Iterable objects,
    boolean waitForTasks,
    int batchSize,
    RequestOptions requestOptions
  ) {
    return chunkedBatch(indexName, objects, Action.ADD_OBJECT, waitForTasks, batchSize, requestOptions);
  }

  /**
   * Helper: Deletes every records for the given objectIDs. The `chunkedBatch` helper is used under
   * the hood, which creates a `batch` requests with at most 1000 objectIDs in it.
   *
   * @param indexName The `indexName` to delete `objectIDs` from.
   * @param objectIDs The array of `objectIDs` to delete from the `indexName`.
   */
  public List deleteObjects(String indexName, List objectIDs) {
    return deleteObjects(indexName, objectIDs, false, null);
  }

  /**
   * Helper: Deletes every records for the given objectIDs. The `chunkedBatch` helper is used under
   * the hood, which creates a `batch` requests with at most 1000 objectIDs in it.
   *
   * @param indexName The `indexName` to delete `objectIDs` from.
   * @param objectIDs The array of `objectIDs` to delete from the `indexName`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public List deleteObjects(String indexName, List objectIDs, RequestOptions requestOptions) {
    return deleteObjects(indexName, objectIDs, false, null);
  }

  /**
   * Helper: Deletes every records for the given objectIDs. The `chunkedBatch` helper is used under
   * the hood, which creates a `batch` requests with at most 1000 objectIDs in it.
   *
   * @param indexName The `indexName` to delete `objectIDs` from.
   * @param objectIDs The array of `objectIDs` to delete from the `indexName`.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public List deleteObjects(String indexName, List objectIDs, boolean waitForTasks, RequestOptions requestOptions) {
    return deleteObjects(indexName, objectIDs, false, 1000, null);
  }

  /**
   * Helper: Deletes every records for the given objectIDs. The `chunkedBatch` helper is used under
   * the hood, which creates a `batch` requests with at most 1000 objectIDs in it.
   *
   * @param indexName The `indexName` to delete `objectIDs` from.
   * @param objectIDs The array of `objectIDs` to delete from the `indexName`.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
   *     to `length(objects) / batchSize`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public List deleteObjects(
    String indexName,
    List objectIDs,
    boolean waitForTasks,
    int batchSize,
    RequestOptions requestOptions
  ) {
    List> objects = new ArrayList<>();

    for (String id : objectIDs) {
      Map obj = new HashMap<>();
      obj.put("objectID", id);
      objects.add(obj);
    }

    return chunkedBatch(indexName, objects, Action.DELETE_OBJECT, waitForTasks, batchSize, requestOptions);
  }

  /**
   * Helper: Replaces object content of all the given objects according to their respective
   * `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch`
   * requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to update `objects` in.
   * @param objects The array of `objects` to update in the given Algolia `indexName`.
   * @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
   *     will fail.
   */
  public  List partialUpdateObjects(String indexName, Iterable objects, boolean createIfNotExists) {
    return partialUpdateObjects(indexName, objects, createIfNotExists, false, null);
  }

  /**
   * Helper: Replaces object content of all the given objects according to their respective
   * `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch`
   * requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to update `objects` in.
   * @param objects The array of `objects` to update in the given Algolia `indexName`.
   * @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
   *     will fail.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   */
  public  List partialUpdateObjects(
    String indexName,
    Iterable objects,
    boolean createIfNotExists,
    boolean waitForTasks
  ) {
    return partialUpdateObjects(indexName, objects, createIfNotExists, waitForTasks, null);
  }

  /**
   * Helper: Replaces object content of all the given objects according to their respective
   * `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch`
   * requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to update `objects` in.
   * @param objects The array of `objects` to update in the given Algolia `indexName`.
   * @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
   *     will fail.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  List partialUpdateObjects(
    String indexName,
    Iterable objects,
    boolean createIfNotExists,
    boolean waitForTasks,
    RequestOptions requestOptions
  ) {
    return partialUpdateObjects(indexName, objects, createIfNotExists, waitForTasks, 1000, null);
  }

  /**
   * Helper: Replaces object content of all the given objects according to their respective
   * `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch`
   * requests with at most 1000 objects in it.
   *
   * @param indexName The `indexName` to update `objects` in.
   * @param objects The array of `objects` to update in the given Algolia `indexName`.
   * @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
   *     will fail.
   * @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
   *     processed, this operation may slow the total execution time of this method but is more
   *     reliable.
   * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
   *     to `length(objects) / batchSize`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   */
  public  List partialUpdateObjects(
    String indexName,
    Iterable objects,
    boolean createIfNotExists,
    boolean waitForTasks,
    int batchSize,
    RequestOptions requestOptions
  ) {
    return chunkedBatch(
      indexName,
      objects,
      createIfNotExists ? Action.PARTIAL_UPDATE_OBJECT : Action.PARTIAL_UPDATE_OBJECT_NO_CREATE,
      waitForTasks,
      batchSize,
      requestOptions
    );
  }

  /**
   * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are
   * untouched. Replace all records in an index without any downtime. See
   * https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation
   * details.
   *
   * @param indexName The `indexName` to replace `objects` in.
   * @param objects The array of `objects` to store in the given Algolia `indexName`.
   * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
   *     to `length(objects) / batchSize`.
   * @param requestOptions The requestOptions to send along with the query, they will be merged with
   *     the transporter requestOptions. (optional)
   * @throws AlgoliaRetryException When the retry has failed on all hosts
   * @throws AlgoliaApiException When the API sends an http error code
   * @throws AlgoliaRuntimeException When an error occurred during the serialization
   */
  public  ReplaceAllObjectsResponse replaceAllObjects(
    String indexName,
    Iterable objects,
    int batchSize,
    RequestOptions requestOptions
  ) {
    Random rnd = new Random();
    String tmpIndexName = indexName + "_tmp_" + rnd.nextInt(100);

    // Copy settings, synonyms and rules
    UpdatedAtResponse copyOperationResponse = operationIndex(
      indexName,
      new OperationIndexParams()
        .setOperation(OperationType.COPY)
        .setDestination(tmpIndexName)
        .addScope(ScopeType.SETTINGS)
        .addScope(ScopeType.RULES)
        .addScope(ScopeType.SYNONYMS),
      requestOptions
    );

    // Save new objects
    List batchResponses = chunkedBatch(tmpIndexName, objects, Action.ADD_OBJECT, true, batchSize, requestOptions);

    waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions);

    copyOperationResponse = operationIndex(
      indexName,
      new OperationIndexParams()
        .setOperation(OperationType.COPY)
        .setDestination(tmpIndexName)
        .addScope(ScopeType.SETTINGS)
        .addScope(ScopeType.RULES)
        .addScope(ScopeType.SYNONYMS),
      requestOptions
    );
    waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions);

    // Move temporary index to source index
    UpdatedAtResponse moveOperationResponse = operationIndex(
      tmpIndexName,
      new OperationIndexParams().setOperation(OperationType.MOVE).setDestination(indexName),
      requestOptions
    );
    waitForTask(tmpIndexName, moveOperationResponse.getTaskID(), requestOptions);

    return new ReplaceAllObjectsResponse()
      .setCopyOperationResponse(copyOperationResponse)
      .setBatchResponses(batchResponses)
      .setMoveOperationResponse(moveOperationResponse);
  }

  /**
   * Helper: Generates a secured API key based on the given `parent_api_key` and given
   * `restrictions`.
   *
   * @param parentApiKey API key to generate from.
   * @param restrictions Restrictions to add the key
   * @throws Exception if an error occurs during the encoding
   * @throws AlgoliaRetryException When the retry has failed on all hosts
   * @throws AlgoliaApiException When the API sends an http error code
   * @throws AlgoliaRuntimeException When an error occurred during the serialization
   */
  public String generateSecuredApiKey(@Nonnull String parentApiKey, @Nonnull SecuredApiKeyRestrictions restrictions) throws Exception {
    Map restrictionsMap = new HashMap<>();
    if (restrictions.getFilters() != null) restrictionsMap.put("filters", StringUtils.paramToString(restrictions.getFilters()));
    if (restrictions.getValidUntil() != 0) restrictionsMap.put("validUntil", StringUtils.paramToString(restrictions.getValidUntil()));
    if (restrictions.getRestrictIndices() != null) restrictionsMap.put(
      "restrictIndices",
      StringUtils.paramToString(restrictions.getRestrictIndices())
    );
    if (restrictions.getRestrictSources() != null) restrictionsMap.put(
      "restrictSources",
      StringUtils.paramToString(restrictions.getRestrictSources())
    );
    if (restrictions.getUserToken() != null) restrictionsMap.put("userToken", StringUtils.paramToString(restrictions.getUserToken()));

    if (restrictions.getSearchParams() != null) {
      Map searchParamsMap = JsonSerializer.getObjectMapper()
        .convertValue(restrictions.getSearchParams(), new TypeReference>() {});
      searchParamsMap.forEach((key, value) -> restrictionsMap.put(key, StringUtils.paramToString(value)));
    }

    String queryStr = restrictionsMap
      .entrySet()
      .stream()
      .sorted(Map.Entry.comparingByKey())
      .map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue()))
      .collect(Collectors.joining("&"));

    String key = hmac(parentApiKey, queryStr);

    return new String(Base64.getEncoder().encode(String.format("%s%s", key, queryStr).getBytes(Charset.forName("UTF8"))));
  }

  private String hmac(String key, String msg) throws NoSuchAlgorithmException, InvalidKeyException {
    Mac hmac = Mac.getInstance("HmacSHA256");
    hmac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
    byte[] rawHmac = hmac.doFinal(msg.getBytes());
    StringBuilder sb = new StringBuilder(rawHmac.length * 2);
    for (byte b : rawHmac) {
      sb.append(String.format("%02x", b & 0xff));
    }
    return sb.toString();
  }

  /**
   * Helper: Retrieves the remaining validity of the previous generated `secured_api_key`, the
   * `validUntil` parameter must have been provided.
   *
   * @param securedApiKey The secured API Key to check
   * @throws AlgoliaRuntimeException if securedApiKey is null, empty or whitespaces.
   * @throws AlgoliaRuntimeException if securedApiKey doesn't have a validUntil
   *      parameter.
   */
  public Duration getSecuredApiKeyRemainingValidity(@Nonnull String securedApiKey) {
    if (securedApiKey == null || securedApiKey.trim().isEmpty()) {
      throw new AlgoliaRuntimeException("securedAPIKey must not be empty, null or whitespaces");
    }

    byte[] decodedBytes = Base64.getDecoder().decode(securedApiKey);
    String decodedString = new String(decodedBytes);

    Pattern pattern = Pattern.compile("validUntil=\\d+");
    Matcher matcher = pattern.matcher(decodedString);

    if (!matcher.find()) {
      throw new AlgoliaRuntimeException("The Secured API Key doesn't have a validUntil parameter.");
    }

    String validUntilMatch = matcher.group(0);
    long timeStamp = Long.parseLong(validUntilMatch.replace("validUntil=", ""));

    return Duration.ofSeconds(timeStamp - Instant.now().getEpochSecond());
  }

  public boolean indexExists(String indexName) {
    try {
      getSettings(indexName);
    } catch (AlgoliaApiException e) {
      if (e.getStatusCode() == 404) {
        return false;
      }
      throw e;
    }
    return true;
  }
}