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