use std::marker::PhantomData;
use serde_json::{Map, Value};
use super::{
Common, FlussoValue, Fuzziness, MinimumShouldMatch, MultiMatchType, Operator, Sort, SortOrder,
Sortable, TermsQuery, ZeroTermsQuery, common_opts, exists_q, keyed_value_query, kind, wrap,
};
use crate::FlussoDocument;
use crate::query::{AsQuery, Query, Root};
fn keyword_term(value: &impl serde::Serialize) -> Value {
match serde_json::to_value(value) {
Ok(Value::String(string)) => Value::String(string),
Ok(other) => Value::String(other.to_string()),
Err(_) => Value::String(String::new()),
}
}
#[derive(Debug, Clone)]
pub struct TermQuery<S = Root> {
path: String,
value: Value,
case_insensitive: Option<bool>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> TermQuery<S> {
fn new(path: &str, value: Value) -> Self {
Self {
path: path.to_string(),
value,
case_insensitive: None,
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn case_insensitive(mut self) -> Self {
self.case_insensitive = Some(true);
self
}
common_opts!(common);
}
impl<S> AsQuery<S> for TermQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = Map::new();
if let Some(ci) = self.case_insensitive {
opts.insert("case_insensitive".to_string(), Value::Bool(ci));
}
self.common.write(&mut opts);
Some(keyed_value_query(
"term", &self.path, "value", self.value, opts,
))
}
}
#[derive(Debug, Clone)]
pub struct PrefixQuery<S = Root> {
path: String,
value: String,
case_insensitive: Option<bool>,
rewrite: Option<String>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> PrefixQuery<S> {
fn new(path: &str, value: String) -> Self {
Self {
path: path.to_string(),
value,
case_insensitive: None,
rewrite: None,
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn case_insensitive(mut self) -> Self {
self.case_insensitive = Some(true);
self
}
#[must_use]
pub fn rewrite(mut self, rewrite: impl Into<String>) -> Self {
self.rewrite = Some(rewrite.into());
self
}
common_opts!(common);
}
impl<S> AsQuery<S> for PrefixQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = Map::new();
if let Some(ci) = self.case_insensitive {
opts.insert("case_insensitive".to_string(), Value::Bool(ci));
}
if let Some(rewrite) = self.rewrite {
opts.insert("rewrite".to_string(), Value::String(rewrite));
}
self.common.write(&mut opts);
Some(keyed_value_query(
"prefix",
&self.path,
"value",
Value::String(self.value),
opts,
))
}
}
#[derive(Debug, Clone)]
pub struct WildcardQuery<S = Root> {
path: String,
value: String,
case_insensitive: Option<bool>,
rewrite: Option<String>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> WildcardQuery<S> {
fn new(path: &str, value: String) -> Self {
Self {
path: path.to_string(),
value,
case_insensitive: None,
rewrite: None,
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn case_insensitive(mut self) -> Self {
self.case_insensitive = Some(true);
self
}
#[must_use]
pub fn rewrite(mut self, rewrite: impl Into<String>) -> Self {
self.rewrite = Some(rewrite.into());
self
}
common_opts!(common);
}
impl<S> AsQuery<S> for WildcardQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = Map::new();
if let Some(ci) = self.case_insensitive {
opts.insert("case_insensitive".to_string(), Value::Bool(ci));
}
if let Some(rewrite) = self.rewrite {
opts.insert("rewrite".to_string(), Value::String(rewrite));
}
self.common.write(&mut opts);
Some(keyed_value_query(
"wildcard",
&self.path,
"value",
Value::String(self.value),
opts,
))
}
}
#[derive(Debug, Clone)]
pub struct RegexpQuery<S = Root> {
path: String,
value: String,
case_insensitive: Option<bool>,
flags: Option<String>,
max_determinized_states: Option<u32>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> RegexpQuery<S> {
fn new(path: &str, value: String) -> Self {
Self {
path: path.to_string(),
value,
case_insensitive: None,
flags: None,
max_determinized_states: None,
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn case_insensitive(mut self) -> Self {
self.case_insensitive = Some(true);
self
}
#[must_use]
pub fn flags(mut self, flags: impl Into<String>) -> Self {
self.flags = Some(flags.into());
self
}
#[must_use]
pub fn max_determinized_states(mut self, max: u32) -> Self {
self.max_determinized_states = Some(max);
self
}
common_opts!(common);
}
impl<S> AsQuery<S> for RegexpQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = Map::new();
if let Some(ci) = self.case_insensitive {
opts.insert("case_insensitive".to_string(), Value::Bool(ci));
}
if let Some(flags) = self.flags {
opts.insert("flags".to_string(), Value::String(flags));
}
if let Some(max) = self.max_determinized_states {
opts.insert("max_determinized_states".to_string(), Value::from(max));
}
self.common.write(&mut opts);
Some(keyed_value_query(
"regexp",
&self.path,
"value",
Value::String(self.value),
opts,
))
}
}
#[derive(Debug, Clone)]
pub struct FuzzyQuery<S = Root> {
path: String,
value: String,
fuzziness: Option<Value>,
prefix_length: Option<u32>,
max_expansions: Option<u32>,
transpositions: Option<bool>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> FuzzyQuery<S> {
fn new(path: &str, value: String) -> Self {
Self {
path: path.to_string(),
value,
fuzziness: None,
prefix_length: None,
max_expansions: None,
transpositions: None,
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn fuzziness(mut self, fuzziness: Fuzziness) -> Self {
self.fuzziness = Some(fuzziness.to_value());
self
}
#[must_use]
pub fn prefix_length(mut self, prefix_length: u32) -> Self {
self.prefix_length = Some(prefix_length);
self
}
#[must_use]
pub fn max_expansions(mut self, max_expansions: u32) -> Self {
self.max_expansions = Some(max_expansions);
self
}
#[must_use]
pub fn transpositions(mut self, transpositions: bool) -> Self {
self.transpositions = Some(transpositions);
self
}
common_opts!(common);
}
impl<S> AsQuery<S> for FuzzyQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = Map::new();
if let Some(fuzziness) = self.fuzziness {
opts.insert("fuzziness".to_string(), fuzziness);
}
if let Some(prefix_length) = self.prefix_length {
opts.insert("prefix_length".to_string(), Value::from(prefix_length));
}
if let Some(max_expansions) = self.max_expansions {
opts.insert("max_expansions".to_string(), Value::from(max_expansions));
}
if let Some(transpositions) = self.transpositions {
opts.insert("transpositions".to_string(), Value::Bool(transpositions));
}
self.common.write(&mut opts);
Some(keyed_value_query(
"fuzzy",
&self.path,
"value",
Value::String(self.value),
opts,
))
}
}
#[derive(Debug)]
pub enum WithSubfields {}
#[derive(Debug)]
pub enum NoSubfields {}
#[derive(Debug)]
pub enum MapKey {}
#[derive(Debug, Clone)]
pub struct Keyword<S = Root, Sub = WithSubfields> {
path: String,
_marker: PhantomData<fn() -> (S, Sub)>,
}
impl<S, Sub> Keyword<S, Sub> {
fn handle(path: impl Into<String>) -> Self {
Self {
path: path.into(),
_marker: PhantomData,
}
}
pub fn eq(&self, value: impl FlussoValue<kind::Keyword>) -> TermQuery<S> {
TermQuery::new(&self.path, keyword_term(&value))
}
pub fn any_of(
&self,
values: impl IntoIterator<Item = impl FlussoValue<kind::Keyword>>,
) -> TermsQuery<S> {
let array = values.into_iter().map(|v| keyword_term(&v)).collect();
TermsQuery::new(&self.path, array)
}
pub fn prefix(&self, value: impl Into<String>) -> PrefixQuery<S> {
PrefixQuery::new(&self.path, value.into())
}
pub fn wildcard(&self, pattern: impl Into<String>) -> WildcardQuery<S> {
WildcardQuery::new(&self.path, pattern.into())
}
pub fn regexp(&self, pattern: impl Into<String>) -> RegexpQuery<S> {
RegexpQuery::new(&self.path, pattern.into())
}
pub fn fuzzy(&self, value: impl Into<String>) -> FuzzyQuery<S> {
FuzzyQuery::new(&self.path, value.into())
}
pub fn exists(&self) -> Query<S> {
exists_q(&self.path)
}
}
macro_rules! keyword_sortable {
($sub:ty) => {
impl<S: FlussoDocument> Sortable for Keyword<S, $sub> {
fn asc(&self) -> Sort {
Sort::field::<S>(&self.path, SortOrder::Asc)
}
fn desc(&self) -> Sort {
Sort::field::<S>(&self.path, SortOrder::Desc)
}
}
};
}
keyword_sortable!(WithSubfields);
keyword_sortable!(NoSubfields);
impl<S> Keyword<S, MapKey> {
pub(crate) fn map_key(path: impl Into<String>) -> Self {
Self::handle(path)
}
}
impl<S> Keyword<S, WithSubfields> {
pub fn at(path: impl Into<String>) -> Self {
Self::handle(path)
}
pub fn text(&self) -> Text<S, NoSubfields> {
Text::leaf(format!("{}.text", self.path))
}
pub fn keyword_lowercase(&self) -> Keyword<S, NoSubfields> {
Keyword::leaf(format!("{}.keyword_lowercase", self.path))
}
}
impl<S> Keyword<S, NoSubfields> {
pub fn leaf(path: impl Into<String>) -> Self {
Self::handle(path)
}
}
#[derive(Debug, Clone)]
pub struct MatchQuery<S = Root> {
wrapper: &'static str,
path: String,
value: String,
opts: Map<String, Value>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> MatchQuery<S> {
fn new(wrapper: &'static str, path: &str, value: String) -> Self {
Self {
wrapper,
path: path.to_string(),
value,
opts: Map::new(),
common: Common::default(),
_scope: PhantomData,
}
}
fn set(mut self, key: &str, value: Value) -> Self {
self.opts.insert(key.to_string(), value);
self
}
#[must_use]
pub fn fuzziness(self, fuzziness: Fuzziness) -> Self {
self.set("fuzziness", fuzziness.to_value())
}
#[must_use]
pub fn operator(self, operator: Operator) -> Self {
self.set("operator", Value::String(operator.as_str().to_string()))
}
#[must_use]
pub fn minimum_should_match(self, value: impl Into<MinimumShouldMatch>) -> Self {
self.set("minimum_should_match", value.into().to_value())
}
#[must_use]
pub fn prefix_length(self, prefix_length: u32) -> Self {
self.set("prefix_length", Value::from(prefix_length))
}
#[must_use]
pub fn max_expansions(self, max_expansions: u32) -> Self {
self.set("max_expansions", Value::from(max_expansions))
}
#[must_use]
pub fn analyzer(self, analyzer: impl Into<String>) -> Self {
self.set("analyzer", Value::String(analyzer.into()))
}
#[must_use]
pub fn slop(self, slop: u32) -> Self {
self.set("slop", Value::from(slop))
}
#[must_use]
pub fn zero_terms_query(self, value: ZeroTermsQuery) -> Self {
self.set(
"zero_terms_query",
Value::String(value.as_str().to_string()),
)
}
#[must_use]
pub fn lenient(self, lenient: bool) -> Self {
self.set("lenient", Value::Bool(lenient))
}
common_opts!(common);
}
impl<S> AsQuery<S> for MatchQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut opts = self.opts;
self.common.write(&mut opts);
Some(keyed_value_query(
self.wrapper,
&self.path,
"query",
Value::String(self.value),
opts,
))
}
}
#[derive(Debug, Clone)]
pub struct Text<S = Root, Sub = WithSubfields> {
path: String,
boost: Option<f32>,
_marker: PhantomData<fn() -> (S, Sub)>,
}
impl<S, Sub> Text<S, Sub> {
fn handle(path: impl Into<String>) -> Self {
Self {
path: path.into(),
boost: None,
_marker: PhantomData,
}
}
#[must_use]
pub fn boosted(mut self, weight: f32) -> Self {
self.boost = Some(weight);
self
}
pub(crate) fn field_spec(&self) -> String {
match self.boost {
Some(weight) => format!("{}^{weight}", self.path),
None => self.path.clone(),
}
}
pub fn matches(&self, value: impl Into<String>) -> MatchQuery<S> {
MatchQuery::new("match", &self.path, value.into())
}
pub fn match_phrase(&self, value: impl Into<String>) -> MatchQuery<S> {
MatchQuery::new("match_phrase", &self.path, value.into())
}
pub fn match_phrase_prefix(&self, value: impl Into<String>) -> MatchQuery<S> {
MatchQuery::new("match_phrase_prefix", &self.path, value.into())
}
pub fn match_bool_prefix(&self, value: impl Into<String>) -> MatchQuery<S> {
MatchQuery::new("match_bool_prefix", &self.path, value.into())
}
pub fn matches_fuzzy(&self, value: impl Into<String>) -> MatchQuery<S> {
self.matches(value).fuzziness(Fuzziness::Auto)
}
pub fn exists(&self) -> Query<S> {
exists_q(&self.path)
}
}
impl<S> Text<S, WithSubfields> {
pub fn at(path: impl Into<String>) -> Self {
Self::handle(path)
}
pub fn any_of(
&self,
values: impl IntoIterator<Item = impl FlussoValue<kind::Keyword>>,
) -> TermsQuery<S> {
self.keyword().any_of(values)
}
pub fn keyword(&self) -> Keyword<S, NoSubfields> {
Keyword::leaf(format!("{}.keyword", self.path))
}
pub fn keyword_lowercase(&self) -> Keyword<S, NoSubfields> {
Keyword::leaf(format!("{}.keyword_lowercase", self.path))
}
}
impl<S: FlussoDocument> Sortable for Text<S, WithSubfields> {
fn asc(&self) -> Sort {
self.keyword_lowercase().asc()
}
fn desc(&self) -> Sort {
self.keyword_lowercase().desc()
}
}
impl<S> Text<S, NoSubfields> {
pub fn leaf(path: impl Into<String>) -> Self {
Self::handle(path)
}
}
impl<S> Text<S, MapKey> {
pub(crate) fn map_key(path: impl Into<String>) -> Self {
Self::handle(path)
}
}
pub fn multi_match<S, Sub>(
query: impl Into<String>,
fields: impl IntoIterator<Item = Text<S, Sub>>,
) -> MultiMatchQuery<S> {
MultiMatchQuery {
query: query.into(),
fields: fields.into_iter().map(|f| f.field_spec()).collect(),
opts: Map::new(),
common: Common::default(),
_scope: PhantomData,
}
}
#[derive(Debug, Clone)]
pub struct MultiMatchQuery<S = Root> {
query: String,
fields: Vec<String>,
opts: Map<String, Value>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> MultiMatchQuery<S> {
fn set(mut self, key: &str, value: Value) -> Self {
self.opts.insert(key.to_string(), value);
self
}
#[must_use]
pub fn match_type(self, match_type: MultiMatchType) -> Self {
self.set("type", Value::String(match_type.as_str().to_string()))
}
#[must_use]
pub fn operator(self, operator: Operator) -> Self {
self.set("operator", Value::String(operator.as_str().to_string()))
}
#[must_use]
pub fn fuzziness(self, fuzziness: Fuzziness) -> Self {
self.set("fuzziness", fuzziness.to_value())
}
#[must_use]
pub fn tie_breaker(self, tie_breaker: f32) -> Self {
self.set("tie_breaker", Value::from(tie_breaker))
}
#[must_use]
pub fn minimum_should_match(self, value: impl Into<MinimumShouldMatch>) -> Self {
self.set("minimum_should_match", value.into().to_value())
}
common_opts!(common);
}
impl<S> AsQuery<S> for MultiMatchQuery<S> {
fn into_query(self) -> Option<Query<S>> {
let mut body = self.opts;
body.insert("query".to_string(), Value::String(self.query));
body.insert(
"fields".to_string(),
Value::Array(self.fields.into_iter().map(Value::String).collect()),
);
self.common.write(&mut body);
Some(wrap("multi_match", body))
}
}