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}