The OpenSearch Rust client lets you connect your Rust application with the data in your OpenSearch cluster. For the client's complete API documentation and additional examples, see the [OpenSearch docs.rs documentation](https://docs.rs/opensearch/).
This getting started guide illustrates how to connect to OpenSearch, index documents, and run queries. For the client source code, see the [opensearch-rs repo](https://github.com/opensearch-project/opensearch-rs).
## Setup
If you're starting a new project, add the `opensearch` crate to Cargo.toml:
```rust
[dependencies]
opensearch = "1.0.0"
```
{% include copy.html %}
Additionally, you may want to add the following `serde` dependencies that help serialize types to JSON and deserialize JSON responses:
```rust
serde = "~1"
serde_json = "~1"
```
{% include copy.html %}
The Rust client uses the higher-level [`reqwest`](https://crates.io/crates/reqwest) HTTP client library for HTTP requests, and reqwest uses the [`tokio`](https://crates.io/crates/tokio) platform to support asynchronous requests. If you are planning to use asynchronous functions, you need to add the `tokio` dependency to Cargo.toml:
```rust
tokio = { version = "*", features = ["full"] }
```
{% include copy.html %}
See the [Sample program](#sample-program) section for the complete Cargo.toml file.
To use the Rust client API, import the modules, structs, and enums you need:
```rust
use opensearch::OpenSearch;
```
{% include copy.html %}
## Connecting to OpenSearch
To connect to the default OpenSearch host, create a default client object that connects to OpenSearch at the address `http://localhost:9200`:
```rust
let client = OpenSearch::default();
```
{% include copy.html %}
To connect to an OpenSearch host that is running at a different address, create a client with the specified address:
```rust
let transport = Transport::single_node("http://localhost:9200")?;
let client = OpenSearch::new(transport);
```
{% include copy.html %}
Alternatively, you can customize the URL and use a connection pool by creating a `TransportBuilder` struct and passing it to `OpenSearch::new` to create a new instance of the client:
```rust
let url = Url::parse("http://localhost:9200")?;
let conn_pool = SingleNodeConnectionPool::new(url);
let transport = TransportBuilder::new(conn_pool).disable_proxy().build()?;
let client = OpenSearch::new(transport);
```
{% include copy.html %}
## Creating an index
To create an OpenSearch index, use the `create` function of the `opensearch::indices::Indices` struct. You can use the following code to construct a JSON object with custom mappings:
```rust
let response = client
.indices()
.create(IndicesCreateParts::Index("movies"))
.body(json!({
"mappings" : {
"properties" : {
"title" : { "type" : "text" }
}
}
}))
.send()
.await?;
```
{% include copy.html %}
## Indexing a document
You can index a document into OpenSearch using the client's `index` function:
```rust
let response = client
.index(IndexParts::IndexId("movies", "1"))
.body(json!({
"id": 1,
"title": "Moneyball",
"director": "Bennett Miller",
"year": "2011"
}))
.send()
.await?;
```
{% include copy.html %}
## Performing bulk operations
You can perform several operations at the same time by using the client's `bulk` function. First, create the JSON body of a Bulk API call, and then pass it to the `bulk` function:
```rust
let mut body: Vec<JsonBody<_>> = Vec::with_capacity(4);
// add the first operation and document
body.push(json!({"index": {"_id": "2"}}).into());
body.push(json!({
"id": 2,
"title": "Interstellar",
"director": "Christopher Nolan",
"year": "2014"
}).into());
// add the second operation and document
body.push(json!({"index": {"_id": "3"}}).into());
body.push(json!({
"id": 3,
"title": "Star Trek Beyond",
"director": "Justin Lin",
"year": "2015"
}).into());
let response = client
.bulk(BulkParts::Index("movies"))
.body(body)
.send()
.await?;
```
{% include copy.html %}
## Searching for documents
The easiest way to search for documents is to construct a query string. The following code uses a `multi_match` query to search for "miller" in the title and director fields. It boosts the documents where "miller" appears in the title field:
```rust
response = client
.search(SearchParts::Index(&["movies"]))
.from(0)
.size(10)
.body(json!({
"query": {
"multi_match": {
"query": "miller",
"fields": ["title^2", "director"]
}
}
}))
.send()
.await?;
```
{% include copy.html %}
You can then read the response body as JSON and iterate over the `hits` array to read all the `_source` documents:
```rust
let response_body = response.json::<Value>().await?;
for hit in response_body["hits"]["hits"].as_array().unwrap() {
You can delete a document using the client's `delete` function:
```rust
let response = client
.delete(DeleteParts::IndexId("movies", "2"))
.send()
.await?;
```
{% include copy.html %}
## Deleting an index
You can delete an index using the `delete` function of the `opensearch::indices::Indices` struct:
```rust
let response = client
.indices()
.delete(IndicesDeleteParts::Index(&["movies"]))
.send()
.await?;
```
{% include copy.html %}
## Sample program
The sample program uses the following Cargo.toml file with all dependencies described in the [Setup](#setup) section:
```rust
[package]
name = "os_rust_project"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
opensearch = "1.0.0"
tokio = { version = "*", features = ["full"] }
serde = "~1"
serde_json = "~1"
```
{% include copy.html %}
The following sample program creates a client, adds an index with non-default mappings, inserts a document, performs bulk operations, searches for the document, deletes the document, and then deletes the index:
```rust
use opensearch::{DeleteParts, OpenSearch, IndexParts, http::request::JsonBody, BulkParts, SearchParts};
use opensearch::{indices::{IndicesDeleteParts, IndicesCreateParts}};