Skip to main content

flusso_query/handles/
mod.rs

1//! Field handles. Each handle carries a field path **and a scope** `S`, and
2//! exposes only the operators its mapping type supports; every operator builds a
3//! [`Query`]`<S>`.
4//!
5//! Root fields and flattened object / to-one-join sub-fields are scope
6//! [`Root`](crate::Root); a `nested` array's element handles carry the element type as their
7//! scope, so their queries must be lifted with [`Nested::any`]/[`Nested::all`]
8//! before joining a parent query. (The derive picks the right scope; hand-written
9//! handles default to `Root`.)
10//!
11//! The handles are grouped by field family:
12//!
13//! - `string` — [`Keyword`], [`Text`], and the cross-field [`multi_match`].
14//! - `scalar` — [`Bool`], [`Number`], [`Date`] (exact/range value fields).
15//! - `map` — [`TextMap`]/[`KeywordMap`] dynamic-key objects and [`MapSearch`].
16//! - `nested` — [`Nested`] arrays and their [`NestedProjection`].
17//! - `object` — [`Object`] sub-documents and the opaque [`Binary`]/[`Json`] fields.
18//! - `geo` — [`Geo`] points and [`GeoPoint`].
19//! - `sort` — [`Sort`]/[`SortOrder`].
20//!
21//! This module holds the pieces they share: the leaf-query builders and the
22//! [`trait@FlussoValue`] type-kind machinery.
23
24use serde_json::{Map, Value};
25
26use crate::query::Query;
27
28mod compound;
29mod extra;
30mod geo;
31mod map;
32mod nested;
33mod object;
34mod params;
35mod scalar;
36mod sort;
37mod string;
38
39pub use compound::{
40    BoostingQuery, ConstantScoreQuery, DisMaxQuery, FunctionScoreQuery, boosting, constant_score,
41    dis_max, function_score,
42};
43pub use extra::{
44    CombinedFieldsQuery, DistanceFeatureQuery, IdsQuery, MoreLikeThisQuery, QueryStringQuery,
45    RankFeatureQuery, ScriptQuery, ScriptScoreQuery, SimpleQueryStringQuery, combined_fields,
46    distance_feature, ids, more_like_this, query_string, rank_feature, script, script_score,
47    simple_query_string,
48};
49pub use geo::{Distance, DistanceUnit, Geo, GeoDistanceQuery, GeoPoint};
50pub use map::{DateMap, KeywordMap, MapSearch, NumberMap, TextMap};
51pub use nested::{Nested, NestedProjection, NestedQuery};
52pub use object::{Binary, Json, Object};
53pub use params::{
54    BoostMode, DistanceType, Fuzziness, MinimumShouldMatch, MultiMatchType, NestedScoreMode,
55    NumericType, Operator, RangeRelation, ScoreMode, ScriptSortType, ValidationMethod,
56    ZeroTermsQuery,
57};
58pub use scalar::{Bool, Date, EqQuery, Number, RangeQuery, TermsQuery};
59pub use sort::{Sort, SortMode, SortOrder};
60pub use string::{
61    FuzzyQuery, Keyword, MatchQuery, MultiMatchQuery, NoSubfields, PrefixQuery, RegexpQuery,
62    TermQuery, Text, WildcardQuery, WithSubfields, multi_match,
63};
64
65/// `{ "<wrapper>": { "<path>": <value> } }`.
66fn single<S>(wrapper: &str, path: &str, value: Value) -> Query<S> {
67    let mut inner = Map::new();
68    inner.insert(path.to_string(), value);
69    wrap(wrapper, inner)
70}
71
72/// `{ "<wrapper>": { <body> } }`.
73fn wrap<S>(wrapper: &str, body: Map<String, Value>) -> Query<S> {
74    let mut outer = Map::new();
75    outer.insert(wrapper.to_string(), Value::Object(body));
76    Query::leaf(Value::Object(outer))
77}
78
79/// The universal leaf-query modifiers every builder carries: `boost` (a
80/// relevance multiplier) and `name` (`_name`, surfaced in a hit's
81/// `matched_queries`). Builders embed one and call [`Common::write`] when
82/// rendering.
83#[derive(Debug, Clone, Default)]
84pub(crate) struct Common {
85    boost: Option<f32>,
86    name: Option<String>,
87}
88
89impl Common {
90    pub(crate) fn set_boost(&mut self, boost: f32) {
91        self.boost = Some(boost);
92    }
93
94    pub(crate) fn set_name(&mut self, name: String) {
95        self.name = Some(name);
96    }
97
98    /// Whether neither modifier is set (so a builder may render the shorthand).
99    pub(crate) fn is_empty(&self) -> bool {
100        self.boost.is_none() && self.name.is_none()
101    }
102
103    /// Write `boost` / `_name` into an option map, if set.
104    pub(crate) fn write(&self, map: &mut Map<String, Value>) {
105        if let Some(boost) = self.boost {
106            map.insert("boost".to_string(), Value::from(boost));
107        }
108        if let Some(name) = &self.name {
109            map.insert("_name".to_string(), Value::String(name.clone()));
110        }
111    }
112}
113
114/// Emit the universal `boost` / `name` setters on a builder whose [`Common`]
115/// lives in `self.$field`. Keeps the two methods identical across every builder.
116macro_rules! common_opts {
117    ($field:ident) => {
118        /// Multiply this clause's relevance score by `boost`.
119        #[must_use]
120        pub fn boost(mut self, boost: f32) -> Self {
121            self.$field.set_boost(boost);
122            self
123        }
124
125        /// Tag this clause with `_name`, surfaced in a hit's `matched_queries`.
126        #[must_use]
127        pub fn name(mut self, name: impl Into<String>) -> Self {
128            self.$field.set_name(name.into());
129            self
130        }
131    };
132}
133pub(crate) use common_opts;
134
135/// Render `{ wrapper: { path: <value> } }` (the DSL shorthand) when `opts` is
136/// empty, else `{ wrapper: { path: { <key>: <value>, ...opts } } }`. The shared
137/// shape for the value-bearing leaf queries (`term`/`prefix`/`wildcard`/… with
138/// `key = "value"`; `match`/`match_phrase`/… with `key = "query"`).
139fn keyed_value_query<S>(
140    wrapper: &str,
141    path: &str,
142    key: &str,
143    value: Value,
144    mut opts: Map<String, Value>,
145) -> Query<S> {
146    if opts.is_empty() {
147        single(wrapper, path, value)
148    } else {
149        opts.insert(key.to_string(), value);
150        single(wrapper, path, Value::Object(opts))
151    }
152}
153
154/// `{ "exists": { "field": "<path>" } }`.
155fn exists_q<S>(path: &str) -> Query<S> {
156    let mut inner = Map::new();
157    inner.insert("field".to_string(), Value::String(path.to_string()));
158    let mut outer = Map::new();
159    outer.insert("exists".to_string(), Value::Object(inner));
160    Query::leaf(Value::Object(outer))
161}
162
163/// `{ "match_all": {} }`.
164pub(crate) fn match_all_value() -> Value {
165    let mut outer = Map::new();
166    outer.insert("match_all".to_string(), Value::Object(Map::new()));
167    Value::Object(outer)
168}
169
170/// Field-category markers for [`trait@FlussoValue`]. Zero-size and uninhabited — they
171/// exist only as the `K` type parameter, so one type can be a valid value for
172/// several kinds (e.g. `String` is a [`kind::Keyword`], [`kind::Text`], and
173/// [`kind::Date`] value).
174pub mod kind {
175    /// A `keyword` field — an exact string.
176    #[derive(Debug)]
177    pub enum Keyword {}
178    /// A `text` field — an analyzed string.
179    #[derive(Debug)]
180    pub enum Text {}
181    /// A numeric field (`byte`…`double`, `scaled_float`).
182    #[derive(Debug)]
183    pub enum Number {}
184    /// A `date`/`timestamp` field — an ISO-8601 string.
185    #[derive(Debug)]
186    pub enum Date {}
187}
188
189/// A Rust type usable where a field of kind `K` is expected: as the field type
190/// in a `#[derive(FlussoDocument)]` struct, and as a query value — for
191/// [`kind::Keyword`] on [`Keyword::eq`]/[`Keyword::any_of`], and for
192/// [`kind::Date`] on [`Date::eq`]/[`Date::gte`]/… (`String`/`&str`, or the
193/// `chrono` date types behind the `chrono` feature).
194///
195/// Built-in leaf types are pre-implemented (`String`/`&str` for keyword, the
196/// numeric primitives for number, …). Custom enums and newtype wrappers opt in
197/// with `#[derive(FlussoValue)]` (e.g. a `Pro`/`Enterprise`/`Free` tier enum →
198/// `Account::tier().eq(AccountTier::Pro)`, matched against its serde string).
199/// `FlussoDocument` emits a deferred bound on this trait for any non-primitive
200/// field type, so a document only compiles when the type genuinely fits.
201#[diagnostic::on_unimplemented(
202    message = "`{Self}` is not a valid value for a `{K}` field",
203    label = "unsupported field type",
204    note = "use a built-in leaf type, or add `#[derive(FlussoValue)]` (with the matching kind) to `{Self}`"
205)]
206pub trait FlussoValue<K> {}
207
208impl FlussoValue<kind::Keyword> for String {}
209impl FlussoValue<kind::Keyword> for &str {}
210#[cfg(feature = "uuid")]
211impl FlussoValue<kind::Keyword> for uuid::Uuid {}
212#[cfg(feature = "uuid")]
213impl FlussoValue<kind::Keyword> for &uuid::Uuid {}
214
215impl FlussoValue<kind::Text> for String {}
216impl FlussoValue<kind::Text> for &str {}
217
218impl FlussoValue<kind::Number> for i8 {}
219impl FlussoValue<kind::Number> for i16 {}
220impl FlussoValue<kind::Number> for i32 {}
221impl FlussoValue<kind::Number> for i64 {}
222impl FlussoValue<kind::Number> for f32 {}
223impl FlussoValue<kind::Number> for f64 {}
224#[cfg(feature = "decimal")]
225impl FlussoValue<kind::Number> for crate::Decimal {}
226
227impl FlussoValue<kind::Date> for String {}
228impl FlussoValue<kind::Date> for &str {}
229#[cfg(feature = "chrono")]
230impl FlussoValue<kind::Date> for chrono::NaiveDate {}
231#[cfg(feature = "chrono")]
232impl FlussoValue<kind::Date> for chrono::NaiveDateTime {}
233#[cfg(feature = "chrono")]
234impl FlussoValue<kind::Date> for chrono::DateTime<chrono::Utc> {}
235
236/// A Rust type usable as the **document type** of a `map` field of value kind
237/// `K`: a dynamic-key object whose values are all of kind `K`.
238///
239/// The canonical map type — `HashMap<String, V>` where `V` is a `K` value — is
240/// pre-implemented via a blanket impl, so `HashMap<String, String>` is a valid
241/// `text`/`keyword` map and `HashMap<String, i64>` a valid `long` map with no
242/// extra code. A whole-map newtype wrapper (`struct Translations(HashMap<…>)`)
243/// opts in with `#[derive(FlussoMap)]`. `FlussoDocument` emits a deferred bound
244/// on this trait for a `map` field, so the document only compiles when its type
245/// genuinely fits the declared value kind.
246#[diagnostic::on_unimplemented(
247    message = "`{Self}` is not a valid map for a `{K}` field",
248    label = "unsupported map type",
249    note = "use `HashMap<String, V>` with a `{K}` value type, or add `#[derive(FlussoMap)]` (with the matching kind) to `{Self}`"
250)]
251pub trait FlussoMap<K> {}
252
253impl<K, V: FlussoValue<K>> FlussoMap<K> for std::collections::HashMap<String, V> {}