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