Skip to main content

flusso_query/handles/
params.rs

1//! Closed enums for the query parameters that take a fixed set of tokens —
2//! replacing the stringly-typed `operator("and")` / `score_mode("avg")` shape so
3//! a typo is a compile error, not a 400 from OpenSearch.
4//!
5//! Genuinely open-ended params (`analyzer`, `format`, `time_zone`,
6//! `minimum_should_match`, simple-query-string `flags`) stay `String` — they
7//! aren't enumerable.
8
9use serde_json::Value;
10
11/// Boolean combinator for analyzed terms (`operator` / `default_operator`).
12#[derive(Debug, Clone, Copy)]
13pub enum Operator {
14    /// Every term must match.
15    And,
16    /// Any term may match.
17    Or,
18}
19
20impl Operator {
21    pub(crate) fn as_str(self) -> &'static str {
22        match self {
23            Operator::And => "AND",
24            Operator::Or => "OR",
25        }
26    }
27}
28
29/// How a `function_score`'s functions combine into one score.
30#[derive(Debug, Clone, Copy)]
31pub enum ScoreMode {
32    /// Multiply the function scores (default).
33    Multiply,
34    /// Sum them.
35    Sum,
36    /// Average them.
37    Avg,
38    /// Take the first matching function's score.
39    First,
40    /// Take the largest.
41    Max,
42    /// Take the smallest.
43    Min,
44}
45
46impl ScoreMode {
47    pub(crate) fn as_str(self) -> &'static str {
48        match self {
49            ScoreMode::Multiply => "multiply",
50            ScoreMode::Sum => "sum",
51            ScoreMode::Avg => "avg",
52            ScoreMode::First => "first",
53            ScoreMode::Max => "max",
54            ScoreMode::Min => "min",
55        }
56    }
57}
58
59/// How a `nested` query's matching-element scores combine into the parent score.
60/// Distinct from [`ScoreMode`]: no `multiply`/`first`, plus `None` (the nested
61/// clause acts as a pure filter, contributing no score).
62#[derive(Debug, Clone, Copy)]
63pub enum NestedScoreMode {
64    /// Average the element scores (default).
65    Avg,
66    /// Sum them.
67    Sum,
68    /// Take the smallest.
69    Min,
70    /// Take the largest.
71    Max,
72    /// Don't contribute to the parent score (filter only).
73    None,
74}
75
76impl NestedScoreMode {
77    pub(crate) fn as_str(self) -> &'static str {
78        match self {
79            NestedScoreMode::Avg => "avg",
80            NestedScoreMode::Sum => "sum",
81            NestedScoreMode::Min => "min",
82            NestedScoreMode::Max => "max",
83            NestedScoreMode::None => "none",
84        }
85    }
86}
87
88/// How a `function_score`'s combined function score merges with the query score.
89#[derive(Debug, Clone, Copy)]
90pub enum BoostMode {
91    /// Multiply (default).
92    Multiply,
93    /// Replace the query score entirely.
94    Replace,
95    /// Sum them.
96    Sum,
97    /// Average them.
98    Avg,
99    /// Take the largest.
100    Max,
101    /// Take the smallest.
102    Min,
103}
104
105impl BoostMode {
106    pub(crate) fn as_str(self) -> &'static str {
107        match self {
108            BoostMode::Multiply => "multiply",
109            BoostMode::Replace => "replace",
110            BoostMode::Sum => "sum",
111            BoostMode::Avg => "avg",
112            BoostMode::Max => "max",
113            BoostMode::Min => "min",
114        }
115    }
116}
117
118/// What a `match` does when analysis yields no terms (`zero_terms_query`).
119#[derive(Debug, Clone, Copy)]
120pub enum ZeroTermsQuery {
121    /// Match nothing (default).
122    None,
123    /// Match everything.
124    All,
125}
126
127impl ZeroTermsQuery {
128    pub(crate) fn as_str(self) -> &'static str {
129        match self {
130            ZeroTermsQuery::None => "none",
131            ZeroTermsQuery::All => "all",
132        }
133    }
134}
135
136/// How a `range` relates to range-typed field values (`relation`).
137#[derive(Debug, Clone, Copy)]
138pub enum RangeRelation {
139    /// The ranges overlap (default).
140    Intersects,
141    /// The field range fully contains the query range.
142    Contains,
143    /// The field range falls entirely within the query range.
144    Within,
145}
146
147impl RangeRelation {
148    pub(crate) fn as_str(self) -> &'static str {
149        match self {
150            RangeRelation::Intersects => "INTERSECTS",
151            RangeRelation::Contains => "CONTAINS",
152            RangeRelation::Within => "WITHIN",
153        }
154    }
155}
156
157/// The scoring `type` of a `multi_match`.
158#[derive(Debug, Clone, Copy)]
159pub enum MultiMatchType {
160    /// Score by the single best-matching field (default).
161    BestFields,
162    /// Sum the scores of every matching field.
163    MostFields,
164    /// Treat the fields as one big field, term-centric.
165    CrossFields,
166    /// Phrase match on each field.
167    Phrase,
168    /// Phrase-prefix match on each field.
169    PhrasePrefix,
170    /// Bool-prefix match on each field.
171    BoolPrefix,
172}
173
174impl MultiMatchType {
175    pub(crate) fn as_str(self) -> &'static str {
176        match self {
177            MultiMatchType::BestFields => "best_fields",
178            MultiMatchType::MostFields => "most_fields",
179            MultiMatchType::CrossFields => "cross_fields",
180            MultiMatchType::Phrase => "phrase",
181            MultiMatchType::PhrasePrefix => "phrase_prefix",
182            MultiMatchType::BoolPrefix => "bool_prefix",
183        }
184    }
185}
186
187/// How many clauses/terms must match (`minimum_should_match`). Types the common
188/// cases — an absolute count and a percentage — while [`raw`](Self::raw) keeps
189/// the full mini-language (`"3<90%"`, `"2<-25% 9<-3"`) reachable.
190///
191/// `i32` converts via [`From`], so `.minimum_should_match(2)` works directly;
192/// use [`percent`](Self::percent) / [`raw`](Self::raw) for the rest.
193#[derive(Debug, Clone)]
194pub enum MinimumShouldMatch {
195    /// An absolute count (negative = all but this many).
196    Count(i32),
197    /// A percentage (negative = all but this percent).
198    Percent(i32),
199    /// The raw `minimum_should_match` expression, verbatim.
200    Raw(String),
201}
202
203impl MinimumShouldMatch {
204    /// At least `n` clauses (negative = all but `n`).
205    pub fn count(n: i32) -> Self {
206        Self::Count(n)
207    }
208
209    /// At least `p` percent of clauses (negative = all but `p` percent).
210    pub fn percent(p: i32) -> Self {
211        Self::Percent(p)
212    }
213
214    /// The raw expression — the escape hatch for the combining mini-language.
215    pub fn raw(expr: impl Into<String>) -> Self {
216        Self::Raw(expr.into())
217    }
218
219    pub(crate) fn to_value(&self) -> Value {
220        match self {
221            MinimumShouldMatch::Count(n) => Value::from(*n),
222            MinimumShouldMatch::Percent(p) => Value::String(format!("{p}%")),
223            MinimumShouldMatch::Raw(expr) => Value::String(expr.clone()),
224        }
225    }
226}
227
228impl From<i32> for MinimumShouldMatch {
229    fn from(n: i32) -> Self {
230        MinimumShouldMatch::Count(n)
231    }
232}
233
234/// How `geo_distance` computes distance (`distance_type`).
235#[derive(Debug, Clone, Copy)]
236pub enum DistanceType {
237    /// Great-circle distance (default; accurate, slower).
238    Arc,
239    /// Planar approximation (faster, less accurate over long spans).
240    Plane,
241}
242
243impl DistanceType {
244    pub(crate) fn as_str(self) -> &'static str {
245        match self {
246            DistanceType::Arc => "arc",
247            DistanceType::Plane => "plane",
248        }
249    }
250}
251
252/// How malformed coordinates are handled (`validation_method`).
253#[derive(Debug, Clone, Copy)]
254pub enum ValidationMethod {
255    /// Reject malformed coordinates (default).
256    Strict,
257    /// Snap out-of-range coordinates into range.
258    Coerce,
259    /// Silently ignore malformed coordinates.
260    IgnoreMalformed,
261}
262
263impl ValidationMethod {
264    pub(crate) fn as_str(self) -> &'static str {
265        match self {
266            ValidationMethod::Strict => "STRICT",
267            ValidationMethod::Coerce => "COERCE",
268            ValidationMethod::IgnoreMalformed => "IGNORE_MALFORMED",
269        }
270    }
271}
272
273/// Numeric type a sort coerces to across indexes (`numeric_type`).
274#[derive(Debug, Clone, Copy)]
275pub enum NumericType {
276    /// Sort as `double`.
277    Double,
278    /// Sort as `long`.
279    Long,
280    /// Sort as `date` (millis).
281    Date,
282    /// Sort as `date_nanos` (nanos).
283    DateNanos,
284}
285
286impl NumericType {
287    pub(crate) fn as_str(self) -> &'static str {
288        match self {
289            NumericType::Double => "double",
290            NumericType::Long => "long",
291            NumericType::Date => "date",
292            NumericType::DateNanos => "date_nanos",
293        }
294    }
295}
296
297/// The value type a `_script` sort emits (`type`).
298#[derive(Debug, Clone, Copy)]
299pub enum ScriptSortType {
300    /// A numeric sort value.
301    Number,
302    /// A string sort value.
303    String,
304}
305
306impl ScriptSortType {
307    pub(crate) fn as_str(self) -> &'static str {
308        match self {
309            ScriptSortType::Number => "number",
310            ScriptSortType::String => "string",
311        }
312    }
313}
314
315/// Allowed edit distance for fuzzy matching (`fuzziness`).
316#[derive(Debug, Clone, Copy)]
317pub enum Fuzziness {
318    /// Distance scaled to term length (the usual choice).
319    Auto,
320    /// `AUTO` with explicit low/high length thresholds (`AUTO:lo:hi`).
321    AutoBounds(u32, u32),
322    /// A fixed number of edits.
323    Edits(u32),
324}
325
326impl Fuzziness {
327    pub(crate) fn to_value(self) -> Value {
328        match self {
329            Fuzziness::Auto => Value::String("AUTO".to_string()),
330            Fuzziness::AutoBounds(lo, hi) => Value::String(format!("AUTO:{lo}:{hi}")),
331            Fuzziness::Edits(edits) => Value::from(edits),
332        }
333    }
334}