use std::marker::PhantomData;
use serde_json::{Map, Value};
use super::{Common, Date, Keyword, Number, Text, common_opts, exists_q, wrap};
use crate::query::{AsQuery, Query, Root};
macro_rules! map_handle {
($(#[$meta:meta])* $Name:ident => $Leaf:ident, $kind:literal) => {
$(#[$meta])*
#[derive(Debug, Clone)]
pub struct $Name<S = Root> {
path: String,
_scope: PhantomData<fn() -> S>,
}
impl<S> $Name<S> {
pub fn at(path: impl Into<String>) -> Self {
Self {
path: path.into(),
_scope: PhantomData,
}
}
#[doc = concat!("A specific runtime key → a fully-typed `", $kind, "` leaf handle, \
queried like any other ", $kind, " field.")]
pub fn key(&self, key: impl AsRef<str>) -> $Leaf<S> {
$Leaf::at(format!("{}.{}", self.path, key.as_ref()))
}
pub fn has_key(&self, key: impl AsRef<str>) -> Query<S> {
exists_q(&format!("{}.{}", self.path, key.as_ref()))
}
pub fn exists(&self) -> Query<S> {
exists_q(&self.path)
}
}
};
}
map_handle!(
TextMap => Text, "text"
);
map_handle!(
KeywordMap => Keyword, "keyword"
);
map_handle!(
DateMap => Date, "date"
);
#[derive(Debug, Clone)]
pub struct NumberMap<T, S = Root> {
path: String,
_marker: PhantomData<fn() -> (T, S)>,
}
impl<T, S> NumberMap<T, S> {
pub fn at(path: impl Into<String>) -> Self {
Self {
path: path.into(),
_marker: PhantomData,
}
}
pub fn has_key(&self, key: impl AsRef<str>) -> Query<S> {
exists_q(&format!("{}.{}", self.path, key.as_ref()))
}
pub fn exists(&self) -> Query<S> {
exists_q(&self.path)
}
}
impl<T, S> NumberMap<T, S>
where
T: Into<serde_json::Value> + Copy,
{
pub fn key(&self, key: impl AsRef<str>) -> Number<T, S> {
Number::at(format!("{}.{}", self.path, key.as_ref()))
}
}
impl<S> TextMap<S> {
pub fn search(&self, query: impl Into<String>) -> MapSearch<S> {
MapSearch::new(&self.path, query.into())
}
}
#[derive(Debug, Clone)]
pub struct MapSearch<S = Root> {
path: String,
query: String,
preferred: Vec<String>,
include_all: bool,
opts: Map<String, Value>,
common: Common,
_scope: PhantomData<fn() -> S>,
}
impl<S> MapSearch<S> {
fn new(path: &str, query: String) -> Self {
Self {
path: path.to_string(),
query,
preferred: Vec::new(),
include_all: true,
opts: Map::new(),
common: Common::default(),
_scope: PhantomData,
}
}
#[must_use]
pub fn prefer(mut self, key: impl AsRef<str>, weight: f32) -> Self {
self.preferred
.push(format!("{}.{}^{weight}", self.path, key.as_ref()));
self
}
#[must_use]
pub fn only_preferred(mut self) -> Self {
self.include_all = false;
self
}
fn set(mut self, key: &str, value: Value) -> Self {
self.opts.insert(key.to_string(), value);
self
}
#[must_use]
pub fn operator(self, operator: impl Into<String>) -> Self {
self.set("operator", Value::String(operator.into()))
}
#[must_use]
pub fn fuzziness(self, fuzziness: impl Into<String>) -> Self {
self.set("fuzziness", Value::String(fuzziness.into()))
}
#[must_use]
pub fn minimum_should_match(self, value: impl Into<String>) -> Self {
self.set("minimum_should_match", Value::String(value.into()))
}
common_opts!(common);
}
impl<S> AsQuery<S> for MapSearch<S> {
fn into_query(self) -> Option<Query<S>> {
let mut fields: Vec<Value> = self.preferred.iter().cloned().map(Value::String).collect();
if self.include_all {
fields.push(Value::String(format!("{}.*", self.path)));
}
let mut body = self.opts;
body.insert("query".to_string(), Value::String(self.query));
body.insert("fields".to_string(), Value::Array(fields));
body.entry("type")
.or_insert_with(|| Value::String("best_fields".to_string()));
self.common.write(&mut body);
Some(wrap("multi_match", body))
}
}