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};
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<i64> { 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, Date, FlussoValue, Geo, GeoPoint, Json, Keyword, Nested, NestedProjection,
100 Number, Object, Sort, SortOrder, Text, kind, multi_match,
101};
102pub use msearch::MsearchBundle;
103pub use multi::{FlussoMultiDocument, MultiSearch};
104pub use query::{AsQuery, Query, Root};
105pub use search::{FlussoDocument, Hit, Search, SearchResponse};
106
107/// `#[derive(FlussoDocument)]` — generates the typed query surface for a
108/// hand-written document struct (its field handles) and implements the
109/// [`FlussoDocument`](trait@FlussoDocument) trait (`INDEX`/`SCHEMA_HASH` +
110/// `search`/`get`). See [`CLIENT.md`](../../../CLIENT.md). Enabled by the
111/// `derive` feature.
112#[cfg(feature = "derive")]
113pub use flusso_query_derive::FlussoDocument;
114
115/// `#[derive(FlussoValue)]` — implements [`trait@FlussoValue`] for an enum or newtype
116/// wrapper, so it may stand in for a field of the chosen kind (`#[flusso(keyword)]`
117/// — the default — `#[flusso(text)]`, `#[flusso(number)]`, or `#[flusso(date)]`)
118/// in a [`FlussoDocument`] struct. Enabled by the `derive` feature.
119#[cfg(feature = "derive")]
120pub use flusso_query_derive::FlussoValue;
121
122/// `#[derive(FlussoMultiDocument)]` — implements [`trait@FlussoMultiDocument`]
123/// for an enum with one single-field variant per document type (the
124/// combined-search union): the generated impl lists every variant's index and
125/// decodes each hit into the variant matching its physical `_index`. Enabled
126/// by the `derive` feature.
127#[cfg(feature = "derive")]
128pub use flusso_query_derive::FlussoMultiDocument;
129
130// The multi-document derive's generated code deserializes variant payloads;
131// routing it through this re-export keeps it on this crate's `serde_json`.
132// Hidden: not API.
133#[doc(hidden)]
134pub use serde_json as __serde_json;
135
136/// `rust_decimal::Decimal`, re-exported for `decimal` fields. Enabled by the
137/// `decimal` feature.
138#[cfg(feature = "decimal")]
139pub use rust_decimal::Decimal;
140
141/// `chrono`, re-exported for `date`/`timestamp` fields. Enabled by the `chrono`
142/// feature.
143#[cfg(feature = "chrono")]
144pub use chrono;