use std::marker::PhantomData;
use serde_json::{Map, Value};
#[derive(Debug, Clone, Copy)]
pub struct Root;
#[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>,
}
#[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);
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)
}
impl<S> Query<S> {
pub(crate) fn leaf(value: Value) -> Self {
Query {
inner: Inner::Leaf(value),
_scope: PhantomData,
}
}
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 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 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>>;
}
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)
}
}