firestore_path/root_document_name.rs
1use std::str::FromStr;
2
3use crate::{
4 error::ErrorKind, CollectionName, CollectionPath, DatabaseId, DatabaseName, DocumentName,
5 DocumentPath, Error, ProjectId,
6};
7
8/// A root document name.
9///
10/// # Format
11///
12/// `{database_name}/documents`
13///
14/// # Examples
15///
16/// ```rust
17/// # fn main() -> anyhow::Result<()> {
18/// use firestore_path::RootDocumentName;
19/// use std::str::FromStr;
20///
21/// let root_document_name = RootDocumentName::from_str(
22/// "projects/my-project/databases/my-database/documents"
23/// )?;
24/// assert_eq!(
25/// root_document_name.to_string(),
26/// "projects/my-project/databases/my-database/documents"
27/// );
28/// # Ok(())
29/// # }
30/// ```
31///
32#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub struct RootDocumentName {
34 database_name: DatabaseName,
35}
36
37impl RootDocumentName {
38 /// Creates a new `RootDocumentName`.
39 ///
40 /// # Examples
41 ///
42 /// ```rust
43 /// # fn main() -> anyhow::Result<()> {
44 /// use firestore_path::{DatabaseName,RootDocumentName};
45 /// use std::str::FromStr;
46 ///
47 /// let database_name = DatabaseName::from_str(
48 /// "projects/my-project/databases/my-database"
49 /// )?;
50 /// assert_eq!(
51 /// RootDocumentName::new(database_name),
52 /// RootDocumentName::from_str(
53 /// "projects/my-project/databases/my-database/documents"
54 /// )?
55 /// );
56 /// # Ok(())
57 /// # }
58 /// ```
59 pub fn new(database_name: DatabaseName) -> Self {
60 Self { database_name }
61 }
62
63 /// Creates a new `CollectionName` from this `RootDocumentName` and `collection_path`.
64 ///
65 /// # Examples
66 ///
67 /// ```rust
68 /// # fn main() -> anyhow::Result<()> {
69 /// use firestore_path::{CollectionId,CollectionName,CollectionPath,RootDocumentName};
70 /// use std::str::FromStr;
71 ///
72 /// let root_document_name = RootDocumentName::from_str(
73 /// "projects/my-project/databases/my-database/documents"
74 /// )?;
75 /// assert_eq!(
76 /// root_document_name.collection("chatrooms")?,
77 /// CollectionName::from_str(
78 /// "projects/my-project/databases/my-database/documents/chatrooms"
79 /// )?
80 /// );
81 /// assert_eq!(
82 /// root_document_name.collection("chatrooms/chatroom1/messages")?,
83 /// CollectionName::from_str(
84 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
85 /// )?
86 /// );
87 /// assert_eq!(
88 /// root_document_name.collection(CollectionId::from_str("chatrooms")?)?,
89 /// CollectionName::from_str(
90 /// "projects/my-project/databases/my-database/documents/chatrooms"
91 /// )?
92 /// );
93 /// assert_eq!(
94 /// root_document_name.collection(CollectionPath::from_str("chatrooms/chatroom1/messages")?)?,
95 /// CollectionName::from_str(
96 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
97 /// )?
98 /// );
99 ///
100 /// # Ok(())
101 /// # }
102 /// ```
103 ///
104 pub fn collection<E, T>(&self, collection_path: T) -> Result<CollectionName, Error>
105 where
106 E: std::fmt::Display,
107 T: TryInto<CollectionPath, Error = E>,
108 {
109 self.clone().into_collection(collection_path)
110 }
111
112 /// Creates a new `CollectionName` by consuming `RootDocumentName` with the provided `collection_path`.
113 ///
114 /// # Examples
115 ///
116 /// ```rust
117 /// # fn main() -> anyhow::Result<()> {
118 /// use firestore_path::{CollectionId,CollectionName,CollectionPath,RootDocumentName};
119 /// use std::str::FromStr;
120 ///
121 /// let root_document_name = RootDocumentName::from_str(
122 /// "projects/my-project/databases/my-database/documents"
123 /// )?;
124 /// assert_eq!(
125 /// root_document_name.clone().into_collection("chatrooms")?,
126 /// CollectionName::from_str(
127 /// "projects/my-project/databases/my-database/documents/chatrooms"
128 /// )?
129 /// );
130 /// assert_eq!(
131 /// root_document_name.clone().into_collection("chatrooms/chatroom1/messages")?,
132 /// CollectionName::from_str(
133 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
134 /// )?
135 /// );
136 /// assert_eq!(
137 /// root_document_name.clone().into_collection(CollectionId::from_str("chatrooms")?)?,
138 /// CollectionName::from_str(
139 /// "projects/my-project/databases/my-database/documents/chatrooms"
140 /// )?
141 /// );
142 /// assert_eq!(
143 /// root_document_name.into_collection(CollectionPath::from_str("chatrooms/chatroom1/messages")?)?,
144 /// CollectionName::from_str(
145 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages"
146 /// )?
147 /// );
148 ///
149 /// # Ok(())
150 /// # }
151 /// ```
152 ///
153 pub fn into_collection<E, T>(self, collection_path: T) -> Result<CollectionName, Error>
154 where
155 E: std::fmt::Display,
156 T: TryInto<CollectionPath, Error = E>,
157 {
158 let collection_path = collection_path
159 .try_into()
160 .map_err(|e| Error::from(ErrorKind::CollectionPathConversion(e.to_string())))?;
161 Ok(CollectionName::new(self, collection_path))
162 }
163
164 /// Creates a new `DocumentName` from this `RootDocumentName` and `document_path`.
165 ///
166 /// # Examples
167 ///
168 /// ```rust
169 /// # fn main() -> anyhow::Result<()> {
170 /// use firestore_path::{DocumentName,DocumentPath,RootDocumentName};
171 /// use std::str::FromStr;
172 ///
173 /// let root_document_name = RootDocumentName::from_str(
174 /// "projects/my-project/databases/my-database/documents"
175 /// )?;
176 /// assert_eq!(
177 /// root_document_name.doc("chatrooms/chatroom1")?,
178 /// DocumentName::from_str(
179 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
180 /// )?
181 /// );
182 /// assert_eq!(
183 /// root_document_name.doc("chatrooms/chatroom1/messages/message1")?,
184 /// DocumentName::from_str(
185 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
186 /// )?
187 /// );
188 /// assert_eq!(
189 /// root_document_name.doc(DocumentPath::from_str("chatrooms/chatroom1")?)?,
190 /// DocumentName::from_str(
191 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
192 /// )?
193 /// );
194 /// assert_eq!(
195 /// root_document_name.doc(DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?)?,
196 /// DocumentName::from_str(
197 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
198 /// )?
199 /// );
200 ///
201 /// # Ok(())
202 /// # }
203 /// ```
204 ///
205 pub fn doc<E, T>(&self, document_path: T) -> Result<DocumentName, Error>
206 where
207 E: std::fmt::Display,
208 T: TryInto<DocumentPath, Error = E>,
209 {
210 self.clone().into_doc(document_path)
211 }
212
213 /// Creates a new `DocumentName` by consuming the `RootDocumentName` with the provided `document_path`.
214 ///
215 /// # Examples
216 ///
217 /// ```rust
218 /// # fn main() -> anyhow::Result<()> {
219 /// use firestore_path::{DocumentName,DocumentPath,RootDocumentName};
220 /// use std::str::FromStr;
221 ///
222 /// let root_document_name = RootDocumentName::from_str(
223 /// "projects/my-project/databases/my-database/documents"
224 /// )?;
225 /// assert_eq!(
226 /// root_document_name.clone().into_doc("chatrooms/chatroom1")?,
227 /// DocumentName::from_str(
228 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
229 /// )?
230 /// );
231 /// assert_eq!(
232 /// root_document_name.clone().into_doc("chatrooms/chatroom1/messages/message1")?,
233 /// DocumentName::from_str(
234 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
235 /// )?
236 /// );
237 /// assert_eq!(
238 /// root_document_name.clone().into_doc(DocumentPath::from_str("chatrooms/chatroom1")?)?,
239 /// DocumentName::from_str(
240 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1"
241 /// )?
242 /// );
243 /// assert_eq!(
244 /// root_document_name.doc(DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?)?,
245 /// DocumentName::from_str(
246 /// "projects/my-project/databases/my-database/documents/chatrooms/chatroom1/messages/message1"
247 /// )?
248 /// );
249 ///
250 /// # Ok(())
251 /// # }
252 /// ```
253 ///
254 pub fn into_doc<E, T>(self, document_path: T) -> Result<DocumentName, Error>
255 where
256 E: std::fmt::Display,
257 T: TryInto<DocumentPath, Error = E>,
258 {
259 let document_path = document_path
260 .try_into()
261 .map_err(|e| Error::from(ErrorKind::DocumentPathConversion(e.to_string())))?;
262 Ok(DocumentName::new(self, document_path))
263 }
264
265 pub(crate) fn as_database_name(&self) -> &DatabaseName {
266 &self.database_name
267 }
268}
269
270impl std::convert::From<DatabaseName> for RootDocumentName {
271 fn from(database_name: DatabaseName) -> Self {
272 Self { database_name }
273 }
274}
275
276impl std::convert::From<RootDocumentName> for DatabaseName {
277 fn from(root_document_name: RootDocumentName) -> Self {
278 root_document_name.database_name
279 }
280}
281
282impl std::convert::TryFrom<&str> for RootDocumentName {
283 type Error = Error;
284
285 fn try_from(s: &str) -> Result<Self, Self::Error> {
286 if !(1..=1_024 * 6).contains(&s.len()) {
287 return Err(Error::from(ErrorKind::LengthOutOfBounds));
288 }
289
290 let parts = s.split('/').collect::<Vec<&str>>();
291 if parts.len() != 5 {
292 return Err(Error::from(ErrorKind::InvalidNumberOfPathComponents));
293 }
294 if parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" {
295 return Err(Error::from(ErrorKind::InvalidName));
296 }
297
298 let project_id = ProjectId::from_str(parts[1])?;
299 let database_id = DatabaseId::from_str(parts[3])?;
300 let database_name = DatabaseName::new(project_id, database_id);
301 Ok(Self { database_name })
302 }
303}
304
305impl std::convert::TryFrom<String> for RootDocumentName {
306 type Error = Error;
307
308 fn try_from(s: String) -> Result<Self, Self::Error> {
309 Self::try_from(s.as_str())
310 }
311}
312
313impl std::fmt::Display for RootDocumentName {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 write!(f, "{}/documents", self.database_name)
316 }
317}
318
319impl std::str::FromStr for RootDocumentName {
320 type Err = Error;
321
322 fn from_str(s: &str) -> Result<Self, Self::Err> {
323 Self::try_from(s)
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use std::str::FromStr;
330
331 use super::*;
332
333 #[test]
334 fn test() -> anyhow::Result<()> {
335 let s = "projects/my-project/databases/my-database/documents";
336 let root_document_name = RootDocumentName::from_str(s)?;
337 assert_eq!(root_document_name.to_string(), s);
338 Ok(())
339 }
340
341 #[test]
342 fn test_impl_from_str_and_impl_try_from_string() -> anyhow::Result<()> {
343 for (s, expected) in [
344 ("", false),
345 ("projects/my-project/databases/my-database/documents", true),
346 ("x".repeat(1024 * 6 + 1).as_ref(), false),
347 ("p/my-project/databases/my-database/documents", false),
348 ("projects/my-project/d/my-database/documents", false),
349 ("projects/my-project/databases/my-database/d", false),
350 ("projects/P/databases/my-database/d", false),
351 ("projects/my-project/databases/D/d", false),
352 ] {
353 assert_eq!(RootDocumentName::from_str(s).is_ok(), expected);
354 assert_eq!(RootDocumentName::try_from(s).is_ok(), expected);
355 assert_eq!(RootDocumentName::try_from(s.to_string()).is_ok(), expected);
356 if expected {
357 assert_eq!(
358 RootDocumentName::from_str(s)?,
359 RootDocumentName::try_from(s)?
360 );
361 assert_eq!(
362 RootDocumentName::from_str(s)?,
363 RootDocumentName::try_from(s.to_string())?
364 );
365 assert_eq!(RootDocumentName::from_str(s)?.to_string(), s);
366 }
367 }
368 Ok(())
369 }
370}