use bson::Bson;
use std::collections::HashSet;
pub struct Projection {
keep_by_default: bool,
exceptions: HashSet<String>,
}
impl Projection {
pub fn new_keep_all() -> Projection {
Projection {
keep_by_default: true,
exceptions: HashSet::new(),
}
}
pub fn new_drop_id() -> Projection {
let mut exceptions = HashSet::new();
exceptions.insert("_id".to_string());
Projection {
keep_by_default: true,
exceptions: exceptions,
}
}
pub fn parse_bson<B: Into<Bson>>(projection: B) -> Result<Projection, String> {
let projection: &Bson = &projection.into();
if let Bson::Array(projection) = projection {
let mut result = Projection {
keep_by_default: false,
exceptions: HashSet::new(),
};
result.exceptions.insert("_id".to_string());
for field_name in projection {
match field_name {
Bson::String(string) => result.exceptions.insert(string.clone()),
_ => {
return Err("The projection array contains a non-string element".to_string())
}
};
}
return Ok(result);
}
if let Bson::Document(projection) = projection {
let mut inclusive = HashSet::new();
let mut exclusive = HashSet::new();
for entry in projection {
let field_name = entry.0;
let include = match entry.1 {
Bson::Boolean(value) => *value,
_ => {
return Err(
"The values in projection objects need to be either `True` or `False`"
.to_string(),
);
}
};
if include {
inclusive.insert(field_name.to_string());
} else {
exclusive.insert(field_name.to_string());
}
}
let explicit_inclusive_id = inclusive.contains("_id");
let explicit_exclusive_id = exclusive.contains("_id");
assert!(!(explicit_inclusive_id && explicit_exclusive_id));
let id_is_explicit = explicit_inclusive_id || explicit_exclusive_id;
let have_inclusive = inclusive.len() - explicit_inclusive_id as usize > 0;
let have_exclusive = exclusive.len() - explicit_exclusive_id as usize > 0;
if have_inclusive && have_exclusive {
return Err(
"Projections cannot have inclusive and exclusive items, except for _id"
.to_string(),
);
}
return if have_inclusive {
if !id_is_explicit {
inclusive.insert("_id".to_string());
}
Ok(Projection {
keep_by_default: false,
exceptions: inclusive,
})
} else {
Ok(Projection {
keep_by_default: true,
exceptions: exclusive,
})
};
}
Err("Projections need to be either Arrays of field names, or Objects.".to_string())
}
pub(crate) fn is_keep_all(&self) -> bool {
self.keep_by_default && self.exceptions.is_empty()
}
fn keep_field(&self, field_name: &str) -> bool {
let is_exception = self.exceptions.contains(field_name);
self.keep_by_default ^ is_exception
}
#[allow(dead_code)]
pub(crate) fn apply_to_clone(&self, document: &bson::Document) -> bson::Document {
let mut result = document.clone();
for (name, value) in document.iter() {
if self.keep_field(name) {
result.insert(name, value);
}
}
result
}
pub(crate) fn apply_in_place(&self, document: &mut bson::Document) {
let keys: Vec<String> = document.keys().cloned().collect();
for name in keys {
if !self.keep_field(&name) {
document.remove(name);
}
}
}
}