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