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