use std::collections::HashSet;
use std::fmt::Display;
use std::hash::Hash;
pub trait QuerySetMember: Hash + Eq + Display + Sized + Clone + 'static {
type Iter: ExactSizeIterator<Item = Self>;
fn all() -> Self::Iter;
}
#[derive(Debug, Clone)]
pub struct QuerySet<Q: QuerySetMember> {
values: Option<HashSet<Q>>,
field_name: String,
}
impl<Q: QuerySetMember> QuerySet<Q> {
pub fn new(field_name: String) -> Self {
QuerySet {
values: None,
field_name,
}
}
pub fn include(&mut self, value: Q) -> &mut Self {
self.values.get_or_insert_with(HashSet::new).insert(value);
self
}
pub fn exclude(&mut self, value: &Q) -> &mut Self {
self.values
.get_or_insert_with(|| Q::all().collect::<HashSet<_>>())
.remove(value);
self
}
pub fn query(&self) -> Option<(String, String)> {
if let Some(values) = &self.values {
match values.len() {
0 => Some((format!("{}__in", self.field_name), String::new())),
1 => Some((
self.field_name.clone(),
values.iter().next().unwrap().to_string(),
)),
_ if values.len() == Q::all().len() => None,
_ => Some((
format!("{}__in", self.field_name),
values
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(","),
)),
}
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::{Formatter, Result};
use strum::{EnumIter, IntoEnumIterator};
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter)]
enum Test1 {
State1,
State2,
State3,
}
impl Display for Test1 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Test1::State1 => write!(f, "State1"),
Test1::State2 => write!(f, "State2"),
Test1::State3 => write!(f, "State3"),
}
}
}
impl QuerySetMember for Test1 {
type Iter = Test1Iter;
fn all() -> Self::Iter {
Self::iter()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter)]
enum Test2 {
State1,
State2,
State3,
State4,
State5,
}
impl Display for Test2 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Test2::State1 => write!(f, "State1"),
Test2::State2 => write!(f, "State2"),
Test2::State3 => write!(f, "State3"),
Test2::State4 => write!(f, "State4"),
Test2::State5 => write!(f, "State5"),
}
}
}
impl QuerySetMember for Test2 {
type Iter = Test2Iter;
fn all() -> Self::Iter {
Self::iter()
}
}
#[test]
fn test_query_set() {
let pair = QuerySet::<Test1>::new(String::from("test1")).query();
assert!(pair.is_none());
let pair = QuerySet::new(String::from("test2"))
.include(Test2::State4)
.query();
assert!(pair.is_some());
let (field, value) = pair.unwrap();
assert_eq!(field, "test2");
assert_eq!(value, "State4");
let pair = QuerySet::new(String::from("test1"))
.include(Test1::State1)
.include(Test1::State2)
.query();
assert!(pair.is_some());
let (field, value) = pair.unwrap();
assert_eq!(field, "test1__in");
assert!(value == "State1,State2" || value == "State2,State1");
let pair = QuerySet::new(String::from("test1"))
.include(Test1::State1)
.include(Test1::State2)
.include(Test1::State3)
.query();
assert!(pair.is_none());
let pair = QuerySet::new(String::from("test1"))
.exclude(&Test1::State1)
.query();
assert!(pair.is_some());
let (field, value) = pair.unwrap();
assert_eq!(field, "test1__in");
assert!(value == "State2,State3" || value == "State3,State2");
let pair = QuerySet::new(String::from("test2"))
.exclude(&Test2::State1)
.exclude(&Test2::State2)
.exclude(&Test2::State4)
.exclude(&Test2::State5)
.query();
let (field, value) = pair.unwrap();
assert_eq!(field, "test2");
assert_eq!(value, "State3");
let pair = QuerySet::new(String::from("test1"))
.exclude(&Test1::State1)
.exclude(&Test1::State2)
.exclude(&Test1::State3)
.query();
assert!(pair.is_some());
let (field, value) = pair.unwrap();
assert_eq!(field, "test1__in");
assert_eq!(value, "");
let pair = QuerySet::new(String::from("test1"))
.include(Test1::State1)
.exclude(&Test1::State1)
.query();
assert!(pair.is_some());
let (field, value) = pair.unwrap();
assert_eq!(field, "test1__in");
assert_eq!(value, "");
let pair = QuerySet::new(String::from("test2"))
.exclude(&Test2::State5)
.include(Test2::State5)
.query();
assert!(pair.is_none());
}
}