Skip to main content

flusso_query/handles/
sort.rs

1//! Sort keys: [`SortOrder`], [`SortMode`], and the [`Sort`] builder produced by
2//! `.asc()` / `.desc()` on a sortable handle (or `Geo::distance_sort`,
3//! [`Sort::score`], [`Sort::script`]).
4//!
5//! A [`Sort`] carries the key it sorts on (a field path, `_score`,
6//! `_geo_distance`, or `_script`) plus its options (`missing`, `mode`,
7//! `unmapped_type`, …); `.missing_first()` / `.mode(..)` chain onto it, and it
8//! renders to one entry in the `sort` array. The typed handle is always the
9//! entry point — there is no public string-path sort.
10
11use serde_json::{Map, Value};
12
13use super::{NumericType, ScriptSortType};
14use crate::query::AsQuery;
15
16/// Sort direction.
17#[derive(Debug, Clone, Copy)]
18pub enum SortOrder {
19    /// Ascending.
20    Asc,
21    /// Descending.
22    Desc,
23}
24
25impl SortOrder {
26    pub(crate) fn as_str(self) -> &'static str {
27        match self {
28            SortOrder::Asc => "asc",
29            SortOrder::Desc => "desc",
30        }
31    }
32}
33
34/// How a multi-valued field collapses to one sort value.
35#[derive(Debug, Clone, Copy)]
36pub enum SortMode {
37    /// Smallest value.
38    Min,
39    /// Largest value.
40    Max,
41    /// Arithmetic mean (numeric fields).
42    Avg,
43    /// Sum (numeric fields).
44    Sum,
45    /// Median (numeric fields).
46    Median,
47}
48
49impl SortMode {
50    fn as_str(self) -> &'static str {
51        match self {
52            SortMode::Min => "min",
53            SortMode::Max => "max",
54            SortMode::Avg => "avg",
55            SortMode::Sum => "sum",
56            SortMode::Median => "median",
57        }
58    }
59}
60
61/// A single sort key. Produced by `.asc()` / `.desc()` on a sortable handle, by
62/// [`Sort::score`] / [`Sort::script`], or by `Geo::distance_sort`; chain the
63/// option setters (`missing_first`, `mode`, `unmapped_type`, …) onto it.
64#[derive(Debug, Clone)]
65pub struct Sort {
66    key: String,
67    body: Map<String, Value>,
68}
69
70impl Sort {
71    /// A field/order sort: `{ "<field>": { "order": "asc"|"desc" } }`.
72    pub(crate) fn new(field: &str, order: SortOrder) -> Self {
73        let mut body = Map::new();
74        body.insert(
75            "order".to_string(),
76            Value::String(order.as_str().to_string()),
77        );
78        Self {
79            key: field.to_string(),
80            body,
81        }
82    }
83
84    /// Sort by relevance `_score` (descending by default).
85    #[must_use]
86    pub fn score() -> Self {
87        let mut sort = Self {
88            key: "_score".to_string(),
89            body: Map::new(),
90        };
91        sort.body
92            .insert("order".to_string(), Value::String("desc".to_string()));
93        sort
94    }
95
96    /// Sort by a computed script value. `script_type` is the emitted value type
97    /// ([`ScriptSortType::Number`] / [`ScriptSortType::String`]); `source` is
98    /// the painless expression.
99    #[must_use]
100    pub fn script(
101        script_type: ScriptSortType,
102        source: impl Into<String>,
103        order: SortOrder,
104    ) -> Self {
105        let mut script = Map::new();
106        script.insert("source".to_string(), Value::String(source.into()));
107        let mut body = Map::new();
108        body.insert(
109            "type".to_string(),
110            Value::String(script_type.as_str().to_string()),
111        );
112        body.insert("script".to_string(), Value::Object(script));
113        body.insert(
114            "order".to_string(),
115            Value::String(order.as_str().to_string()),
116        );
117        Self {
118            key: "_script".to_string(),
119            body,
120        }
121    }
122
123    /// A pre-built sort clause (e.g. `_geo_distance`).
124    pub(crate) fn from_parts(key: String, body: Map<String, Value>) -> Self {
125        Self { key, body }
126    }
127
128    /// Sort ascending.
129    #[must_use]
130    pub fn asc(mut self) -> Self {
131        self.body
132            .insert("order".to_string(), Value::String("asc".to_string()));
133        self
134    }
135
136    /// Sort descending.
137    #[must_use]
138    pub fn desc(mut self) -> Self {
139        self.body
140            .insert("order".to_string(), Value::String("desc".to_string()));
141        self
142    }
143
144    /// Place documents missing this field first.
145    #[must_use]
146    pub fn missing_first(mut self) -> Self {
147        self.body
148            .insert("missing".to_string(), Value::String("_first".to_string()));
149        self
150    }
151
152    /// Place documents missing this field last.
153    #[must_use]
154    pub fn missing_last(mut self) -> Self {
155        self.body
156            .insert("missing".to_string(), Value::String("_last".to_string()));
157        self
158    }
159
160    /// Substitute a literal value for documents missing this field.
161    #[must_use]
162    pub fn missing(mut self, value: impl Into<Value>) -> Self {
163        self.body.insert("missing".to_string(), value.into());
164        self
165    }
166
167    /// How a multi-valued field reduces to one sort value.
168    #[must_use]
169    pub fn mode(mut self, mode: SortMode) -> Self {
170        self.body
171            .insert("mode".to_string(), Value::String(mode.as_str().to_string()));
172        self
173    }
174
175    /// Type to assume when the field is unmapped on some shard (instead of
176    /// failing the search), e.g. `"long"`.
177    #[must_use]
178    pub fn unmapped_type(mut self, unmapped_type: impl Into<String>) -> Self {
179        self.body.insert(
180            "unmapped_type".to_string(),
181            Value::String(unmapped_type.into()),
182        );
183        self
184    }
185
186    /// Numeric type to sort as ([`NumericType`]), for cross-index type coercion.
187    #[must_use]
188    pub fn numeric_type(mut self, numeric_type: NumericType) -> Self {
189        self.body.insert(
190            "numeric_type".to_string(),
191            Value::String(numeric_type.as_str().to_string()),
192        );
193        self
194    }
195
196    /// Date `format` for a `date` field sort.
197    #[must_use]
198    pub fn format(mut self, format: impl Into<String>) -> Self {
199        self.body
200            .insert("format".to_string(), Value::String(format.into()));
201        self
202    }
203
204    /// Sort by a field inside a `nested` array, scoped to `path`.
205    #[must_use]
206    pub fn nested(mut self, path: impl Into<String>) -> Self {
207        let mut nested = Map::new();
208        nested.insert("path".to_string(), Value::String(path.into()));
209        self.body
210            .insert("nested".to_string(), Value::Object(nested));
211        self
212    }
213
214    /// Sort by a field inside a `nested` array scoped to `path`, considering
215    /// only elements matching `filter`.
216    #[must_use]
217    pub fn nested_filtered<S>(mut self, path: impl Into<String>, filter: impl AsQuery<S>) -> Self {
218        let mut nested = Map::new();
219        nested.insert("path".to_string(), Value::String(path.into()));
220        if let Some(query) = filter.into_query() {
221            nested.insert("filter".to_string(), query.to_value());
222        }
223        self.body
224            .insert("nested".to_string(), Value::Object(nested));
225        self
226    }
227
228    pub(crate) fn to_value(&self) -> Value {
229        let mut outer = Map::new();
230        outer.insert(self.key.clone(), Value::Object(self.body.clone()));
231        Value::Object(outer)
232    }
233}