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 query;
91mod search;
92
93#[cfg(test)]
94mod tests;
95
96pub use client::Client;
97pub use error::{Error, Result};
98pub use handles::{
99 Binary, Bool, BoostMode, BoostingQuery, CombinedFieldsQuery, ConstantScoreQuery, Date, DateMap,
100 DisMaxQuery, Distance, DistanceFeatureQuery, DistanceType, DistanceUnit, EqQuery, FlussoMap,
101 FlussoValue, FunctionScoreQuery, Fuzziness, FuzzyQuery, Geo, GeoDistanceQuery, GeoPoint,
102 IdsQuery, Json, Keyword, KeywordMap, MapSearch, MatchQuery, MinimumShouldMatch,
103 MoreLikeThisQuery, MultiMatchQuery, MultiMatchType, Nested, NestedProjection, NestedQuery,
104 NestedScoreMode, NoSubfields, Number, NumberMap, NumericType, Object, Operator, PrefixQuery,
105 QueryStringQuery, RangeQuery, RangeRelation, RankFeatureQuery, RegexpQuery, ScoreMode,
106 ScriptQuery, ScriptScoreQuery, ScriptSortType, SimpleQueryStringQuery, Sort, SortMode,
107 SortOrder, TermQuery, TermsQuery, Text, TextMap, ValidationMethod, WildcardQuery,
108 WithSubfields, ZeroTermsQuery, boosting, combined_fields, constant_score, dis_max,
109 distance_feature, function_score, ids, kind, more_like_this, multi_match, query_string,
110 rank_feature, script, script_score, simple_query_string,
111};
112pub use msearch::MsearchBundle;
113pub use multi::{FlussoMultiDocument, MultiSearch};
114pub use query::{AsQuery, Query, Root};
115pub use search::{FlussoDocument, Highlight, Hit, Search, SearchResponse};
116
117/// `#[derive(FlussoDocument)]` — generates the typed query surface for a
118/// hand-written document struct (its field handles) and implements the
119/// [`FlussoDocument`](trait@FlussoDocument) trait (`INDEX`/`SCHEMA_HASH` +
120/// `search`/`get`). See [`CLIENT.md`](../../../CLIENT.md). Enabled by the
121/// `derive` feature.
122#[cfg(feature = "derive")]
123pub use flusso_query_derive::FlussoDocument;
124
125/// `#[derive(FlussoValue)]` — implements [`trait@FlussoValue`] for an enum or newtype
126/// wrapper, so it may stand in for a field of the chosen kind (`#[flusso(keyword)]`
127/// — the default — `#[flusso(text)]`, `#[flusso(number)]`, or `#[flusso(date)]`)
128/// in a [`FlussoDocument`] struct. Enabled by the `derive` feature.
129#[cfg(feature = "derive")]
130pub use flusso_query_derive::FlussoValue;
131
132/// `#[derive(FlussoMap)]` — implements [`trait@FlussoMap`] for a newtype wrapper
133/// over a `map` field, so it may stand in for a `map` of the chosen value kind
134/// (`#[flusso(keyword)]` — the default — `#[flusso(text)]`, `#[flusso(number)]`,
135/// or `#[flusso(date)]`) in a [`FlussoDocument`] struct. A bare
136/// `HashMap<String, V>` needs no derive. Enabled by the `derive` feature.
137#[cfg(feature = "derive")]
138pub use flusso_query_derive::FlussoMap;
139
140/// `#[derive(FlussoMultiDocument)]` — implements [`trait@FlussoMultiDocument`]
141/// for an enum with one single-field variant per document type (the
142/// combined-search union): the generated impl lists every variant's index and
143/// decodes each hit into the variant matching its physical `_index`. Enabled
144/// by the `derive` feature.
145#[cfg(feature = "derive")]
146pub use flusso_query_derive::FlussoMultiDocument;
147
148// The multi-document derive's generated code deserializes variant payloads;
149// routing it through this re-export keeps it on this crate's `serde_json`.
150// Hidden: not API.
151#[doc(hidden)]
152pub use serde_json as __serde_json;
153
154/// `rust_decimal::Decimal`, re-exported for `decimal` fields. Enabled by the
155/// `decimal` feature.
156#[cfg(feature = "decimal")]
157pub use rust_decimal::Decimal;
158
159/// `chrono`, re-exported for `date`/`timestamp` fields. Enabled by the `chrono`
160/// feature.
161#[cfg(feature = "chrono")]
162pub use chrono;
163
164/// `uuid`, re-exported for `keyword` id / foreign-key fields. With this feature
165/// a `uuid::Uuid` field needs no `#[flusso(skip)]` and `id().eq(some_uuid)`
166/// works without `.to_string()`. Enabled by the `uuid` feature.
167#[cfg(feature = "uuid")]
168pub use uuid;