use crate::errors::FirestoreError;
use crate::{
FirestoreCollectionId, FirestoreInstant, FirestoreOperationWaitOptions, FirestoreResult,
};
use rsb_derive::Builder;
use std::collections::HashSet;
use std::fmt::{self, Display, Formatter};
use std::time::Duration;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct FirestoreBulkDeleteProgress {
pub estimated_work: i64,
pub completed_work: i64,
}
impl Display for FirestoreBulkDeleteProgress {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.estimated_work == 0 {
write!(f, "{}", self.completed_work)
} else {
write!(f, "{}/{}", self.completed_work, self.estimated_work)
}
}
}
#[derive(Debug, PartialEq, Clone, Builder)]
pub struct FirestoreBulkDeleteParams {
pub collection_groups: Vec<FirestoreCollectionId>,
#[default = "None"]
pub wait: Option<FirestoreOperationWaitOptions>,
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct FirestoreBulkDeleteResult {
pub operation_name: String,
pub collection_groups: Vec<FirestoreCollectionId>,
pub snapshot_time: Option<FirestoreInstant>,
pub documents: Option<FirestoreBulkDeleteProgress>,
pub bytes: Option<FirestoreBulkDeleteProgress>,
pub elapsed: Duration,
}
impl Display for FirestoreBulkDeleteResult {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let groups: Vec<&str> = self
.collection_groups
.iter()
.map(FirestoreCollectionId::as_str)
.collect();
write!(
f,
"Bulk delete {} ({}), elapsed {:?}",
groups.join(", "),
self.operation_name,
self.elapsed,
)?;
match (&self.documents, &self.bytes) {
(None, None) => write!(f, ", progress not reported")?,
(documents, bytes) => {
if let Some(documents) = documents {
write!(f, ", documents {documents}")?;
}
if let Some(bytes) = bytes {
write!(f, ", bytes {bytes}")?;
}
}
}
if let Some(snapshot_time) = &self.snapshot_time {
write!(f, ", snapshot_time {snapshot_time}")?;
}
Ok(())
}
}
pub(crate) fn validate_bulk_delete_params(
params: &FirestoreBulkDeleteParams,
) -> FirestoreResult<()> {
if params.collection_groups.is_empty() {
return Err(FirestoreError::invalid_parameters(
"collection_groups",
"bulk delete needs at least one collection group; an empty list means the whole \
database in Firestore's API, and this crate never sends that",
));
}
let mut seen = HashSet::new();
for group in ¶ms.collection_groups {
if !seen.insert(group.as_str()) {
return Err(FirestoreError::invalid_parameters(
"collection_groups",
format!(
"collection group {} is declared more than once",
group.as_str()
),
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn group(id: &str) -> FirestoreCollectionId {
FirestoreCollectionId::new(id).unwrap()
}
#[test]
fn an_estimate_of_zero_shows_only_the_completed_work() {
let progress = FirestoreBulkDeleteProgress {
estimated_work: 0,
completed_work: 5,
};
assert_eq!(progress.to_string(), "5");
}
#[test]
fn a_known_estimate_shows_completed_over_estimated_work() {
let progress = FirestoreBulkDeleteProgress {
estimated_work: 10,
completed_work: 5,
};
assert_eq!(progress.to_string(), "5/10");
}
#[test]
fn empty_collection_groups_is_rejected() {
let params = FirestoreBulkDeleteParams::new(vec![]);
let err = validate_bulk_delete_params(¶ms).unwrap_err();
assert!(err.to_string().contains("collection_groups"));
}
#[test]
fn duplicate_collection_group_is_rejected() {
let params = FirestoreBulkDeleteParams::new(vec![group("users"), group("users")]);
let err = validate_bulk_delete_params(¶ms).unwrap_err();
assert!(err.to_string().contains("more than once"));
}
#[test]
fn distinct_collection_groups_are_accepted() {
let params = FirestoreBulkDeleteParams::new(vec![group("users"), group("orders")]);
assert!(validate_bulk_delete_params(¶ms).is_ok());
}
}