1use std::marker::PhantomData;
11
12use serde_json::{Map, Value};
13
14use super::{
15 Common, FlussoValue, Fuzziness, MinimumShouldMatch, MultiMatchType, Operator, Sort, SortOrder,
16 Sortable, TermsQuery, ZeroTermsQuery, common_opts, exists_q, keyed_value_query, kind, wrap,
17};
18use crate::FlussoDocument;
19use crate::query::{AsQuery, Query, Root};
20
21fn keyword_term(value: &impl serde::Serialize) -> Value {
27 match serde_json::to_value(value) {
28 Ok(Value::String(string)) => Value::String(string),
29 Ok(other) => Value::String(other.to_string()),
30 Err(_) => Value::String(String::new()),
31 }
32}
33
34#[derive(Debug, Clone)]
37pub struct TermQuery<S = Root> {
38 path: String,
39 value: Value,
40 case_insensitive: Option<bool>,
41 common: Common,
42 _scope: PhantomData<fn() -> S>,
43}
44
45impl<S> TermQuery<S> {
46 fn new(path: &str, value: Value) -> Self {
47 Self {
48 path: path.to_string(),
49 value,
50 case_insensitive: None,
51 common: Common::default(),
52 _scope: PhantomData,
53 }
54 }
55
56 #[must_use]
58 pub fn case_insensitive(mut self) -> Self {
59 self.case_insensitive = Some(true);
60 self
61 }
62
63 common_opts!(common);
64}
65
66impl<S> AsQuery<S> for TermQuery<S> {
67 fn into_query(self) -> Option<Query<S>> {
68 let mut opts = Map::new();
69 if let Some(ci) = self.case_insensitive {
70 opts.insert("case_insensitive".to_string(), Value::Bool(ci));
71 }
72 self.common.write(&mut opts);
73 Some(keyed_value_query(
74 "term", &self.path, "value", self.value, opts,
75 ))
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct PrefixQuery<S = Root> {
82 path: String,
83 value: String,
84 case_insensitive: Option<bool>,
85 rewrite: Option<String>,
86 common: Common,
87 _scope: PhantomData<fn() -> S>,
88}
89
90impl<S> PrefixQuery<S> {
91 fn new(path: &str, value: String) -> Self {
92 Self {
93 path: path.to_string(),
94 value,
95 case_insensitive: None,
96 rewrite: None,
97 common: Common::default(),
98 _scope: PhantomData,
99 }
100 }
101
102 #[must_use]
104 pub fn case_insensitive(mut self) -> Self {
105 self.case_insensitive = Some(true);
106 self
107 }
108
109 #[must_use]
111 pub fn rewrite(mut self, rewrite: impl Into<String>) -> Self {
112 self.rewrite = Some(rewrite.into());
113 self
114 }
115
116 common_opts!(common);
117}
118
119impl<S> AsQuery<S> for PrefixQuery<S> {
120 fn into_query(self) -> Option<Query<S>> {
121 let mut opts = Map::new();
122 if let Some(ci) = self.case_insensitive {
123 opts.insert("case_insensitive".to_string(), Value::Bool(ci));
124 }
125 if let Some(rewrite) = self.rewrite {
126 opts.insert("rewrite".to_string(), Value::String(rewrite));
127 }
128 self.common.write(&mut opts);
129 Some(keyed_value_query(
130 "prefix",
131 &self.path,
132 "value",
133 Value::String(self.value),
134 opts,
135 ))
136 }
137}
138
139#[derive(Debug, Clone)]
141pub struct WildcardQuery<S = Root> {
142 path: String,
143 value: String,
144 case_insensitive: Option<bool>,
145 rewrite: Option<String>,
146 common: Common,
147 _scope: PhantomData<fn() -> S>,
148}
149
150impl<S> WildcardQuery<S> {
151 fn new(path: &str, value: String) -> Self {
152 Self {
153 path: path.to_string(),
154 value,
155 case_insensitive: None,
156 rewrite: None,
157 common: Common::default(),
158 _scope: PhantomData,
159 }
160 }
161
162 #[must_use]
164 pub fn case_insensitive(mut self) -> Self {
165 self.case_insensitive = Some(true);
166 self
167 }
168
169 #[must_use]
171 pub fn rewrite(mut self, rewrite: impl Into<String>) -> Self {
172 self.rewrite = Some(rewrite.into());
173 self
174 }
175
176 common_opts!(common);
177}
178
179impl<S> AsQuery<S> for WildcardQuery<S> {
180 fn into_query(self) -> Option<Query<S>> {
181 let mut opts = Map::new();
182 if let Some(ci) = self.case_insensitive {
183 opts.insert("case_insensitive".to_string(), Value::Bool(ci));
184 }
185 if let Some(rewrite) = self.rewrite {
186 opts.insert("rewrite".to_string(), Value::String(rewrite));
187 }
188 self.common.write(&mut opts);
189 Some(keyed_value_query(
190 "wildcard",
191 &self.path,
192 "value",
193 Value::String(self.value),
194 opts,
195 ))
196 }
197}
198
199#[derive(Debug, Clone)]
202pub struct RegexpQuery<S = Root> {
203 path: String,
204 value: String,
205 case_insensitive: Option<bool>,
206 flags: Option<String>,
207 max_determinized_states: Option<u32>,
208 common: Common,
209 _scope: PhantomData<fn() -> S>,
210}
211
212impl<S> RegexpQuery<S> {
213 fn new(path: &str, value: String) -> Self {
214 Self {
215 path: path.to_string(),
216 value,
217 case_insensitive: None,
218 flags: None,
219 max_determinized_states: None,
220 common: Common::default(),
221 _scope: PhantomData,
222 }
223 }
224
225 #[must_use]
227 pub fn case_insensitive(mut self) -> Self {
228 self.case_insensitive = Some(true);
229 self
230 }
231
232 #[must_use]
234 pub fn flags(mut self, flags: impl Into<String>) -> Self {
235 self.flags = Some(flags.into());
236 self
237 }
238
239 #[must_use]
241 pub fn max_determinized_states(mut self, max: u32) -> Self {
242 self.max_determinized_states = Some(max);
243 self
244 }
245
246 common_opts!(common);
247}
248
249impl<S> AsQuery<S> for RegexpQuery<S> {
250 fn into_query(self) -> Option<Query<S>> {
251 let mut opts = Map::new();
252 if let Some(ci) = self.case_insensitive {
253 opts.insert("case_insensitive".to_string(), Value::Bool(ci));
254 }
255 if let Some(flags) = self.flags {
256 opts.insert("flags".to_string(), Value::String(flags));
257 }
258 if let Some(max) = self.max_determinized_states {
259 opts.insert("max_determinized_states".to_string(), Value::from(max));
260 }
261 self.common.write(&mut opts);
262 Some(keyed_value_query(
263 "regexp",
264 &self.path,
265 "value",
266 Value::String(self.value),
267 opts,
268 ))
269 }
270}
271
272#[derive(Debug, Clone)]
275pub struct FuzzyQuery<S = Root> {
276 path: String,
277 value: String,
278 fuzziness: Option<Value>,
279 prefix_length: Option<u32>,
280 max_expansions: Option<u32>,
281 transpositions: Option<bool>,
282 common: Common,
283 _scope: PhantomData<fn() -> S>,
284}
285
286impl<S> FuzzyQuery<S> {
287 fn new(path: &str, value: String) -> Self {
288 Self {
289 path: path.to_string(),
290 value,
291 fuzziness: None,
292 prefix_length: None,
293 max_expansions: None,
294 transpositions: None,
295 common: Common::default(),
296 _scope: PhantomData,
297 }
298 }
299
300 #[must_use]
302 pub fn fuzziness(mut self, fuzziness: Fuzziness) -> Self {
303 self.fuzziness = Some(fuzziness.to_value());
304 self
305 }
306
307 #[must_use]
309 pub fn prefix_length(mut self, prefix_length: u32) -> Self {
310 self.prefix_length = Some(prefix_length);
311 self
312 }
313
314 #[must_use]
316 pub fn max_expansions(mut self, max_expansions: u32) -> Self {
317 self.max_expansions = Some(max_expansions);
318 self
319 }
320
321 #[must_use]
323 pub fn transpositions(mut self, transpositions: bool) -> Self {
324 self.transpositions = Some(transpositions);
325 self
326 }
327
328 common_opts!(common);
329}
330
331impl<S> AsQuery<S> for FuzzyQuery<S> {
332 fn into_query(self) -> Option<Query<S>> {
333 let mut opts = Map::new();
334 if let Some(fuzziness) = self.fuzziness {
335 opts.insert("fuzziness".to_string(), fuzziness);
336 }
337 if let Some(prefix_length) = self.prefix_length {
338 opts.insert("prefix_length".to_string(), Value::from(prefix_length));
339 }
340 if let Some(max_expansions) = self.max_expansions {
341 opts.insert("max_expansions".to_string(), Value::from(max_expansions));
342 }
343 if let Some(transpositions) = self.transpositions {
344 opts.insert("transpositions".to_string(), Value::Bool(transpositions));
345 }
346 self.common.write(&mut opts);
347 Some(keyed_value_query(
348 "fuzzy",
349 &self.path,
350 "value",
351 Value::String(self.value),
352 opts,
353 ))
354 }
355}
356
357#[derive(Debug)]
364pub enum WithSubfields {}
365
366#[derive(Debug)]
371pub enum NoSubfields {}
372
373#[derive(Debug, Clone)]
377pub struct Keyword<S = Root, Sub = WithSubfields> {
378 path: String,
379 _marker: PhantomData<fn() -> (S, Sub)>,
380}
381
382impl<S, Sub> Keyword<S, Sub> {
383 fn handle(path: impl Into<String>) -> Self {
384 Self {
385 path: path.into(),
386 _marker: PhantomData,
387 }
388 }
389
390 pub fn eq(&self, value: impl FlussoValue<kind::Keyword>) -> TermQuery<S> {
394 TermQuery::new(&self.path, keyword_term(&value))
395 }
396
397 pub fn any_of(
399 &self,
400 values: impl IntoIterator<Item = impl FlussoValue<kind::Keyword>>,
401 ) -> TermsQuery<S> {
402 let array = values.into_iter().map(|v| keyword_term(&v)).collect();
403 TermsQuery::new(&self.path, array)
404 }
405
406 pub fn prefix(&self, value: impl Into<String>) -> PrefixQuery<S> {
408 PrefixQuery::new(&self.path, value.into())
409 }
410
411 pub fn wildcard(&self, pattern: impl Into<String>) -> WildcardQuery<S> {
413 WildcardQuery::new(&self.path, pattern.into())
414 }
415
416 pub fn regexp(&self, pattern: impl Into<String>) -> RegexpQuery<S> {
418 RegexpQuery::new(&self.path, pattern.into())
419 }
420
421 pub fn fuzzy(&self, value: impl Into<String>) -> FuzzyQuery<S> {
423 FuzzyQuery::new(&self.path, value.into())
424 }
425
426 pub fn exists(&self) -> Query<S> {
428 exists_q(&self.path)
429 }
430}
431
432impl<S: FlussoDocument, Sub> Sortable for Keyword<S, Sub> {
433 fn asc(&self) -> Sort {
434 Sort::field::<S>(&self.path, SortOrder::Asc)
435 }
436 fn desc(&self) -> Sort {
437 Sort::field::<S>(&self.path, SortOrder::Desc)
438 }
439}
440
441impl<S> Keyword<S, WithSubfields> {
442 pub fn at(path: impl Into<String>) -> Self {
443 Self::handle(path)
444 }
445
446 pub fn text(&self) -> Text<S, NoSubfields> {
450 Text::leaf(format!("{}.text", self.path))
451 }
452
453 pub fn keyword_lowercase(&self) -> Keyword<S, NoSubfields> {
457 Keyword::leaf(format!("{}.keyword_lowercase", self.path))
458 }
459}
460
461impl<S> Keyword<S, NoSubfields> {
462 pub fn leaf(path: impl Into<String>) -> Self {
465 Self::handle(path)
466 }
467}
468
469#[derive(Debug, Clone)]
475pub struct MatchQuery<S = Root> {
476 wrapper: &'static str,
477 path: String,
478 value: String,
479 opts: Map<String, Value>,
480 common: Common,
481 _scope: PhantomData<fn() -> S>,
482}
483
484impl<S> MatchQuery<S> {
485 fn new(wrapper: &'static str, path: &str, value: String) -> Self {
486 Self {
487 wrapper,
488 path: path.to_string(),
489 value,
490 opts: Map::new(),
491 common: Common::default(),
492 _scope: PhantomData,
493 }
494 }
495
496 fn set(mut self, key: &str, value: Value) -> Self {
497 self.opts.insert(key.to_string(), value);
498 self
499 }
500
501 #[must_use]
503 pub fn fuzziness(self, fuzziness: Fuzziness) -> Self {
504 self.set("fuzziness", fuzziness.to_value())
505 }
506
507 #[must_use]
510 pub fn operator(self, operator: Operator) -> Self {
511 self.set("operator", Value::String(operator.as_str().to_string()))
512 }
513
514 #[must_use]
517 pub fn minimum_should_match(self, value: impl Into<MinimumShouldMatch>) -> Self {
518 self.set("minimum_should_match", value.into().to_value())
519 }
520
521 #[must_use]
523 pub fn prefix_length(self, prefix_length: u32) -> Self {
524 self.set("prefix_length", Value::from(prefix_length))
525 }
526
527 #[must_use]
529 pub fn max_expansions(self, max_expansions: u32) -> Self {
530 self.set("max_expansions", Value::from(max_expansions))
531 }
532
533 #[must_use]
535 pub fn analyzer(self, analyzer: impl Into<String>) -> Self {
536 self.set("analyzer", Value::String(analyzer.into()))
537 }
538
539 #[must_use]
541 pub fn slop(self, slop: u32) -> Self {
542 self.set("slop", Value::from(slop))
543 }
544
545 #[must_use]
548 pub fn zero_terms_query(self, value: ZeroTermsQuery) -> Self {
549 self.set(
550 "zero_terms_query",
551 Value::String(value.as_str().to_string()),
552 )
553 }
554
555 #[must_use]
557 pub fn lenient(self, lenient: bool) -> Self {
558 self.set("lenient", Value::Bool(lenient))
559 }
560
561 common_opts!(common);
562}
563
564impl<S> AsQuery<S> for MatchQuery<S> {
565 fn into_query(self) -> Option<Query<S>> {
566 let mut opts = self.opts;
567 self.common.write(&mut opts);
568 Some(keyed_value_query(
569 self.wrapper,
570 &self.path,
571 "query",
572 Value::String(self.value),
573 opts,
574 ))
575 }
576}
577
578#[derive(Debug, Clone)]
582pub struct Text<S = Root, Sub = WithSubfields> {
583 path: String,
584 boost: Option<f32>,
585 _marker: PhantomData<fn() -> (S, Sub)>,
586}
587
588impl<S, Sub> Text<S, Sub> {
589 fn handle(path: impl Into<String>) -> Self {
590 Self {
591 path: path.into(),
592 boost: None,
593 _marker: PhantomData,
594 }
595 }
596
597 #[must_use]
601 pub fn boosted(mut self, weight: f32) -> Self {
602 self.boost = Some(weight);
603 self
604 }
605
606 pub(crate) fn field_spec(&self) -> String {
609 match self.boost {
610 Some(weight) => format!("{}^{weight}", self.path),
611 None => self.path.clone(),
612 }
613 }
614
615 pub fn matches(&self, value: impl Into<String>) -> MatchQuery<S> {
617 MatchQuery::new("match", &self.path, value.into())
618 }
619
620 pub fn match_phrase(&self, value: impl Into<String>) -> MatchQuery<S> {
622 MatchQuery::new("match_phrase", &self.path, value.into())
623 }
624
625 pub fn match_phrase_prefix(&self, value: impl Into<String>) -> MatchQuery<S> {
627 MatchQuery::new("match_phrase_prefix", &self.path, value.into())
628 }
629
630 pub fn match_bool_prefix(&self, value: impl Into<String>) -> MatchQuery<S> {
633 MatchQuery::new("match_bool_prefix", &self.path, value.into())
634 }
635
636 pub fn matches_fuzzy(&self, value: impl Into<String>) -> MatchQuery<S> {
639 self.matches(value).fuzziness(Fuzziness::Auto)
640 }
641
642 pub fn exists(&self) -> Query<S> {
644 exists_q(&self.path)
645 }
646}
647
648impl<S> Text<S, WithSubfields> {
649 pub fn at(path: impl Into<String>) -> Self {
650 Self::handle(path)
651 }
652
653 pub fn any_of(
658 &self,
659 values: impl IntoIterator<Item = impl FlussoValue<kind::Keyword>>,
660 ) -> TermsQuery<S> {
661 self.keyword().any_of(values)
662 }
663
664 pub fn keyword(&self) -> Keyword<S, NoSubfields> {
669 Keyword::leaf(format!("{}.keyword", self.path))
670 }
671
672 pub fn keyword_lowercase(&self) -> Keyword<S, NoSubfields> {
676 Keyword::leaf(format!("{}.keyword_lowercase", self.path))
677 }
678}
679
680impl<S: FlussoDocument> Sortable for Text<S, WithSubfields> {
684 fn asc(&self) -> Sort {
685 self.keyword_lowercase().asc()
686 }
687 fn desc(&self) -> Sort {
688 self.keyword_lowercase().desc()
689 }
690}
691
692impl<S> Text<S, NoSubfields> {
693 pub fn leaf(path: impl Into<String>) -> Self {
696 Self::handle(path)
697 }
698}
699
700pub fn multi_match<S, Sub>(
704 query: impl Into<String>,
705 fields: impl IntoIterator<Item = Text<S, Sub>>,
706) -> MultiMatchQuery<S> {
707 MultiMatchQuery {
708 query: query.into(),
709 fields: fields.into_iter().map(|f| f.field_spec()).collect(),
710 opts: Map::new(),
711 common: Common::default(),
712 _scope: PhantomData,
713 }
714}
715
716#[derive(Debug, Clone)]
720pub struct MultiMatchQuery<S = Root> {
721 query: String,
722 fields: Vec<String>,
723 opts: Map<String, Value>,
724 common: Common,
725 _scope: PhantomData<fn() -> S>,
726}
727
728impl<S> MultiMatchQuery<S> {
729 fn set(mut self, key: &str, value: Value) -> Self {
730 self.opts.insert(key.to_string(), value);
731 self
732 }
733
734 #[must_use]
736 pub fn match_type(self, match_type: MultiMatchType) -> Self {
737 self.set("type", Value::String(match_type.as_str().to_string()))
738 }
739
740 #[must_use]
742 pub fn operator(self, operator: Operator) -> Self {
743 self.set("operator", Value::String(operator.as_str().to_string()))
744 }
745
746 #[must_use]
748 pub fn fuzziness(self, fuzziness: Fuzziness) -> Self {
749 self.set("fuzziness", fuzziness.to_value())
750 }
751
752 #[must_use]
754 pub fn tie_breaker(self, tie_breaker: f32) -> Self {
755 self.set("tie_breaker", Value::from(tie_breaker))
756 }
757
758 #[must_use]
761 pub fn minimum_should_match(self, value: impl Into<MinimumShouldMatch>) -> Self {
762 self.set("minimum_should_match", value.into().to_value())
763 }
764
765 common_opts!(common);
766}
767
768impl<S> AsQuery<S> for MultiMatchQuery<S> {
769 fn into_query(self) -> Option<Query<S>> {
770 let mut body = self.opts;
771 body.insert("query".to_string(), Value::String(self.query));
772 body.insert(
773 "fields".to_string(),
774 Value::Array(self.fields.into_iter().map(Value::String).collect()),
775 );
776 self.common.write(&mut body);
777 Some(wrap("multi_match", body))
778 }
779}