aditjind_crate/lib.rs
1/*
2 * Licensed to Elasticsearch B.V. under one or more contributor
3 * license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright
5 * ownership. Elasticsearch B.V. licenses this file to you under
6 * the Apache License, Version 2.0 (the "License"); you may
7 * not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19//! Official Rust client for [OpenSearch](https://opensearch.org/)
20//!
21//! `OpenSearch` is an official Rust client for OpenSearch, providing an efficient asynchronous
22//! client for all stable OpenSearch APIs that's easy to use.
23//!
24//! # Versions and Compatibility
25//!
26//! | Rust client | OpenSearch |
27//! |-------------|---------------|
28//! | 1.x | 1.x |
29//!
30//! A major version of the client is compatible with the same major version of OpenSearch.
31//! Since OpenSearch is developed following [Semantic Versioning](https://semver.org/) principles,
32//! Any minor/patch version of the client can be used against any minor/patch version of OpenSearch
33//! **within the same major version lineage**. For example,
34//!
35//! - A `1.5.0` client can be used against `1.0.0` OpenSearch
36//! - A `1.4.0` client can be used against `1.5.1` OpenSearch
37//!
38//! In the former case, a 1.5.0 client may contain additional API functions that are not available
39//! in 1.0.0 OpenSearch. In this case, these APIs cannot be used, but for any APIs available in
40//! OpenSearch, the respective API functions on the client will be compatible.
41//!
42//! In the latter case, a 1.4.0 client won't contain API functions for APIs that are introduced in
43//! OpenSearch 1.5.0+, but for all other APIs available in OpenSearch, the respective API
44//! functions on the client will be compatible.
45//!
46//! **No compatibility assurances are given between different major versions of the client and
47//! OpenSearch**. Major differences likely exist between major versions of OpenSearch, particularly
48//! around request and response object formats, but also around API urls and behaviour.
49//!
50//! # Features
51//!
52//! The following are a list of Cargo features that can be enabled or disabled:
53//!
54//! - **native-tls** *(enabled by default)*: Enables TLS functionality provided by `native-tls`.
55//! - **rustls-tls**: Enables TLS functionality provided by `rustls`.
56//! - **beta-apis**: Enables beta APIs. Beta APIs are on track to become stable and permanent features. Use them with
57//! caution because it is possible that breaking changes are made to these APIs in a minor version.
58//! - **experimental-apis**: Enables experimental APIs. Experimental APIs are just that - an experiment. An experimental
59//! API might have breaking changes in any future version, or it might even be removed entirely. This feature also
60//! enables `beta-apis`.
61//!
62//! # Getting started
63//!
64//! Add the `opensearch` crate and version to Cargo.toml. Choose the version that is compatible with
65//! the version of OpenSearch you're using
66//!
67//! ```toml,no_run
68//! [dependencies]
69//! opensearch = "1.0.0"
70//! ```
71//! The following _optional_ dependencies may also be useful to create requests and read responses
72//!
73//! ```toml,no_run
74//! serde = "~1"
75//! serde_json = "~1"
76//! ```
77//!
78//! ### Async support with tokio
79//!
80//! The client uses [`reqwest`](https://crates.io/crates/reqwest) to make HTTP calls, which internally uses
81//! the [`tokio`](https://crates.io/crates/tokio) runtime for async support. As such, you may require
82//! to take a dependency on `tokio` in order to use the client. For example, in Cargo.toml, you may
83//! need the following dependency
84//!
85//! ```toml,no_run
86//! tokio = { version = "*", features = ["full"] }
87//! ```
88//!
89//! and to attribute async main function with `#[tokio::main]`
90//!
91//! ```rust,no_run
92//! #[tokio::main]
93//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
94//! // your code ...
95//! Ok(())
96//! }
97//! ```
98//!
99//! and attribute test functions with `#[tokio::test]`
100//!
101//! ```rust,no_run
102//! #[tokio::test]
103//! async fn my_test() -> Result<(), Box<dyn std::error::Error>> {
104//! // your code ...
105//! Ok(())
106//! }
107//! ```
108//!
109//! ## Create a client
110//!
111//! To create a client to make API calls to OpenSearch running on `http://localhost:9200`
112//!
113//! ```rust,no_run
114//! # use opensearch::OpenSearch;
115//! let client = OpenSearch::default();
116//! ```
117//!
118//! Alternatively, you can create a client to make API calls against OpenSearch running on a
119//! specific [url::Url]
120//!
121//! ```rust,no_run
122//! # use opensearch::{
123//! # Error, OpenSearch,
124//! # http::transport::{Transport, SingleNodeConnectionPool}
125//! # };
126//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
127//! let transport = Transport::single_node("https://example.com")?;
128//! let client = OpenSearch::new(transport);
129//! # Ok(())
130//! # }
131//! ```
132//!
133//! More control over how a [Transport](http::transport::Transport) is built can be
134//! achieved using [TransportBuilder](http::transport::TransportBuilder) to build a transport, and
135//! passing it to [OpenSearch::new] create a new instance of [OpenSearch]
136//!
137//! ```rust,no_run
138//! # use opensearch::{
139//! # auth::Credentials,
140//! # Error, OpenSearch,
141//! # http::transport::{TransportBuilder,SingleNodeConnectionPool},
142//! # };
143//! # use url::Url;
144//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
145//! let url = Url::parse("https://example.com")?;
146//! let conn_pool = SingleNodeConnectionPool::new(url);
147//! let transport = TransportBuilder::new(conn_pool).disable_proxy().build()?;
148//! let client = OpenSearch::new(transport);
149//! # Ok(())
150//! # }
151//! ```
152//!
153//! ## Making API calls
154//!
155//! The client exposes all stable OpenSearch APIs, either on the root [OpenSearch] client,
156//! or on a _namespace_ client that groups related APIs, such as [Cat](cat::Cat), which groups the
157//! Cat related APIs. All API functions are `async` and can be `await`ed.
158//!
159//! The following makes an API call to the cat indices API
160//!
161//! ```rust,no_run
162//! # use opensearch::{auth::Credentials, OpenSearch, Error, cat::CatIndicesParts};
163//! # use url::Url;
164//! # use serde_json::{json, Value};
165//! # #[tokio::main]
166//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
167//! # let client = OpenSearch::default();
168//! let response = client
169//! .cat()
170//! .indices(CatIndicesParts::Index(&["*"]))
171//! .send()
172//! .await?;
173//!
174//! let response_body = response.json::<Value>().await?;
175//! for record in response_body.as_array().unwrap() {
176//! // print the name of each index
177//! println!("{}", record["index"].as_str().unwrap());
178//! }
179//! # Ok(())
180//! # }
181//! ```
182//! For APIs that contain parts of the Url path to be provided by the consumer, the Url path
183//! variants are modelled as an `enum`, such as [CatIndicesParts](cat::CatIndicesParts) in the above example, which models
184//! the variants of the [CatIndices](cat::CatIndices) API.
185//!
186//! ### Indexing
187//!
188//! Indexing a single document can be achieved with the index API
189//!
190//! ```rust,no_run
191//! # use opensearch::{auth::Credentials, OpenSearch, Error, SearchParts, IndexParts};
192//! # use url::Url;
193//! # use serde_json::{json, Value};
194//! # #[tokio::main]
195//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
196//! # let client = OpenSearch::default();
197//! let response = client
198//! .index(IndexParts::IndexId("tweets", "1"))
199//! .body(json!({
200//! "id": 1,
201//! "user": "kimchy",
202//! "post_date": "2009-11-15T00:00:00Z",
203//! "message": "Trying out OpenSearch, so far so good?"
204//! }))
205//! .send()
206//! .await?;
207//!
208//! let successful = response.status_code().is_success();
209//! # Ok(())
210//! # }
211//! ```
212//!
213//! For indexing multiple documents, the bulk API is a better option, allowing multiple operations
214//! to be sent in one API call
215//!
216//! ```rust,no_run
217//! # use opensearch::{auth::Credentials, OpenSearch, Error, IndexParts, BulkParts, http::request::JsonBody};
218//! # use url::Url;
219//! # use serde_json::{json, Value};
220//! # #[tokio::main]
221//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
222//! # let client = OpenSearch::default();
223//! let mut body: Vec<JsonBody<_>> = Vec::with_capacity(4);
224//!
225//! // add the first operation and document
226//! body.push(json!({"index": {"_id": "1"}}).into());
227//! body.push(json!({
228//! "id": 1,
229//! "user": "kimchy",
230//! "post_date": "2009-11-15T00:00:00Z",
231//! "message": "Trying out OpenSearch, so far so good?"
232//! }).into());
233//!
234//! // add the second operation and document
235//! body.push(json!({"index": {"_id": "2"}}).into());
236//! body.push(json!({
237//! "id": 2,
238//! "user": "forloop",
239//! "post_date": "2020-01-08T00:00:00Z",
240//! "message": "Bulk indexing with the rust client, yeah!"
241//! }).into());
242//!
243//! let response = client
244//! .bulk(BulkParts::Index("tweets"))
245//! .body(body)
246//! .send()
247//! .await?;
248//!
249//! let response_body = response.json::<Value>().await?;
250//! let successful = response_body["errors"].as_bool().unwrap() == false;
251//! # Ok(())
252//! # }
253//! ```
254//! ### Searching
255//!
256//! The following makes an API call to `tweets/_search` with the json body
257//! `{"query":{"match":{"message":"OpenSearch"}}}`
258//!
259//! ```rust,no_run
260//! # use opensearch::{auth::Credentials, OpenSearch, Error, SearchParts};
261//! # use url::Url;
262//! # use serde_json::{json, Value};
263//! # #[tokio::main]
264//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
265//! # let client = OpenSearch::default();
266//! let response = client
267//! .search(SearchParts::Index(&["tweets"]))
268//! .from(0)
269//! .size(10)
270//! .body(json!({
271//! "query": {
272//! "match": {
273//! "message": "OpenSearch rust"
274//! }
275//! }
276//! }))
277//! .send()
278//! .await?;
279//!
280//! let response_body = response.json::<Value>().await?;
281//! let took = response_body["took"].as_i64().unwrap();
282//! for hit in response_body["hits"]["hits"].as_array().unwrap() {
283//! // print the source document
284//! println!("{:?}", hit["_source"]);
285//! }
286//! # Ok(())
287//! # }
288//! ```
289//!
290//! ## Request bodies
291//!
292//! For APIs that expect JSON, the `body` associated function of the API constrains the input
293//! to a type that implements [serde::Serialize] trait. An example of this was the indexing a single
294//! document example above.
295//!
296//! Some APIs expect newline delimited JSON
297//! (NDJSON) however, so the `body` associated for these APIs constrain the input to a vector of
298//! types that implement [Body](http::request::Body) trait. An example of this was the bulk indexing multiple documents
299//! above.
300//!
301//! The [Body](http::request::Body) trait represents the body of an API call, allowing for different body implementations.
302//! As well as those to represent JSON and NDJSON, a few other types also have implementations for
303//! [Body](http::request::Body), such as byte slice. Whilst these can't be passed to the API functions directly,
304//! [OpenSearch::send] can be used
305//!
306//! ```rust,no_run
307//! # use opensearch::{auth::Credentials, http::{Method,headers::HeaderMap}, OpenSearch, Error, SearchParts};
308//! # use url::Url;
309//! # use serde_json::{json, Value};
310//! # #[tokio::main]
311//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
312//! # let client = OpenSearch::default();
313//! let body = b"{\"query\":{\"match_all\":{}}}";
314//!
315//! let response = client
316//! .send(Method::Post,
317//! SearchParts::Index(&["tweets"]).url().as_ref(),
318//! HeaderMap::new(),
319//! Option::<&Value>::None,
320//! Some(body.as_ref()),
321//! None,
322//! )
323//! .await?;
324//!
325//! # Ok(())
326//! # }
327//! ```
328
329#![doc(
330 html_logo_url = "https://github.com/opensearch-project/opensearch-rs/raw/main/OpenSearch.svg"
331)]
332// TODO: turn on before releasing :) Will require adding documentation within all REST API specs
333// #![deny(missing_docs)]
334
335// also test examples in README
336// source: https://github.com/rust-lang/cargo/issues/383#issuecomment-720873790
337#[cfg(doctest)]
338mod readme {
339 macro_rules! external_doc_test {
340 ($x:expr) => {
341 #[doc = $x]
342 extern "C" {}
343 };
344 }
345
346 external_doc_test!(include_str!("../../README.md"));
347}
348
349#[macro_use]
350extern crate dyn_clone;
351
352pub mod auth;
353pub mod cert;
354pub mod http;
355pub mod params;
356
357// GENERATED-BEGIN:namespace-modules
358// Generated code - do not edit until the next GENERATED-END marker
359
360pub mod cat;
361pub mod cluster;
362pub mod dangling_indices;
363pub mod indices;
364pub mod ingest;
365pub mod nodes;
366pub mod snapshot;
367pub mod tasks;
368pub mod text_structure;
369// GENERATED-END
370
371mod client;
372mod error;
373mod root;
374
375// exposes types within modules at the library root level
376pub use crate::{client::*, error::*, http::transport::DEFAULT_ADDRESS, root::*};
377use serde::{
378 de,
379 de::{MapAccess, Visitor},
380 Deserialize, Deserializer,
381};
382use std::{fmt, marker::PhantomData, str::FromStr};
383use void::Void;
384
385/// Deserializes a string or a map into a struct `T` that implements `FromStr`
386pub(crate) fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
387where
388 T: Deserialize<'de> + FromStr<Err = Void>,
389 D: Deserializer<'de>,
390{
391 // This is a Visitor that forwards string types to T's `FromStr` impl and
392 // forwards map types to T's `Deserialize` impl. The `PhantomData` is to
393 // keep the compiler from complaining about T being an unused generic type
394 // parameter. We need T in order to know the Value type for the Visitor
395 // impl.
396 struct StringOrStruct<T>(PhantomData<fn() -> T>);
397
398 impl<'de, T> Visitor<'de> for StringOrStruct<T>
399 where
400 T: Deserialize<'de> + FromStr<Err = Void>,
401 {
402 type Value = T;
403
404 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
405 formatter.write_str("string or map")
406 }
407
408 fn visit_str<E>(self, value: &str) -> Result<T, E>
409 where
410 E: de::Error,
411 {
412 Ok(FromStr::from_str(value).unwrap())
413 }
414
415 fn visit_map<M>(self, map: M) -> Result<T, M::Error>
416 where
417 M: MapAccess<'de>,
418 {
419 Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
420 }
421 }
422
423 deserializer.deserialize_any(StringOrStruct(PhantomData))
424}
425
426/// Deserializes a string or a sequence of strings into a Vec<String>
427pub(crate) fn string_or_seq_string<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
428where
429 D: Deserializer<'de>,
430{
431 struct StringOrVec;
432
433 impl<'de> de::Visitor<'de> for StringOrVec {
434 type Value = Vec<String>;
435
436 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
437 formatter.write_str("string or seq of strings")
438 }
439
440 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
441 where
442 E: de::Error,
443 {
444 Ok(vec![value.to_string()])
445 }
446
447 fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
448 where
449 S: de::SeqAccess<'de>,
450 {
451 Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))
452 }
453 }
454
455 deserializer.deserialize_any(StringOrVec)
456}
457
458#[cfg(test)]
459pub mod tests {
460 use crate::SearchParts;
461
462 #[test]
463 fn build_search_on_all_indices_and_types() {
464 let parts = SearchParts::None;
465 let url = parts.url();
466 assert_eq!(url, "/_search");
467 }
468
469 #[test]
470 fn build_search_on_selected_indices() {
471 let parts = SearchParts::Index(&["index-1", "index-2"]);
472 let url = parts.url();
473 assert_eq!(url, "/index-1,index-2/_search");
474 }
475
476 #[test]
477 fn percent_encode_characters() {
478 let parts = SearchParts::Index(&[" !\"#$%&'\\()*+,-./:;<=>?@[\\]^_`{|}~"]);
479 let url = parts.url();
480 assert_eq!(url, "/%20%21%22%23%24%25%26%27%5C%28%29*%2B,-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D%7E/_search");
481 }
482
483 #[test]
484 fn build_search_on_selected_indices_and_types() {
485 let parts = SearchParts::IndexType(&["index-1", "index-2"], &["type-1", "type-2"]);
486 let url = parts.url();
487 assert_eq!(url, "/index-1,index-2/type-1,type-2/_search");
488 }
489}