Skip to main content

flusso_query/
lib.rs

1//! `flusso-query` — a typed query client for a flusso-maintained search index.
2//!
3//! Targets OpenSearch and Elasticsearch 7.x, which share the `_search` query DSL
4//! this crate emits; any future backend divergence is handled on the [`Client`],
5//! not by separate crates.
6//!
7//! This is the **runtime** layer described in [`CLIENT.md`](../../../CLIENT.md):
8//! the transport, the field-handle / [`Query`] / [`Search`] builder, and the
9//! typed [`SearchResponse`]. It is generic over the caller's document type `T`.
10//!
11//! # What this first cut covers
12//!
13//! - [`Client`] transport over OpenSearch (`connect`, `basic_auth`, search,
14//!   msearch, get).
15//! - Field handles ([`Keyword`], [`Text`], [`Bool`], [`Number`], [`Date`],
16//!   [`Nested`], [`Binary`], [`Json`]) with operators that build a [`Query`].
17//! - [`Query`] composition (`and` / `or` / `not`) and the [`Search`] bool-clause
18//!   builder (`query` / `filter` / `must_not` / `should`, plus `sort` / `from` /
19//!   `size` / `raw`). A [`Search`] is a plain client-free value — build it
20//!   anywhere, store and reuse it; a [`Client`] appears only at the terminals:
21//!   [`Search::send`] (a typed page), [`Search::ids`] (a page of bare document
22//!   ids, `_source: false`), or [`Search::count`] (just the number of matches,
23//!   via `_count`).
24//! - Several searches in one round-trip: [`Client::msearch`] (a tuple of
25//!   `&Search<T>`, mixed document types, one typed response per slot) and
26//!   [`Client::msearch_all`] (a slice of one type).
27//! - Combined (blended) search: [`FlussoMultiDocument`] — a caller-owned enum
28//!   with one variant per document type — and its [`MultiSearch`] builder. One
29//!   query across all the union's indexes, one relevance-ranked result list,
30//!   each hit decoded into the variant matching its physical `_index`.
31//! - Typed [`SearchResponse`] / [`Hit`].
32//!
33//! Also covered: optional filters (`Option<Q>` is a [`Query`]); object/to-one-join
34//! handles ([`Object`]); shaping returned nested arrays ([`Search::filter_nested`]
35//! with [`Nested::matching`], via `inner_hits`); and scope-tagged queries —
36//! [`Query`]`<S>` carries the scope `S` it was built in ([`Root`] for the document
37//! root and flattened objects, the element type for a `nested` array), so a nested
38//! query must be lifted through [`Nested::any`]/[`Nested::all`] before it can join a
39//! root query; the compiler enforces it.
40//!
41//! # Not yet built (see CLIENT.md for the endgame)
42//!
43//! - The `#[derive(FlussoDocument)]` and `#[derive(FlussoMultiDocument)]`
44//!   proc-macros live in `flusso-query-derive` (the `derive` feature). Without
45//!   them, document structs + handles and union impls are written by hand —
46//!   exactly the calls this crate exposes (see the integration tests).
47//! - `filter_nested`'s `keep_source()` opt-out (it always replaces the array in
48//!   `source` today) and a typed `hit.nested(handle)` accessor.
49//!
50//! # Example (hand-written until the derive lands)
51//!
52//! ```no_run
53//! use flusso_query::{Client, Keyword, Number, Nested, kind};
54//!
55//! #[derive(serde::Deserialize)]
56//! struct User {
57//!     email: String,
58//!     #[serde(rename = "orderCount")]
59//!     order_count: i64,
60//! }
61//!
62//! impl User {
63//!     fn email() -> Keyword { Keyword::at("email") }
64//!     fn order_count() -> Number<kind::Long> { Number::at("orderCount") }
65//!     fn query() -> flusso_query::Search<User> {
66//!         flusso_query::Search::new("users", "xxxxxx")
67//!     }
68//! }
69//!
70//! # async fn run() -> flusso_query::Result<()> {
71//! // A query is a plain value — no client involved while building it.
72//! let busy = User::query()
73//!     .filter(User::email().eq("ada@example.com"))
74//!     .filter(User::order_count().gte(5))
75//!     .size(20);
76//!
77//! // The client appears once, when it's time to run.
78//! let client = Client::connect("https://localhost:9200")?;
79//! let page = busy.send(&client).await?;
80//! println!("{} matches", page.total);
81//! # Ok(())
82//! # }
83//! ```
84
85mod client;
86mod error;
87mod handles;
88mod msearch;
89mod multi;
90mod path;
91mod query;
92mod search;
93
94#[cfg(test)]
95mod tests;
96
97pub use client::Client;
98pub use error::{Error, Result};
99pub use handles::{
100    Binary, Bool, BoostMode, BoostingQuery, CombinedFieldsQuery, ConstantScoreQuery, Date, DateMap,
101    DisMaxQuery, Distance, DistanceFeatureQuery, DistanceType, DistanceUnit, EqQuery, FlussoMap,
102    FlussoValue, FunctionScoreQuery, Fuzziness, FuzzyQuery, Geo, GeoDistanceQuery, GeoPoint,
103    IdsQuery, Json, Keyword, KeywordMap, MapSearch, MatchQuery, MaybeOrderBy, MinimumShouldMatch,
104    Missing, MoreLikeThisQuery, MultiMatchQuery, MultiMatchType, Nested, NestedProjection,
105    NestedQuery, NestedScoreMode, NoSubfields, Number, NumberMap, NumericType, Object, Operator,
106    OrderBy, PrefixQuery, QueryStringQuery, RangeQuery, RangeRelation, RankFeatureQuery,
107    RegexpQuery, ScoreMode, ScriptQuery, ScriptScoreQuery, ScriptSortType, SimpleQueryStringQuery,
108    Sort, SortBuilder, SortMode, SortOrder, Sortable, TermQuery, TermsQuery, Text, TextMap,
109    ValidationMethod, WildcardQuery, WithSubfields, ZeroTermsQuery, boosting, combined_fields,
110    constant_score, dis_max, distance_feature, function_score, ids, kind, more_like_this,
111    multi_match, query_string, rank_feature, script, script_score, simple_query_string,
112};
113pub use msearch::MsearchBundle;
114pub use multi::{FlussoMultiDocument, MultiSearch};
115pub use path::{Segment, SegmentKind, nested_boundaries};
116pub use query::{AsQuery, Query, Root};
117pub use search::{FlussoDocument, FlussoIndex, Highlight, Hit, Search, SearchResponse};
118
119/// `#[derive(FlussoDocument)]` — generates the typed query surface for a
120/// hand-written document struct (its field handles) and implements the
121/// [`FlussoDocument`](trait@FlussoDocument) trait (`INDEX`/`SCHEMA_HASH` +
122/// `search`/`get`). See [`CLIENT.md`](../../../CLIENT.md). Enabled by the
123/// `derive` feature.
124#[cfg(feature = "derive")]
125pub use flusso_query_derive::FlussoDocument;
126
127/// `#[derive(FlussoValue)]` — implements [`trait@FlussoValue`] for an enum or newtype
128/// wrapper, so it may stand in for a field of the chosen kind (`#[flusso(keyword)]`
129/// — the default — `#[flusso(text)]`, `#[flusso(number)]`, or `#[flusso(date)]`)
130/// in a [`FlussoDocument`] struct. Enabled by the `derive` feature.
131#[cfg(feature = "derive")]
132pub use flusso_query_derive::FlussoValue;
133
134/// `#[derive(FlussoMap)]` — implements [`trait@FlussoMap`] for a newtype wrapper
135/// over a `map` field, so it may stand in for a `map` of the chosen value kind
136/// (`#[flusso(keyword)]` — the default — `#[flusso(text)]`, `#[flusso(number)]`,
137/// or `#[flusso(date)]`) in a [`FlussoDocument`] struct. A bare
138/// `HashMap<String, V>` needs no derive. Enabled by the `derive` feature.
139#[cfg(feature = "derive")]
140pub use flusso_query_derive::FlussoMap;
141
142/// `#[derive(FlussoMultiDocument)]` — implements [`trait@FlussoMultiDocument`]
143/// for an enum with one single-field variant per document type (the
144/// combined-search union): the generated impl lists every variant's index and
145/// decodes each hit into the variant matching its physical `_index`. Enabled
146/// by the `derive` feature.
147#[cfg(feature = "derive")]
148pub use flusso_query_derive::FlussoMultiDocument;
149
150// The multi-document derive's generated code deserializes variant payloads;
151// routing it through this re-export keeps it on this crate's `serde_json`.
152// Hidden: not API.
153#[doc(hidden)]
154pub use serde_json as __serde_json;
155
156/// `rust_decimal::Decimal`, re-exported for `decimal` fields. Enabled by the
157/// `decimal` feature.
158#[cfg(feature = "decimal")]
159pub use rust_decimal::Decimal;
160
161/// `chrono`, re-exported for `date`/`timestamp` fields. Enabled by the `chrono`
162/// feature.
163#[cfg(feature = "chrono")]
164pub use chrono;
165
166/// `uuid`, re-exported for `keyword` id / foreign-key fields. With this feature
167/// a `uuid::Uuid` field needs no `#[flusso(skip)]` and `id().eq(some_uuid)`
168/// works without `.to_string()`. Enabled by the `uuid` feature.
169#[cfg(feature = "uuid")]
170pub use uuid;