Skip to main content

flusso_query/handles/
scalar.rs

1//! Scalar value fields: the [`Bool`] exact-match field and the ordered
2//! [`Number`] / [`Date`] fields with their range operators.
3//!
4//! The value operators return small builders ([`EqQuery`], [`TermsQuery`],
5//! [`RangeQuery`]) that carry the universal `boost` / `name` modifiers (and, for
6//! ranges, `format` / `time_zone` / `relation`) and render lazily through
7//! [`AsQuery`] — so they drop straight into a clause, with or
8//! without options.
9
10use std::marker::PhantomData;
11
12use serde_json::{Map, Value};
13
14use super::{
15    Common, FlussoValue, RangeRelation, Sort, SortOrder, common_opts, exists_q, kind, single, wrap,
16};
17use crate::query::{AsQuery, Query, Root};
18
19/// The JSON value for a typed date input, taken from its serde serialization
20/// (`String`/`&str` pass straight through; `chrono` types serialize to their
21/// ISO-8601 string). Mirrors `keyword_term` — a non-string serialization falls
22/// back to its display form rather than failing.
23fn date_value(value: &impl FlussoValue<kind::Date>) -> Value {
24    match serde_json::to_value(value) {
25        Ok(Value::String(string)) => Value::String(string),
26        Ok(other) => Value::String(other.to_string()),
27        Err(_) => Value::String(String::new()),
28    }
29}
30
31/// The JSON value for a numeric input, from its serde serialization. The
32/// primitives serialize straight to a JSON number; `rust_decimal::Decimal`
33/// serializes to a string (the workspace's `serde-with-str`), so parse it back
34/// to a number — the field is numeric, so a clean number is what it queries.
35/// Generic over the numeric kind `K` (`Byte`…`Decimal`).
36fn number_value<K>(value: &impl FlussoValue<K>) -> Value {
37    match serde_json::to_value(value) {
38        Ok(Value::String(string)) => string
39            .parse::<serde_json::Number>()
40            .map_or(Value::String(string), Value::Number),
41        Ok(other) => other,
42        Err(_) => Value::Null,
43    }
44}
45
46/// The JSON value for a boolean input, from its serde serialization (`bool` →
47/// `Value::Bool`; a bool newtype serializes through to the same).
48fn bool_value(value: &impl FlussoValue<kind::Bool>) -> Value {
49    serde_json::to_value(value).unwrap_or(Value::Null)
50}
51
52/// An exact-match (`term`) clause for a non-string value (number, bool, date),
53/// carrying the universal `boost` / `name` modifiers.
54#[derive(Debug, Clone)]
55pub struct EqQuery<S = Root> {
56    path: String,
57    value: Value,
58    common: Common,
59    _scope: PhantomData<fn() -> S>,
60}
61
62impl<S> EqQuery<S> {
63    fn new(path: &str, value: Value) -> Self {
64        Self {
65            path: path.to_string(),
66            value,
67            common: Common::default(),
68            _scope: PhantomData,
69        }
70    }
71
72    common_opts!(common);
73}
74
75impl<S> AsQuery<S> for EqQuery<S> {
76    fn into_query(self) -> Option<Query<S>> {
77        if self.common.is_empty() {
78            Some(single("term", &self.path, self.value))
79        } else {
80            let mut body = Map::new();
81            body.insert("value".to_string(), self.value);
82            self.common.write(&mut body);
83            Some(single("term", &self.path, Value::Object(body)))
84        }
85    }
86}
87
88/// A multi-value (`terms`) clause, carrying `boost` / `name`. Shared by the
89/// keyword and numeric `any_of` operators.
90#[derive(Debug, Clone)]
91pub struct TermsQuery<S = Root> {
92    path: String,
93    values: Vec<Value>,
94    common: Common,
95    _scope: PhantomData<fn() -> S>,
96}
97
98impl<S> TermsQuery<S> {
99    pub(crate) fn new(path: &str, values: Vec<Value>) -> Self {
100        Self {
101            path: path.to_string(),
102            values,
103            common: Common::default(),
104            _scope: PhantomData,
105        }
106    }
107
108    common_opts!(common);
109}
110
111impl<S> AsQuery<S> for TermsQuery<S> {
112    fn into_query(self) -> Option<Query<S>> {
113        // `terms` carries `boost` / `_name` beside the field, not inside it.
114        let mut body = Map::new();
115        body.insert(self.path, Value::Array(self.values));
116        self.common.write(&mut body);
117        Some(wrap("terms", body))
118    }
119}
120
121/// A `range` clause with bounds plus the universal `boost` / `name` and the
122/// range-specific `format` / `time_zone` / `relation` modifiers.
123#[derive(Debug, Clone)]
124pub struct RangeQuery<S = Root> {
125    path: String,
126    bounds: Vec<(&'static str, Value)>,
127    extra: Map<String, Value>,
128    common: Common,
129    _scope: PhantomData<fn() -> S>,
130}
131
132impl<S> RangeQuery<S> {
133    pub(crate) fn new(path: &str, bounds: Vec<(&'static str, Value)>) -> Self {
134        Self {
135            path: path.to_string(),
136            bounds,
137            extra: Map::new(),
138            common: Common::default(),
139            _scope: PhantomData,
140        }
141    }
142
143    /// Date math / numeric `format` for the bounds (`date` fields).
144    #[must_use]
145    pub fn format(mut self, format: impl Into<String>) -> Self {
146        self.extra
147            .insert("format".to_string(), Value::String(format.into()));
148        self
149    }
150
151    /// Time zone applied to the bounds (`date` fields), e.g. `"+01:00"`.
152    #[must_use]
153    pub fn time_zone(mut self, time_zone: impl Into<String>) -> Self {
154        self.extra
155            .insert("time_zone".to_string(), Value::String(time_zone.into()));
156        self
157    }
158
159    /// How the range relates to range-typed field values
160    /// ([`RangeRelation::Intersects`] / `Contains` / `Within`).
161    #[must_use]
162    pub fn relation(mut self, relation: RangeRelation) -> Self {
163        self.extra.insert(
164            "relation".to_string(),
165            Value::String(relation.as_str().to_string()),
166        );
167        self
168    }
169
170    common_opts!(common);
171}
172
173impl<S> AsQuery<S> for RangeQuery<S> {
174    fn into_query(self) -> Option<Query<S>> {
175        let mut body = self.extra;
176        for (key, value) in self.bounds {
177            body.insert(key.to_string(), value);
178        }
179        self.common.write(&mut body);
180        Some(single("range", &self.path, Value::Object(body)))
181    }
182}
183
184/// A boolean field.
185#[derive(Debug, Clone)]
186pub struct Bool<S = Root> {
187    path: String,
188    _scope: PhantomData<fn() -> S>,
189}
190
191impl<S> Bool<S> {
192    pub fn at(path: impl Into<String>) -> Self {
193        Self {
194            path: path.into(),
195            _scope: PhantomData,
196        }
197    }
198
199    /// Exact match. Accepts a `bool`, or a `#[derive(FlussoValue)]` bool newtype.
200    pub fn eq(&self, value: impl FlussoValue<kind::Bool>) -> EqQuery<S> {
201        EqQuery::new(&self.path, bool_value(&value))
202    }
203
204    /// The field has a non-null value.
205    pub fn exists(&self) -> Query<S> {
206        exists_q(&self.path)
207    }
208
209    pub fn asc(&self) -> Sort {
210        Sort::new(&self.path, SortOrder::Asc)
211    }
212
213    pub fn desc(&self) -> Sort {
214        Sort::new(&self.path, SortOrder::Desc)
215    }
216}
217
218/// A numeric field. `K` is the numeric kind ([`kind::Byte`]…[`kind::Decimal`]),
219/// `S` the scope. Value operators accept any value of that kind — the matching
220/// primitive, a losslessly-widening one (`i32` on a `Long`/`Double`/`Decimal`
221/// field), `rust_decimal::Decimal` (`decimal` feature, on a `Decimal` field), or
222/// a `#[derive(FlussoValue)]` numeric newtype — so a custom money/quantity type
223/// queries with no cast. A lossy value is a compile error (a float on an integer
224/// field, an `i64` on a `Short`).
225#[derive(Debug, Clone)]
226pub struct Number<K, S = Root> {
227    path: String,
228    _marker: PhantomData<fn() -> (K, S)>,
229}
230
231impl<K, S> Number<K, S> {
232    pub fn at(path: impl Into<String>) -> Self {
233        Self {
234            path: path.into(),
235            _marker: PhantomData,
236        }
237    }
238
239    /// Exact match.
240    pub fn eq(&self, value: impl FlussoValue<K>) -> EqQuery<S> {
241        EqQuery::new(&self.path, number_value(&value))
242    }
243
244    /// Match any of the given values.
245    pub fn any_of(&self, values: impl IntoIterator<Item = impl FlussoValue<K>>) -> TermsQuery<S> {
246        let array = values.into_iter().map(|v| number_value(&v)).collect();
247        TermsQuery::new(&self.path, array)
248    }
249
250    /// Strictly less than `value`.
251    pub fn lt(&self, value: impl FlussoValue<K>) -> RangeQuery<S> {
252        RangeQuery::new(&self.path, vec![("lt", number_value(&value))])
253    }
254
255    /// Less than or equal to `value`.
256    pub fn lte(&self, value: impl FlussoValue<K>) -> RangeQuery<S> {
257        RangeQuery::new(&self.path, vec![("lte", number_value(&value))])
258    }
259
260    /// Strictly greater than `value`.
261    pub fn gt(&self, value: impl FlussoValue<K>) -> RangeQuery<S> {
262        RangeQuery::new(&self.path, vec![("gt", number_value(&value))])
263    }
264
265    /// Greater than or equal to `value`.
266    pub fn gte(&self, value: impl FlussoValue<K>) -> RangeQuery<S> {
267        RangeQuery::new(&self.path, vec![("gte", number_value(&value))])
268    }
269
270    /// Inclusive range `[low, high]`.
271    pub fn between(&self, low: impl FlussoValue<K>, high: impl FlussoValue<K>) -> RangeQuery<S> {
272        RangeQuery::new(
273            &self.path,
274            vec![("gte", number_value(&low)), ("lte", number_value(&high))],
275        )
276    }
277
278    /// The field has a non-null value.
279    pub fn exists(&self) -> Query<S> {
280        exists_q(&self.path)
281    }
282
283    pub fn asc(&self) -> Sort {
284        Sort::new(&self.path, SortOrder::Asc)
285    }
286
287    pub fn desc(&self) -> Sort {
288        Sort::new(&self.path, SortOrder::Desc)
289    }
290}
291
292/// A `date`/`timestamp` field. Bounds are ISO-8601 strings.
293#[derive(Debug, Clone)]
294pub struct Date<S = Root> {
295    path: String,
296    _scope: PhantomData<fn() -> S>,
297}
298
299impl<S> Date<S> {
300    pub fn at(path: impl Into<String>) -> Self {
301        Self {
302            path: path.into(),
303            _scope: PhantomData,
304        }
305    }
306
307    /// Exact match. Accepts a `String`/`&str`, or — with the `chrono` feature —
308    /// a `NaiveDate` / `NaiveDateTime` / `DateTime<Utc>`.
309    pub fn eq(&self, value: impl FlussoValue<kind::Date>) -> EqQuery<S> {
310        EqQuery::new(&self.path, date_value(&value))
311    }
312
313    /// Match any of the given dates (`String`/`&str` or `chrono` date types).
314    pub fn any_of(
315        &self,
316        values: impl IntoIterator<Item = impl FlussoValue<kind::Date>>,
317    ) -> TermsQuery<S> {
318        let array = values.into_iter().map(|v| date_value(&v)).collect();
319        TermsQuery::new(&self.path, array)
320    }
321
322    /// Strictly before `value`.
323    pub fn lt(&self, value: impl FlussoValue<kind::Date>) -> RangeQuery<S> {
324        RangeQuery::new(&self.path, vec![("lt", date_value(&value))])
325    }
326
327    /// At or before `value`.
328    pub fn lte(&self, value: impl FlussoValue<kind::Date>) -> RangeQuery<S> {
329        RangeQuery::new(&self.path, vec![("lte", date_value(&value))])
330    }
331
332    /// Strictly after `value`.
333    pub fn gt(&self, value: impl FlussoValue<kind::Date>) -> RangeQuery<S> {
334        RangeQuery::new(&self.path, vec![("gt", date_value(&value))])
335    }
336
337    /// At or after `value`.
338    pub fn gte(&self, value: impl FlussoValue<kind::Date>) -> RangeQuery<S> {
339        RangeQuery::new(&self.path, vec![("gte", date_value(&value))])
340    }
341
342    /// Inclusive range `[low, high]`.
343    pub fn between(
344        &self,
345        low: impl FlussoValue<kind::Date>,
346        high: impl FlussoValue<kind::Date>,
347    ) -> RangeQuery<S> {
348        RangeQuery::new(
349            &self.path,
350            vec![("gte", date_value(&low)), ("lte", date_value(&high))],
351        )
352    }
353
354    /// The field has a non-null value.
355    pub fn exists(&self) -> Query<S> {
356        exists_q(&self.path)
357    }
358
359    pub fn asc(&self) -> Sort {
360        Sort::new(&self.path, SortOrder::Asc)
361    }
362
363    pub fn desc(&self) -> Sort {
364        Sort::new(&self.path, SortOrder::Desc)
365    }
366}