firestore_path/
database_name.rs

1use std::str::FromStr;
2
3use crate::{
4    error::ErrorKind, CollectionName, CollectionPath, DatabaseId, DocumentName, DocumentPath,
5    Error, ProjectId, RootDocumentName,
6};
7
8/// A database name.
9///
10/// # Format
11///
12/// `projects/{project_id}/databases/{database_id}`
13///
14/// # Examples
15///
16/// ```rust
17/// # fn main() -> anyhow::Result<()> {
18/// use firestore_path::{DatabaseId,DatabaseName,ProjectId,RootDocumentName};
19/// use std::str::FromStr;
20///
21/// let database_name = DatabaseName::from_project_id("my-project")?;
22/// assert_eq!(database_name.to_string(), "projects/my-project/databases/(default)");
23///
24/// assert_eq!(
25///     database_name.root_document_name(),
26///     RootDocumentName::from_str("projects/my-project/databases/(default)/documents")?
27/// );
28///
29/// assert_eq!(
30///     database_name.database_id(),
31///     &DatabaseId::from_str("(default)")?
32/// );
33/// assert_eq!(
34///     database_name.project_id(),
35///     &ProjectId::from_str("my-project")?
36/// );
37///
38/// assert_eq!(
39///     DatabaseId::from(database_name.clone()),
40///     DatabaseId::from_str("(default)")?
41/// );
42/// assert_eq!(
43///     ProjectId::from(database_name.clone()),
44///     ProjectId::from_str("my-project")?
45/// );
46///
47/// let database_name = DatabaseName::from_str("projects/my-project/databases/my-database")?;
48/// assert_eq!(database_name.to_string(), "projects/my-project/databases/my-database");
49///
50/// let project_id = ProjectId::from_str("my-project")?;
51/// let database_id = DatabaseId::from_str("my-database")?;
52/// let database_name = DatabaseName::new(project_id, database_id);
53/// assert_eq!(database_name.to_string(), "projects/my-project/databases/my-database");
54///
55/// let project_id = ProjectId::from_str("my-project")?;
56/// let database_id = DatabaseId::default();
57/// let database_name = DatabaseName::new(project_id, database_id);
58/// assert_eq!(database_name.to_string(), "projects/my-project/databases/(default)");
59/// #     Ok(())
60/// # }
61/// ```
62///
63#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
64pub struct DatabaseName {
65    database_id: DatabaseId,
66    project_id: ProjectId,
67}
68
69impl DatabaseName {
70    /// Creates a new `DatabaseName`.
71    ///
72    /// # Examples
73    ///
74    /// ```rust
75    /// # fn main() -> anyhow::Result<()> {
76    /// use firestore_path::{DatabaseId,DatabaseName,ProjectId};
77    /// use std::str::FromStr;
78    ///
79    /// let project_id = ProjectId::from_str("my-project")?;
80    /// let database_id = DatabaseId::from_str("my-database")?;
81    /// let database_name = DatabaseName::new(project_id, database_id);
82    /// assert_eq!(database_name.to_string(), "projects/my-project/databases/my-database");
83    /// #     Ok(())
84    /// # }
85    /// ```
86    ///
87    pub fn new(project_id: ProjectId, database_id: DatabaseId) -> Self {
88        Self {
89            database_id,
90            project_id,
91        }
92    }
93
94    /// Creates a new `DatabaseName` with the provided `project_id` and default `database_id`.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// # fn test_database_name_from_project_id() -> Result<(), firestore_path::Error> {
100    /// use firestore_path::{DatabaseName, ProjectId};
101    /// use std::str::FromStr;
102    ///
103    /// let database_name = DatabaseName::from_project_id("my-project")?;
104    /// assert_eq!(database_name.to_string(), "projects/my-project/databases/(default)");
105    /// let project_id = ProjectId::from_str("my-project")?;
106    /// let database_name = DatabaseName::from_project_id(project_id)?;
107    /// assert_eq!(database_name.to_string(), "projects/my-project/databases/(default)");
108    /// #     Ok(())
109    /// # }
110    /// ```
111    ///
112    pub fn from_project_id<P>(project_id: P) -> Result<Self, Error>
113    where
114        P: TryInto<ProjectId>,
115        P::Error: std::fmt::Display,
116    {
117        Ok(Self {
118            database_id: DatabaseId::default(),
119            project_id: project_id
120                .try_into()
121                .map_err(|e| Error::from(ErrorKind::ProjectIdConversion(e.to_string())))?,
122        })
123    }
124
125    /// Creates a new `CollectionName` from this `DatabaseName` and `collection_path`.
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// # fn main() -> anyhow::Result<()> {
131    /// use firestore_path::{CollectionId,CollectionName,CollectionPath,DatabaseName};
132    /// use std::str::FromStr;
133    ///
134    /// let database_name = DatabaseName::from_str(
135    ///     "projects/my-project/databases/my-database"
136    /// )?;
137    /// assert_eq!(
138    ///     database_name.collection("chatrooms")?,
139    ///     CollectionName::from_str(
140    ///         "projects/my-project/databases/my-database/documents/chatrooms"
141    ///     )?
142    /// );
143    /// assert_eq!(
144    ///     database_name.collection("chatrooms/chatroom1/messages")?,
145    ///     CollectionName::from_str(
146    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
147    ///     )?
148    /// );
149    /// assert_eq!(
150    ///     database_name.collection(CollectionId::from_str("chatrooms")?)?,
151    ///     CollectionName::from_str(
152    ///         "projects/my-project/databases/my-database/documents/chatrooms"
153    ///     )?
154    /// );
155    /// assert_eq!(
156    ///     database_name.collection(CollectionPath::from_str("chatrooms/chatroom1/messages")?)?,
157    ///     CollectionName::from_str(
158    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
159    ///     )?
160    /// );
161    ///
162    /// #     Ok(())
163    /// # }
164    /// ```
165    ///
166    pub fn collection<E, T>(&self, collection_path: T) -> Result<CollectionName, Error>
167    where
168        E: std::fmt::Display,
169        T: TryInto<CollectionPath, Error = E>,
170    {
171        self.clone().into_collection(collection_path)
172    }
173
174    /// Creates a new `CollectionName` by consuming the `DatabaseName` with the provided `collection_path`.
175    ///
176    /// # Examples
177    ///
178    /// ```rust
179    /// # fn main() -> anyhow::Result<()> {
180    /// use firestore_path::{CollectionId,CollectionName,CollectionPath,DatabaseName};
181    /// use std::str::FromStr;
182    ///
183    /// let database_name = DatabaseName::from_str(
184    ///     "projects/my-project/databases/my-database"
185    /// )?;
186    /// assert_eq!(
187    ///     database_name.clone().into_collection("chatrooms")?,
188    ///     CollectionName::from_str(
189    ///         "projects/my-project/databases/my-database/documents/chatrooms"
190    ///     )?
191    /// );
192    /// assert_eq!(
193    ///     database_name.clone().into_collection("chatrooms/chatroom1/messages")?,
194    ///     CollectionName::from_str(
195    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
196    ///     )?
197    /// );
198    /// assert_eq!(
199    ///     database_name.clone().into_collection(CollectionId::from_str("chatrooms")?)?,
200    ///     CollectionName::from_str(
201    ///         "projects/my-project/databases/my-database/documents/chatrooms"
202    ///     )?
203    /// );
204    /// assert_eq!(
205    ///     database_name.into_collection(CollectionPath::from_str("chatrooms/chatroom1/messages")?)?,
206    ///     CollectionName::from_str(
207    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
208    ///     )?
209    /// );
210    ///
211    /// #     Ok(())
212    /// # }
213    /// ```
214    ///
215    pub fn into_collection<E, T>(self, collection_path: T) -> Result<CollectionName, Error>
216    where
217        E: std::fmt::Display,
218        T: TryInto<CollectionPath, Error = E>,
219    {
220        let collection_path = collection_path
221            .try_into()
222            .map_err(|e| Error::from(ErrorKind::CollectionPathConversion(e.to_string())))?;
223        Ok(CollectionName::new(self, collection_path))
224    }
225
226    /// Returns the `DatabaseId` of this `DatabaseName`.
227    ///
228    /// # Examples
229    ///
230    /// ```rust
231    /// # fn main() -> anyhow::Result<()> {
232    /// use firestore_path::{DatabaseId,DatabaseName};
233    /// use std::str::FromStr;
234    ///
235    /// let database_name = DatabaseName::from_str("projects/my-project/databases/my-database")?;
236    /// assert_eq!(
237    ///     database_name.database_id(),
238    ///     &DatabaseId::from_str("my-database")?
239    /// );
240    /// #     Ok(())
241    /// # }
242    /// ```
243    pub fn database_id(&self) -> &DatabaseId {
244        &self.database_id
245    }
246
247    /// Creates a new `DocumentName` from this `DatabaseName` and `document_path`.
248    ///
249    /// # Examples
250    ///
251    /// ```rust
252    /// # fn main() -> anyhow::Result<()> {
253    /// use firestore_path::{DocumentName,DocumentPath,DatabaseName};
254    /// use std::str::FromStr;
255    ///
256    /// let database_name = DatabaseName::from_str(
257    ///     "projects/my-project/databases/my-database"
258    /// )?;
259    /// assert_eq!(
260    ///     database_name.doc("chatrooms/chatroom1")?,
261    ///     DocumentName::from_str(
262    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
263    ///     )?
264    /// );
265    /// assert_eq!(
266    ///     database_name.doc("chatrooms/chatroom1/messages/message1")?,
267    ///     DocumentName::from_str(
268    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
269    ///     )?
270    /// );
271    /// assert_eq!(
272    ///     database_name.doc(DocumentPath::from_str("chatrooms/chatroom1")?)?,
273    ///     DocumentName::from_str(
274    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
275    ///     )?
276    /// );
277    /// assert_eq!(
278    ///     database_name.doc(DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?)?,
279    ///     DocumentName::from_str(
280    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
281    ///     )?
282    /// );
283    ///
284    /// #     Ok(())
285    /// # }
286    /// ```
287    ///
288    pub fn doc<E, T>(&self, document_path: T) -> Result<DocumentName, Error>
289    where
290        E: std::fmt::Display,
291        T: TryInto<DocumentPath, Error = E>,
292    {
293        self.clone().into_doc(document_path)
294    }
295
296    /// Creates a new `DocumentName` by consuming the `DatabaseName` with the provided `document_path`.
297    ///
298    /// # Examples
299    ///
300    /// ```rust
301    /// # fn main() -> anyhow::Result<()> {
302    /// use firestore_path::{DocumentName,DocumentPath,DatabaseName};
303    /// use std::str::FromStr;
304    ///
305    /// let database_name = DatabaseName::from_str(
306    ///     "projects/my-project/databases/my-database"
307    /// )?;
308    /// assert_eq!(
309    ///     database_name.clone().into_doc("chatrooms/chatroom1")?,
310    ///     DocumentName::from_str(
311    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
312    ///     )?
313    /// );
314    /// assert_eq!(
315    ///     database_name.clone().into_doc("chatrooms/chatroom1/messages/message1")?,
316    ///     DocumentName::from_str(
317    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
318    ///     )?
319    /// );
320    /// assert_eq!(
321    ///     database_name.clone().into_doc(DocumentPath::from_str("chatrooms/chatroom1")?)?,
322    ///     DocumentName::from_str(
323    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
324    ///     )?
325    /// );
326    /// assert_eq!(
327    ///     database_name.into_doc(DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?)?,
328    ///     DocumentName::from_str(
329    ///         "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
330    ///     )?
331    /// );
332    ///
333    /// #     Ok(())
334    /// # }
335    /// ```
336    ///
337    pub fn into_doc<E, T>(self, document_path: T) -> Result<DocumentName, Error>
338    where
339        E: std::fmt::Display,
340        T: TryInto<DocumentPath, Error = E>,
341    {
342        let document_path = document_path
343            .try_into()
344            .map_err(|e| Error::from(ErrorKind::DocumentPathConversion(e.to_string())))?;
345        Ok(DocumentName::new(self, document_path))
346    }
347
348    /// Consumes the `DatabaseName`, returning the `RootDocumentName`.
349    ///
350    /// # Examples
351    ///
352    /// ```rust
353    /// # fn main() -> anyhow::Result<()> {
354    /// use firestore_path::{DatabaseName,RootDocumentName};
355    /// use std::str::FromStr;
356    ///
357    /// let database_name = DatabaseName::from_str("projects/my-project/databases/my-database")?;
358    /// assert_eq!(
359    ///     database_name.into_root_document_name(),
360    ///     RootDocumentName::from_str("projects/my-project/databases/my-database/documents")?
361    /// );
362    /// #     Ok(())
363    /// # }
364    /// ```
365    pub fn into_root_document_name(self) -> RootDocumentName {
366        RootDocumentName::new(self)
367    }
368
369    /// Returns the `ProjectId` of this `DatabaseName`.
370    ///
371    /// # Examples
372    ///
373    /// ```rust
374    /// # fn main() -> anyhow::Result<()> {
375    /// use firestore_path::{DatabaseName,ProjectId};
376    /// use std::str::FromStr;
377    ///
378    /// let database_name = DatabaseName::from_str("projects/my-project/databases/my-database")?;
379    /// assert_eq!(
380    ///     database_name.project_id(),
381    ///     &ProjectId::from_str("my-project")?
382    /// );
383    /// #     Ok(())
384    /// # }
385    /// ```
386    pub fn project_id(&self) -> &ProjectId {
387        &self.project_id
388    }
389
390    /// Returns a new `RootDocumentName` from this `DatabaseName`.
391    ///
392    /// # Examples
393    ///
394    /// ```rust
395    /// # fn main() -> anyhow::Result<()> {
396    /// use firestore_path::{DatabaseName,RootDocumentName};
397    /// use std::str::FromStr;
398    ///
399    /// let database_name = DatabaseName::from_str("projects/my-project/databases/my-database")?;
400    /// assert_eq!(
401    ///     database_name.root_document_name(),
402    ///     RootDocumentName::from_str("projects/my-project/databases/my-database/documents")?
403    /// );
404    /// #     Ok(())
405    /// # }
406    /// ```
407    pub fn root_document_name(&self) -> RootDocumentName {
408        self.clone().into_root_document_name()
409    }
410}
411
412impl std::convert::From<DatabaseName> for DatabaseId {
413    fn from(database_name: DatabaseName) -> Self {
414        database_name.database_id
415    }
416}
417
418impl std::convert::From<DatabaseName> for ProjectId {
419    fn from(database_name: DatabaseName) -> Self {
420        database_name.project_id
421    }
422}
423
424impl std::convert::TryFrom<&str> for DatabaseName {
425    type Error = Error;
426
427    fn try_from(s: &str) -> Result<Self, Self::Error> {
428        if !(1..=1_024 * 6).contains(&s.len()) {
429            return Err(Error::from(ErrorKind::LengthOutOfBounds));
430        }
431
432        let parts = s.split('/').collect::<Vec<&str>>();
433        if parts.len() != 4 {
434            return Err(Error::from(ErrorKind::InvalidNumberOfPathComponents));
435        }
436        if parts[0] != "projects" || parts[2] != "databases" {
437            return Err(Error::from(ErrorKind::InvalidName));
438        }
439
440        let project_id = ProjectId::from_str(parts[1])?;
441        let database_id = DatabaseId::from_str(parts[3])?;
442        Ok(Self {
443            database_id,
444            project_id,
445        })
446    }
447}
448
449impl std::convert::TryFrom<String> for DatabaseName {
450    type Error = Error;
451
452    fn try_from(s: String) -> Result<Self, Self::Error> {
453        Self::try_from(s.as_str())
454    }
455}
456
457impl std::fmt::Display for DatabaseName {
458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        write!(
460            f,
461            "projects/{}/databases/{}",
462            self.project_id, self.database_id
463        )
464    }
465}
466
467impl std::str::FromStr for DatabaseName {
468    type Err = Error;
469
470    fn from_str(s: &str) -> Result<Self, Self::Err> {
471        Self::try_from(s)
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use std::str::FromStr;
478
479    use super::*;
480
481    #[test]
482    fn test() -> anyhow::Result<()> {
483        let s = "projects/my-project/databases/my-database";
484        let database_name = DatabaseName::from_str(s)?;
485        assert_eq!(database_name.to_string(), s);
486        Ok(())
487    }
488
489    #[test]
490    fn test_impl_from_str_and_impl_try_from_string() -> anyhow::Result<()> {
491        for (s, expected) in [
492            ("", false),
493            ("projects/my-project/databases/my-database", true),
494            ("x".repeat(1024 * 6 + 1).as_ref(), false),
495            ("p/my-project/databases/my-database", false),
496            ("projects/my-project/d/my-database", false),
497            ("projects/P/databases/my-database/d", false),
498            ("projects/my-project/databases/D", false),
499        ] {
500            assert_eq!(DatabaseName::from_str(s).is_ok(), expected);
501            assert_eq!(DatabaseName::try_from(s).is_ok(), expected);
502            assert_eq!(DatabaseName::try_from(s.to_string()).is_ok(), expected);
503            if expected {
504                assert_eq!(DatabaseName::from_str(s)?, DatabaseName::try_from(s)?);
505                assert_eq!(
506                    DatabaseName::from_str(s)?,
507                    DatabaseName::try_from(s.to_string())?
508                );
509                assert_eq!(DatabaseName::from_str(s)?.to_string(), s);
510            }
511        }
512        Ok(())
513    }
514
515    #[test]
516    fn test_new() -> anyhow::Result<()> {
517        let project_id = build_project_id()?;
518        let database_id = build_database_id()?;
519        let database_name = DatabaseName::new(project_id.clone(), database_id.clone());
520        assert_eq!(
521            database_name.to_string(),
522            format!("projects/{}/databases/{}", project_id, database_id)
523        );
524        Ok(())
525    }
526
527    fn build_database_id() -> anyhow::Result<DatabaseId> {
528        Ok(DatabaseId::from_str("my-database")?)
529    }
530
531    fn build_project_id() -> anyhow::Result<ProjectId> {
532        Ok(ProjectId::from_str("my-project")?)
533    }
534}