use std::marker::PhantomData;
use serde_json::{Map, Value};
use crate::handles::MinimumShouldMatch;
#[derive(Debug, Clone, Copy)]
pub struct Root;
impl crate::FlussoDocument for Root {
const PATH: &'static [crate::Segment] = &[];
}
#[derive(Debug, Clone)]
pub struct Query<S = Root> {
inner: Inner,
_scope: PhantomData<fn() -> S>,
}
#[derive(Debug, Clone)]
enum Inner {
Leaf(Value),
Bool(BoolInner),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct BoolInner {
must: Vec<Inner>,
filter: Vec<Inner>,
should: Vec<Inner>,
must_not: Vec<Inner>,
minimum_should_match: Option<Value>,
boost: Option<f32>,
}
#[derive(Debug, Clone, Copy)]
enum Clause {
Must,
Filter,
Should,
MustNot,
}
impl BoolInner {
pub(crate) fn is_empty(&self) -> bool {
self.must.is_empty()
&& self.filter.is_empty()
&& self.should.is_empty()
&& self.must_not.is_empty()
}
fn push(&mut self, clause: Clause, inner: Inner) {
match clause {
Clause::Must => self.must.push(inner),
Clause::Filter => self.filter.push(inner),
Clause::Should => self.should.push(inner),
Clause::MustNot => self.must_not.push(inner),
}
}
fn is_pure(&self, clause: Clause) -> bool {
match clause {
Clause::Must => {
self.filter.is_empty() && self.should.is_empty() && self.must_not.is_empty()
}
Clause::Filter => {
self.must.is_empty() && self.should.is_empty() && self.must_not.is_empty()
}
Clause::Should => {
self.must.is_empty() && self.filter.is_empty() && self.must_not.is_empty()
}
Clause::MustNot => {
self.must.is_empty() && self.filter.is_empty() && self.should.is_empty()
}
}
}
pub(crate) fn to_value(&self) -> Value {
let mut body = Map::new();
insert_clause(&mut body, "must", &self.must);
insert_clause(&mut body, "filter", &self.filter);
insert_clause(&mut body, "should", &self.should);
insert_clause(&mut body, "must_not", &self.must_not);
if let Some(msm) = &self.minimum_should_match {
body.insert("minimum_should_match".to_string(), msm.clone());
}
if let Some(boost) = self.boost {
body.insert("boost".to_string(), Value::from(boost));
}
let mut outer = Map::new();
outer.insert("bool".to_string(), Value::Object(body));
Value::Object(outer)
}
}
fn insert_clause(target: &mut Map<String, Value>, key: &str, clauses: &[Inner]) {
if clauses.is_empty() {
return;
}
let array = clauses.iter().map(Inner::to_value).collect();
target.insert(key.to_string(), Value::Array(array));
}
impl Inner {
fn to_value(&self) -> Value {
match self {
Inner::Leaf(value) => value.clone(),
Inner::Bool(bool_inner) => bool_inner.to_value(),
}
}
}
fn combine(a: Inner, b: Inner, clause: Clause) -> Inner {
if let Inner::Bool(mut bool_inner) = a {
if bool_inner.is_pure(clause) {
bool_inner.push(clause, b);
return Inner::Bool(bool_inner);
}
let mut combined = BoolInner::default();
combined.push(clause, Inner::Bool(bool_inner));
combined.push(clause, b);
return Inner::Bool(combined);
}
let mut combined = BoolInner::default();
combined.push(clause, a);
combined.push(clause, b);
Inner::Bool(combined)
}
fn combine_opt<S>(a: Option<Query<S>>, b: Option<Query<S>>, clause: Clause) -> Query<S> {
match (a, b) {
(Some(a), Some(b)) => Query::wrap(combine(a.inner, b.inner, clause)),
(Some(only), None) | (None, Some(only)) => only,
(None, None) => Query::match_all(),
}
}
impl<S> Query<S> {
pub(crate) fn leaf(value: Value) -> Self {
Query {
inner: Inner::Leaf(value),
_scope: PhantomData,
}
}
pub(crate) fn match_all() -> Self {
Query::leaf(crate::handles::match_all_value())
}
fn wrap(inner: Inner) -> Self {
Query {
inner,
_scope: PhantomData,
}
}
#[must_use]
pub fn and(self, other: impl AsQuery<S>) -> Query<S> {
match other.into_query() {
Some(other) => Query::wrap(combine(self.inner, other.inner, Clause::Must)),
None => self,
}
}
#[must_use]
pub fn or(self, other: impl AsQuery<S>) -> Query<S> {
match other.into_query() {
Some(other) => Query::wrap(combine(self.inner, other.inner, Clause::Should)),
None => self,
}
}
#[must_use]
#[allow(clippy::should_implement_trait)]
pub fn not(self) -> Query<S> {
Query::wrap(Inner::Bool(BoolInner {
must_not: vec![self.inner],
..BoolInner::default()
}))
}
#[must_use]
pub fn boost(mut self, boost: f32) -> Query<S> {
self.inner = match self.inner {
Inner::Bool(mut bool_inner) => {
bool_inner.boost = Some(boost);
Inner::Bool(bool_inner)
}
leaf => Inner::Bool(BoolInner {
must: vec![leaf],
boost: Some(boost),
..BoolInner::default()
}),
};
self
}
#[must_use]
pub fn min_should_match(mut self, value: impl Into<MinimumShouldMatch>) -> Query<S> {
let value = value.into().to_value();
self.inner = match self.inner {
Inner::Bool(mut bool_inner) => {
bool_inner.minimum_should_match = Some(value);
Inner::Bool(bool_inner)
}
leaf => Inner::Bool(BoolInner {
should: vec![leaf],
minimum_should_match: Some(value),
..BoolInner::default()
}),
};
self
}
#[must_use]
pub fn to_value(&self) -> Value {
self.inner.to_value()
}
pub(crate) fn into_inner(self) -> InnerClause {
InnerClause(self.inner)
}
}
pub(crate) struct InnerClause(Inner);
#[derive(Debug, Clone, Default)]
pub(crate) struct BoolBuilder {
bool_inner: BoolInner,
}
impl BoolBuilder {
pub(crate) fn push_must(&mut self, clause: InnerClause) {
self.bool_inner.push(Clause::Must, clause.0);
}
pub(crate) fn push_filter(&mut self, clause: InnerClause) {
self.bool_inner.push(Clause::Filter, clause.0);
}
pub(crate) fn push_should(&mut self, clause: InnerClause) {
self.bool_inner.push(Clause::Should, clause.0);
}
pub(crate) fn push_must_not(&mut self, clause: InnerClause) {
self.bool_inner.push(Clause::MustNot, clause.0);
}
pub(crate) fn set_min_should_match(&mut self, value: Value) {
self.bool_inner.minimum_should_match = Some(value);
}
pub(crate) fn is_empty(&self) -> bool {
self.bool_inner.is_empty()
}
pub(crate) fn to_value(&self) -> Value {
self.bool_inner.to_value()
}
}
pub trait AsQuery<S> {
fn into_query(self) -> Option<Query<S>>;
#[must_use]
fn and(self, other: impl AsQuery<S>) -> Query<S>
where
Self: Sized,
{
combine_opt(self.into_query(), other.into_query(), Clause::Must)
}
#[must_use]
fn or(self, other: impl AsQuery<S>) -> Query<S>
where
Self: Sized,
{
combine_opt(self.into_query(), other.into_query(), Clause::Should)
}
#[must_use]
#[allow(clippy::should_implement_trait)]
fn not(self) -> Query<S>
where
Self: Sized,
{
self.into_query().map_or_else(Query::match_all, Query::not)
}
#[must_use]
fn to_value(&self) -> Value
where
Self: Sized + Clone,
{
self.clone()
.into_query()
.map_or_else(crate::handles::match_all_value, |q| q.to_value())
}
}
impl<S> AsQuery<S> for Query<S> {
fn into_query(self) -> Option<Query<S>> {
Some(self)
}
}
impl<S, T: AsQuery<S>> AsQuery<S> for Option<T> {
fn into_query(self) -> Option<Query<S>> {
self.and_then(AsQuery::into_query)
}
}