tiny_firestore_odm/
collection.rs

1use crate::dynamic_firestore_client::SharedFirestoreClient;
2use crate::identifiers::{CollectionName, DocumentName, QualifyDocumentName};
3use crate::list_response::ListResponse;
4use firestore_serde::firestore::{
5    precondition::ConditionType, CreateDocumentRequest, DeleteDocumentRequest, GetDocumentRequest,
6    Precondition, UpdateDocumentRequest,
7};
8use serde::{de::DeserializeOwned, Serialize};
9use std::marker::PhantomData;
10use tonic::Code;
11
12/// Represents a collection of documents in a Firestore database.
13///
14/// Documents in Firestore do not have types, but on the Rust end, we associate each collection
15/// with a type. Documents are serialized into and deserialized from this type when writing/reading
16/// to Firestore.
17pub struct Collection<T>
18where
19    T: Serialize + DeserializeOwned + 'static,
20{
21    db: SharedFirestoreClient,
22    name: CollectionName,
23    _ph: PhantomData<T>,
24}
25
26impl<T> Collection<T>
27where
28    T: Serialize + DeserializeOwned + Unpin,
29{
30    /// Construct a top-level collection (i.e. a collection whose parent is the root.)
31    pub fn new(db: SharedFirestoreClient, name: CollectionName) -> Self {
32        Collection {
33            db,
34            name,
35            _ph: PhantomData::default(),
36        }
37    }
38
39    /// Returns a stream of all of the documents in a collection (as [NamedDocument]s).
40    pub fn list(&self) -> ListResponse<T> {
41        ListResponse::new(self.name.clone(), self.db.clone())
42    }
43
44    pub fn name(&self) -> CollectionName {
45        self.name.clone()
46    }
47
48    pub fn subcollection<S>(&self, name: &str, collection: &str) -> Collection<S>
49    where
50        S: Serialize + DeserializeOwned + Unpin,
51    {
52        Collection {
53            db: self.db.clone(),
54            name: self.name.subcollection(name, collection),
55            _ph: PhantomData::default(),
56        }
57    }
58
59    /// Create the given document in this collection with the given key.
60    /// Returns an error if the key is already in use (if you intend to replace the
61    /// document in that case, use `upsert` instead.)
62    pub async fn create_with_key(
63        &self,
64        ob: &T,
65        key: impl QualifyDocumentName,
66    ) -> anyhow::Result<()> {
67        let mut document = firestore_serde::to_document(ob)?;
68
69        document.name = key.qualify(&self.name)?.name();
70        self.db
71            .lock()
72            .await
73            .update_document(UpdateDocumentRequest {
74                document: Some(document),
75                current_document: Some(Precondition {
76                    condition_type: Some(ConditionType::Exists(false)),
77                }),
78                ..UpdateDocumentRequest::default()
79            })
80            .await?;
81        Ok(())
82    }
83
84    /// Create the given document in this collection with the given key.
85    /// Returns `true` if the document was created, or `false` if it already existed.
86    pub async fn try_create(&self, ob: &T, key: impl QualifyDocumentName) -> anyhow::Result<bool> {
87        let mut document = firestore_serde::to_document(ob)?;
88        document.name = key.qualify(&self.name)?.name();
89        let result = self
90            .db
91            .lock()
92            .await
93            .update_document(UpdateDocumentRequest {
94                document: Some(document),
95                current_document: Some(Precondition {
96                    condition_type: Some(ConditionType::Exists(false)),
97                }),
98                ..UpdateDocumentRequest::default()
99            })
100            .await;
101
102        match result {
103            Ok(_) => Ok(true),
104            Err(e) if e.code() == Code::AlreadyExists => Ok(false),
105            Err(e) => Err(e.into()),
106        }
107    }
108
109    /// Add the given document to this collection, assigning it a new key at random.
110    pub async fn create(&self, ob: &T) -> anyhow::Result<DocumentName> {
111        let document = firestore_serde::to_document(ob)?;
112        let result = self
113            .db
114            .lock()
115            .await
116            .create_document(CreateDocumentRequest {
117                document: Some(document),
118                collection_id: self.name.leaf_name(),
119                parent: self.name.parent().name(),
120                ..CreateDocumentRequest::default()
121            })
122            .await?
123            .into_inner();
124        Ok(DocumentName::parse(&result.name)?)
125    }
126
127    /// Overwrite the given document to this collection, creating a new document if one does not exist.
128    pub async fn upsert(&self, ob: &T, key: impl QualifyDocumentName) -> anyhow::Result<()> {
129        let mut document = firestore_serde::to_document(ob)?;
130        document.name = key.qualify(&self.name)?.name();
131        self.db
132            .lock()
133            .await
134            .update_document(UpdateDocumentRequest {
135                document: Some(document),
136                ..UpdateDocumentRequest::default()
137            })
138            .await?;
139        Ok(())
140    }
141
142    /// Update the given document, returning an error if it does not exist.
143    pub async fn update(&self, ob: &T, key: impl QualifyDocumentName) -> anyhow::Result<()> {
144        let mut document = firestore_serde::to_document(ob)?;
145        document.name = key.qualify(&self.name)?.name();
146        self.db
147            .lock()
148            .await
149            .update_document(UpdateDocumentRequest {
150                document: Some(document),
151                current_document: Some(Precondition {
152                    condition_type: Some(ConditionType::Exists(true)),
153                }),
154                ..UpdateDocumentRequest::default()
155            })
156            .await?;
157        Ok(())
158    }
159
160    /// Get the document with a given key.
161    pub async fn get(&self, key: impl QualifyDocumentName) -> anyhow::Result<T> {
162        let document = self
163            .db
164            .lock()
165            .await
166            .get_document(GetDocumentRequest {
167                name: key.qualify(&self.name)?.name(),
168                ..GetDocumentRequest::default()
169            })
170            .await?
171            .into_inner();
172
173        firestore_serde::from_document(document)
174            .map_err(|_| anyhow::anyhow!("Error deserializing."))
175    }
176
177    /// Delete the document with a given key.
178    pub async fn delete(&self, key: impl QualifyDocumentName) -> anyhow::Result<()> {
179        let name = key.qualify(&self.name)?.name();
180        self.db
181            .lock()
182            .await
183            .delete_document(DeleteDocumentRequest {
184                name,
185                current_document: Some(Precondition {
186                    condition_type: Some(ConditionType::Exists(true)),
187                }),
188            })
189            .await?;
190        Ok(())
191    }
192}