Skip to main content

rustlavel_search/
lib.rs

1//! rustlavel-search: Elasticsearch and OpenSearch.
2//!
3//! **One package serves both.** OpenSearch was forked from Elasticsearch 7.10
4//! and the two have not diverged on anything this crate touches: `_search`,
5//! `_bulk`, `_doc`, `_mapping`, `_refresh` and `_cluster/health` take the same
6//! bodies and answer with the same shapes on either. Nothing here prefers one.
7//! [`ClusterInfo::distribution`] says which one answered, for the rare caller
8//! that has to branch on it.
9//!
10//! There is no protocol to write here — the cluster speaks HTTP and JSON, and
11//! the framework already has both. What this crate is actually for is the
12//! things that go wrong:
13//!
14//! - **`_bulk` answers 200 when documents were lost.** Per-item failures live
15//!   in the body, and a caller that checks the status writes a loader that
16//!   silently drops rows. See [`BulkReport`].
17//! - **The total on a search is a floor, not a count.** Counting stops at
18//!   10,000 by default. See [`TotalHits`].
19//! - **A search can succeed on some shards and fail on others**, answering 200
20//!   with fewer results and no other sign. See [`SearchResults::is_partial`].
21//! - **A document is not searchable the moment it is written.** See
22//!   [`SearchClient::refresh`].
23//! - **Failures need telling apart.** A rejected write is worth repeating; a
24//!   document that does not fit the mapping never is. See [`SearchError`].
25//!
26//! ```ignore
27//! let client = SearchClient::new("http://localhost:9200").basic_auth("elastic", &password);
28//!
29//! client
30//!     .create_index_if_missing(
31//!         "posts",
32//!         &IndexDefinition::new()
33//!             .field("title", Field::text().with_keyword())
34//!             .field("tags", Field::keyword())
35//!             .field("views", Field::long())
36//!             .dynamic_strict(),
37//!     )
38//!     .await?;
39//!
40//! client.bulk("posts", &operations).await?.ok_or_error()?;
41//! client.refresh("posts").await?;
42//!
43//! let results = client
44//!     .search(
45//!         "posts",
46//!         &Search::new()
47//!             .query(
48//!                 Query::bool()
49//!                     .must(Query::matching("title", "rust").and())
50//!                     .filter(Query::range("views").gte(100)),
51//!             )
52//!             .aggregate("by_tag", Aggregation::terms("tags").size(10))
53//!             .track_total_hits(true),
54//!     )
55//!     .await?;
56//!
57//! println!("{} matches", results.total);
58//! ```
59//!
60//! Tests need no cluster: [`SearchClient::faking`] is `Http::fake()`, and the
61//! integration tests under `tests/` run against a real server only when
62//! `ELASTICSEARCH_URL` is set.
63
64pub mod client;
65pub mod document;
66pub mod error;
67pub mod index;
68pub mod logs;
69pub mod query;
70pub mod response;
71
72pub use client::{ClusterHealth, ClusterInfo, HealthStatus, SearchClient};
73pub use document::{BulkItem, BulkOperation, BulkReport, Document, IndexedDocument, WriteResult};
74pub use error::{Result, SearchError};
75pub use index::{Field, IndexDefinition};
76pub use logs::{LogSettings, LogShipper, LogShipping, LogStats};
77pub use query::{Aggregation, BoolQuery, Match, Order, Query, Range, Search};
78pub use response::{
79    Aggregations, Bucket, Hit, SearchResults, Shards, StatsResult, TermsResult, TotalHits,
80    TotalRelation,
81};
82
83/// Everything an application normally needs, in one import.
84pub mod prelude {
85    pub use crate::client::SearchClient;
86    pub use crate::document::{BulkOperation, BulkReport, Document};
87    pub use crate::error::SearchError;
88    pub use crate::index::{Field, IndexDefinition};
89    pub use crate::query::{Aggregation, Order, Query, Search};
90    pub use crate::response::{Hit, SearchResults, TotalHits};
91}