use crate::expr::Expr;
use crate::field_witnesses::{FieldName, HasField};
use crate::path::Path;
use bson;
pub struct BasicProjectionBuilder<T> {
prefix: Vec<String>,
clauses: Vec<(String, bson::Bson)>,
_marker: std::marker::PhantomData<T>,
}
impl<T> Default for BasicProjectionBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> BasicProjectionBuilder<T> {
pub fn new() -> Self {
Self {
prefix: Vec::new(),
clauses: Vec::new(),
_marker: std::marker::PhantomData,
}
}
fn field_path<F: FieldName>(&self) -> String {
if self.prefix.is_empty() {
F::field_name().to_string()
} else {
format!("{}.{}", self.prefix.join("."), F::field_name())
}
}
fn project_field<F: FieldName>(&mut self, includes: bool) -> &mut Self
where
T: HasField<F>,
{
let path = self.field_path::<F>();
let flag = if includes { 1 } else { 0 };
self.clauses.push((path, flag.into()));
self
}
pub fn project_expr<F: FieldName, V>(&mut self, expr: Expr<T, V>) -> &mut Self
where
T: HasField<F>,
{
let path = self.field_path::<F>();
self.clauses.push((path, expr.into_bson()));
self
}
pub fn includes<F: FieldName>(&mut self) -> &mut Self
where
T: HasField<F>,
{
self.project_field::<F>(true)
}
pub fn excludes<F: FieldName>(&mut self) -> &mut Self
where
T: HasField<F>,
{
self.project_field::<F>(false)
}
pub fn with_lookup<F: FieldName, L, G: FieldName, U: HasField<G>, N>(
&mut self,
lookup: L,
f: N,
) -> &mut Self
where
T: HasField<F>,
L: FnOnce(&Path<F, T, T>) -> Path<G, U, T>,
N: FnOnce(&mut BasicProjectionBuilder<U>),
{
let base_field: Path<F, T, T> = Path {
prefix: self.prefix.clone(),
_marker: std::marker::PhantomData,
};
let resolved_field = lookup(&base_field);
let mut nested_builder = BasicProjectionBuilder::<U> {
prefix: resolved_field.prefix.clone(),
clauses: vec![],
_marker: std::marker::PhantomData,
};
f(&mut nested_builder);
self.clauses.extend(nested_builder.clauses);
self
}
pub fn with_field<F: FieldName, N>(&mut self, f: N) -> &mut Self
where
T: HasField<F>,
N: FnOnce(&mut BasicProjectionBuilder<T>),
{
self.with_lookup::<F, _, F, T, _>(
|path| Path {
prefix: path.prefix.clone(),
_marker: std::marker::PhantomData,
},
f,
)
}
pub fn build(&mut self) -> bson::Document {
let mut doc = bson::Document::new();
for (field, value) in &self.clauses {
doc.insert(field.clone(), value.clone());
}
doc
}
}
pub fn empty<T>() -> BasicProjectionBuilder<T> {
BasicProjectionBuilder::new()
}
pub trait ProjectionBuilder<T>: Sized {
fn project(&mut self, path: String, expr: bson::Bson) -> &mut Self;
}
impl<T> ProjectionBuilder<T> for BasicProjectionBuilder<T> {
fn project(&mut self, path: String, expr: bson::Bson) -> &mut Self {
self.clauses.push((path, expr));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field_witnesses::FieldName;
struct Name;
impl FieldName for Name {
fn field_name() -> &'static str {
"name"
}
}
struct Id;
impl FieldName for Id {
fn field_name() -> &'static str {
"id"
}
}
struct Age;
impl FieldName for Age {
fn field_name() -> &'static str {
"age"
}
}
struct Email;
impl FieldName for Email {
fn field_name() -> &'static str {
"email"
}
}
struct TestStruct;
#[test]
fn test_field_path_empty_prefix() {
let builder = empty::<TestStruct>();
let path = builder.field_path::<Name>();
assert_eq!(path, "name");
let path = builder.field_path::<Id>();
assert_eq!(path, "id");
let path = builder.field_path::<Age>();
assert_eq!(path, "age");
let path = builder.field_path::<Email>();
assert_eq!(path, "email");
}
#[test]
fn test_field_path_single_prefix() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["user".to_string()];
let path = builder.field_path::<Name>();
assert_eq!(path, "user.name");
let path = builder.field_path::<Id>();
assert_eq!(path, "user.id");
}
#[test]
fn test_field_path_multiple_prefixes() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["profile".to_string(), "address".to_string()];
let path = builder.field_path::<Name>();
assert_eq!(path, "profile.address.name");
let path = builder.field_path::<Age>();
assert_eq!(path, "profile.address.age");
}
#[test]
fn test_field_path_deeply_nested_prefix() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec![
"collection".to_string(),
"documents".to_string(),
"user_data".to_string(),
"profile".to_string(),
];
let path = builder.field_path::<Name>();
assert_eq!(path, "collection.documents.user_data.profile.name");
}
#[test]
fn test_field_path_consistency_across_multiple_calls() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["test".to_string()];
let path1 = builder.field_path::<Name>();
let path2 = builder.field_path::<Name>();
let path3 = builder.field_path::<Name>();
assert_eq!(path1, path2);
assert_eq!(path2, path3);
assert_eq!(path1, "test.name");
}
#[test]
fn test_field_path_special_characters_in_prefix() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["test-prefix".to_string(), "sub_field".to_string()];
let path = builder.field_path::<Email>();
assert_eq!(path, "test-prefix.sub_field.email");
}
#[test]
fn test_field_path_empty_string_prefix() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["".to_string()];
let path = builder.field_path::<Name>();
assert_eq!(path, ".name");
}
#[test]
fn test_field_path_mixed_prefix_types() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec![
"root".to_string(),
"nested_object".to_string(),
"array_element".to_string(),
];
let path = builder.field_path::<Id>();
assert_eq!(path, "root.nested_object.array_element.id");
let path = builder.field_path::<Email>();
assert_eq!(path, "root.nested_object.array_element.email");
}
#[test]
fn test_field_path_with_numeric_string_prefix() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["users".to_string(), "0".to_string()];
let path = builder.field_path::<Name>();
assert_eq!(path, "users.0.name");
}
#[test]
fn test_field_path_different_field_types() {
let mut builder = empty::<TestStruct>();
builder.prefix = vec!["common".to_string()];
let name_path = builder.field_path::<Name>();
let id_path = builder.field_path::<Id>();
let age_path = builder.field_path::<Age>();
let email_path = builder.field_path::<Email>();
assert_eq!(name_path, "common.name");
assert_eq!(id_path, "common.id");
assert_eq!(age_path, "common.age");
assert_eq!(email_path, "common.email");
let paths = [name_path, id_path, age_path, email_path];
let unique_paths: std::collections::HashSet<_> = paths.iter().collect();
assert_eq!(unique_paths.len(), 4);
}
}