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 geo;
28mod nested;
29mod object;
30mod scalar;
31mod sort;
32mod string;
33
34pub use geo::{Geo, GeoPoint};
35pub use nested::{Nested, NestedProjection};
36pub use object::{Binary, Json, Object};
37pub use scalar::{Bool, Date, Number};
38pub use sort::{Sort, SortOrder};
39pub use string::{Keyword, Text, multi_match};
40
41// ---- shared leaf builders (generic over scope) -----------------------------
42
43/// `{ "<wrapper>": { "<path>": <value> } }`.
44fn single<S>(wrapper: &str, path: &str, value: Value) -> Query<S> {
45    let mut inner = Map::new();
46    inner.insert(path.to_string(), value);
47    let mut outer = Map::new();
48    outer.insert(wrapper.to_string(), Value::Object(inner));
49    Query::leaf(Value::Object(outer))
50}
51
52/// `{ "exists": { "field": "<path>" } }`.
53fn exists_q<S>(path: &str) -> Query<S> {
54    let mut inner = Map::new();
55    inner.insert("field".to_string(), Value::String(path.to_string()));
56    let mut outer = Map::new();
57    outer.insert("exists".to_string(), Value::Object(inner));
58    Query::leaf(Value::Object(outer))
59}
60
61/// `{ "range": { "<path>": { <bounds…> } } }`.
62fn range_q<S>(path: &str, bounds: Vec<(&str, Value)>) -> Query<S> {
63    let mut body = Map::new();
64    for (key, value) in bounds {
65        body.insert(key.to_string(), value);
66    }
67    single("range", path, Value::Object(body))
68}
69
70/// `{ "match_all": {} }`.
71pub(crate) fn match_all_value() -> Value {
72    let mut outer = Map::new();
73    outer.insert("match_all".to_string(), Value::Object(Map::new()));
74    Value::Object(outer)
75}
76
77// ---- FlussoValue ------------------------------------------------------------
78
79/// Field-category markers for [`trait@FlussoValue`]. Zero-size and uninhabited — they
80/// exist only as the `K` type parameter, so one type can be a valid value for
81/// several kinds (e.g. `String` is a [`kind::Keyword`], [`kind::Text`], and
82/// [`kind::Date`] value).
83pub mod kind {
84    /// A `keyword` field — an exact string.
85    #[derive(Debug)]
86    pub enum Keyword {}
87    /// A `text` field — an analyzed string.
88    #[derive(Debug)]
89    pub enum Text {}
90    /// A numeric field (`byte`…`double`, `scaled_float`).
91    #[derive(Debug)]
92    pub enum Number {}
93    /// A `date`/`timestamp` field — an ISO-8601 string.
94    #[derive(Debug)]
95    pub enum Date {}
96}
97
98/// A Rust type usable where a field of kind `K` is expected: as the field type
99/// in a `#[derive(FlussoDocument)]` struct, and (for [`kind::Keyword`]) as a
100/// query value on [`Keyword::eq`]/[`Keyword::in_`].
101///
102/// Built-in leaf types are pre-implemented (`String`/`&str` for keyword, the
103/// numeric primitives for number, …). Custom enums and newtype wrappers opt in
104/// with `#[derive(FlussoValue)]` (e.g. a `Pro`/`Enterprise`/`Free` tier enum →
105/// `Account::tier().eq(AccountTier::Pro)`, matched against its serde string).
106/// `FlussoDocument` emits a deferred bound on this trait for any non-primitive
107/// field type, so a document only compiles when the type genuinely fits.
108#[diagnostic::on_unimplemented(
109    message = "`{Self}` is not a valid value for a `{K}` field",
110    label = "unsupported field type",
111    note = "use a built-in leaf type, or add `#[derive(FlussoValue)]` (with the matching kind) to `{Self}`"
112)]
113pub trait FlussoValue<K> {}
114
115impl FlussoValue<kind::Keyword> for String {}
116impl FlussoValue<kind::Keyword> for &str {}
117
118impl FlussoValue<kind::Text> for String {}
119impl FlussoValue<kind::Text> for &str {}
120
121impl FlussoValue<kind::Number> for i8 {}
122impl FlussoValue<kind::Number> for i16 {}
123impl FlussoValue<kind::Number> for i32 {}
124impl FlussoValue<kind::Number> for i64 {}
125impl FlussoValue<kind::Number> for f32 {}
126impl FlussoValue<kind::Number> for f64 {}
127#[cfg(feature = "decimal")]
128impl FlussoValue<kind::Number> for crate::Decimal {}
129
130impl FlussoValue<kind::Date> for String {}