use crate::{
FirestoreBatch, FirestoreBatchWriter, FirestoreDeleteSupport, FirestoreResult,
FirestoreTransactionOps, FirestoreWritePrecondition,
};
#[cfg(feature = "admin")]
use crate::{
FirestoreBulkDeleteParams, FirestoreBulkDeleteResult, FirestoreBulkDeleteSupport,
FirestoreCollectionId, FirestoreOperationWaitOptions,
};
#[derive(Clone, Debug)]
pub struct FirestoreDeleteInitialBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
db: &'a D,
}
impl<'a, D> FirestoreDeleteInitialBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
#[inline]
pub(crate) fn new(db: &'a D) -> Self {
Self { db }
}
#[inline]
pub fn from<S: AsRef<str>>(self, collection_id: S) -> FirestoreDeleteDocIdBuilder<'a, D> {
FirestoreDeleteDocIdBuilder::new(self.db, collection_id.as_ref().to_string())
}
}
#[cfg(feature = "admin")]
impl<'a, D> FirestoreDeleteInitialBuilder<'a, D>
where
D: FirestoreDeleteSupport + FirestoreBulkDeleteSupport + Clone + Send + Sync + 'static,
{
#[inline]
pub fn bulk(self) -> FirestoreBulkDeleteBuilder<'a, D> {
FirestoreBulkDeleteBuilder::new(self.db)
}
}
#[derive(Clone, Debug)]
pub struct FirestoreDeleteDocIdBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
db: &'a D,
collection_id: String,
parent: Option<String>,
precondition: Option<FirestoreWritePrecondition>,
}
impl<'a, D> FirestoreDeleteDocIdBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
#[inline]
pub(crate) fn new(db: &'a D, collection_id: String) -> Self {
Self {
db,
collection_id,
parent: None,
precondition: None,
}
}
#[inline]
pub fn parent<S>(self, parent: S) -> Self
where
S: AsRef<str>,
{
Self {
parent: Some(parent.as_ref().to_string()),
..self
}
}
#[inline]
pub fn precondition(self, precondition: FirestoreWritePrecondition) -> Self {
Self {
precondition: Some(precondition),
..self
}
}
#[inline]
pub fn document_id<S>(self, document_id: S) -> FirestoreDeleteExecuteBuilder<'a, D>
where
S: AsRef<str> + Send,
{
FirestoreDeleteExecuteBuilder::new(
self.db,
self.collection_id.to_string(),
document_id.as_ref().to_string(),
self.parent,
self.precondition,
)
}
}
#[derive(Clone, Debug)]
pub struct FirestoreDeleteExecuteBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
db: &'a D,
collection_id: String,
document_id: String,
parent: Option<String>,
precondition: Option<FirestoreWritePrecondition>,
}
impl<'a, D> FirestoreDeleteExecuteBuilder<'a, D>
where
D: FirestoreDeleteSupport,
{
#[inline]
pub(crate) fn new(
db: &'a D,
collection_id: String,
document_id: String,
parent: Option<String>,
precondition: Option<FirestoreWritePrecondition>,
) -> Self {
Self {
db,
collection_id,
document_id,
parent,
precondition,
}
}
#[inline]
pub fn parent<S>(self, parent: S) -> Self
where
S: AsRef<str>,
{
Self {
parent: Some(parent.as_ref().to_string()),
..self
}
}
#[inline]
pub fn precondition(self, precondition: FirestoreWritePrecondition) -> Self {
Self {
precondition: Some(precondition),
..self
}
}
pub async fn execute(self) -> FirestoreResult<()> {
if let Some(parent) = self.parent {
self.db
.delete_by_id_at(
parent.as_str(),
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
.await
} else {
self.db
.delete_by_id(
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
.await
}
}
#[inline]
pub fn add_to_transaction<'t, TO>(self, transaction: &'t mut TO) -> FirestoreResult<&'t mut TO>
where
TO: FirestoreTransactionOps,
{
if let Some(parent) = self.parent {
transaction.delete_by_id_at(
parent.as_str(),
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
} else {
transaction.delete_by_id(
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
}
}
#[inline]
pub fn add_to_batch<'t, W>(
self,
batch: &'a mut FirestoreBatch<'t, W>,
) -> FirestoreResult<&'a mut FirestoreBatch<'t, W>>
where
W: FirestoreBatchWriter,
{
if let Some(parent) = self.parent {
batch.delete_by_id_at(
parent.as_str(),
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
} else {
batch.delete_by_id(
self.collection_id.as_str(),
self.document_id,
self.precondition,
)
}
}
}
#[cfg(feature = "admin")]
#[derive(Clone, Debug)]
pub struct FirestoreBulkDeleteBuilder<'a, D>
where
D: FirestoreBulkDeleteSupport,
{
db: &'a D,
collection_groups: Vec<String>,
wait: Option<FirestoreOperationWaitOptions>,
}
#[cfg(feature = "admin")]
impl<'a, D> FirestoreBulkDeleteBuilder<'a, D>
where
D: FirestoreBulkDeleteSupport + Clone + Send + Sync + 'static,
{
#[inline]
pub(crate) fn new(db: &'a D) -> Self {
Self {
db,
collection_groups: Vec::new(),
wait: None,
}
}
#[inline]
pub fn collection_groups<I>(self, collection_groups: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
Self {
collection_groups: collection_groups
.into_iter()
.map(|group| group.as_ref().to_string())
.collect(),
..self
}
}
#[inline]
pub fn wait_until_done(self, timeout: std::time::Duration) -> Self {
self.wait_until_done_with_options(FirestoreOperationWaitOptions::new(timeout))
}
#[inline]
pub fn wait_until_done_with_options(self, options: FirestoreOperationWaitOptions) -> Self {
Self {
wait: Some(options),
..self
}
}
pub async fn execute(self) -> FirestoreResult<FirestoreBulkDeleteResult> {
let collection_groups = self
.collection_groups
.into_iter()
.map(FirestoreCollectionId::new)
.collect::<FirestoreResult<Vec<_>>>()?;
let params = FirestoreBulkDeleteParams::new(collection_groups).opt_wait(self.wait);
crate::validate_bulk_delete_params(¶ms)?;
self.db.bulk_delete_documents(params).await
}
}
#[cfg(all(test, feature = "admin"))]
mod tests {
use super::FirestoreDeleteInitialBuilder;
use crate::fluent_api::tests::mockdb::MockBulkDeleteDatabase;
use crate::{FirestoreCollectionId, FirestoreOperationWaitOptions};
use std::time::Duration;
#[tokio::test]
async fn bulk_chain_produces_the_expected_params() {
let mock = MockBulkDeleteDatabase::default();
FirestoreDeleteInitialBuilder::new(&mock)
.bulk()
.collection_groups(["users", "orders"])
.wait_until_done(Duration::from_secs(600))
.execute()
.await
.expect("capturing mock always succeeds");
let params = mock.captured().expect("bulk_delete_documents was called");
assert_eq!(
params.collection_groups,
vec![
FirestoreCollectionId::new("users").unwrap(),
FirestoreCollectionId::new("orders").unwrap(),
]
);
assert_eq!(
params.wait,
Some(FirestoreOperationWaitOptions::new(Duration::from_secs(600)))
);
}
#[tokio::test]
async fn without_wait_defaults_to_no_wait() {
let mock = MockBulkDeleteDatabase::default();
FirestoreDeleteInitialBuilder::new(&mock)
.bulk()
.collection_groups(["users"])
.execute()
.await
.unwrap();
let params = mock.captured().unwrap();
assert!(params.wait.is_none());
}
#[tokio::test]
async fn empty_collection_groups_is_rejected_before_the_call() {
let mock = MockBulkDeleteDatabase::default();
let err = FirestoreDeleteInitialBuilder::new(&mock)
.bulk()
.execute()
.await
.unwrap_err();
assert!(err.to_string().contains("collection_groups"));
assert!(mock.captured().is_none());
}
#[tokio::test]
async fn duplicate_collection_group_is_rejected_before_the_call() {
let mock = MockBulkDeleteDatabase::default();
let err = FirestoreDeleteInitialBuilder::new(&mock)
.bulk()
.collection_groups(["users", "users"])
.execute()
.await
.unwrap_err();
assert!(err.to_string().contains("more than once"));
assert!(mock.captured().is_none());
}
#[tokio::test]
async fn invalid_collection_group_is_rejected_before_the_call() {
let mock = MockBulkDeleteDatabase::default();
let err = FirestoreDeleteInitialBuilder::new(&mock)
.bulk()
.collection_groups(["a/b"])
.execute()
.await
.unwrap_err();
assert!(err.to_string().contains("collection_id"));
assert!(mock.captured().is_none());
}
}