firestore_path/
collection_path.rs1use std::str::FromStr;
2
3use crate::{error::ErrorKind, CollectionId, DocumentId, DocumentPath, Error};
4
5#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28pub struct CollectionPath {
29 document_path: Option<DocumentPath>,
30 collection_id: CollectionId,
31}
32
33impl CollectionPath {
34 pub fn new(parent: Option<DocumentPath>, collection_id: CollectionId) -> Self {
55 Self {
56 document_path: parent,
57 collection_id,
58 }
59 }
60
61 pub fn collection_id(&self) -> &CollectionId {
76 &self.collection_id
77 }
78
79 pub fn doc<E, T>(&self, document_id: T) -> Result<DocumentPath, Error>
101 where
102 E: std::fmt::Display,
103 T: TryInto<DocumentId, Error = E>,
104 {
105 self.clone().into_doc(document_id)
106 }
107
108 pub fn into_doc<E, T>(self, document_id: T) -> Result<DocumentPath, Error>
130 where
131 E: std::fmt::Display,
132 T: TryInto<DocumentId, Error = E>,
133 {
134 let document_id = document_id
135 .try_into()
136 .map_err(|e| Error::from(ErrorKind::DocumentIdConversion(e.to_string())))?;
137 let document_path = DocumentPath::new(self, document_id);
138 Ok(document_path)
139 }
140
141 pub fn into_parent(self) -> Option<DocumentPath> {
160 self.document_path
161 }
162
163 pub fn parent(&self) -> Option<&DocumentPath> {
181 self.document_path.as_ref()
182 }
183
184 pub(crate) fn into_tuple(self) -> (Option<DocumentPath>, CollectionId) {
185 (self.document_path, self.collection_id)
186 }
187}
188
189impl std::convert::From<CollectionId> for CollectionPath {
190 fn from(collection_id: CollectionId) -> Self {
191 CollectionPath::new(None, collection_id)
192 }
193}
194
195impl std::convert::From<CollectionPath> for CollectionId {
196 fn from(collection_path: CollectionPath) -> Self {
197 collection_path.collection_id
198 }
199}
200
201impl std::convert::From<CollectionPath> for Option<DocumentPath> {
202 fn from(collection_path: CollectionPath) -> Self {
203 collection_path.document_path
204 }
205}
206
207impl std::convert::TryFrom<&str> for CollectionPath {
208 type Error = Error;
209
210 fn try_from(s: &str) -> Result<Self, Self::Error> {
211 Self::try_from(s.to_string())
212 }
213}
214
215impl std::convert::TryFrom<String> for CollectionPath {
216 type Error = Error;
217
218 fn try_from(s: String) -> Result<Self, Self::Error> {
219 Ok(match s.rsplit_once('/') {
220 Some((document_path, collection_id)) => Self {
221 document_path: Some(DocumentPath::from_str(document_path)?),
222 collection_id: CollectionId::from_str(collection_id)?,
223 },
224 None => Self {
225 document_path: None,
226 collection_id: CollectionId::try_from(s)?,
227 },
228 })
229 }
230}
231
232impl std::fmt::Display for CollectionPath {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 match self.document_path.as_ref() {
235 Some(document_path) => write!(f, "{}/{}", document_path, self.collection_id),
236 None => self.collection_id.fmt(f),
237 }
238 }
239}
240
241impl std::str::FromStr for CollectionPath {
242 type Err = Error;
243
244 fn from_str(s: &str) -> Result<Self, Self::Err> {
245 Self::try_from(s)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use std::str::FromStr;
252
253 use super::*;
254
255 #[test]
256 fn test() -> anyhow::Result<()> {
257 let s = "chatrooms";
258 let collection_path = CollectionPath::from_str(s)?;
259 assert_eq!(collection_path.to_string(), s);
260
261 let s = "chatrooms/chatroom1/messages";
262 let collection_path = CollectionPath::from_str(s)?;
263 assert_eq!(collection_path.to_string(), s);
264 Ok(())
265 }
266
267 #[test]
268 fn test_collection_id() -> anyhow::Result<()> {
269 let collection_path = CollectionPath::from_str("chatrooms")?;
270 assert_eq!(
271 collection_path.collection_id(),
272 &CollectionId::from_str("chatrooms")?
273 );
274 Ok(())
275 }
276
277 #[test]
278 fn test_doc() -> anyhow::Result<()> {
279 let collection_path = CollectionPath::from_str("chatrooms")?;
280 let document_path = collection_path.doc("chatroom1")?;
281 assert_eq!(
282 document_path,
283 DocumentPath::from_str("chatrooms/chatroom1")?
284 );
285 let document_path = collection_path.doc("chatroom2")?;
286 assert_eq!(
287 document_path,
288 DocumentPath::from_str("chatrooms/chatroom2")?
289 );
290
291 let collection_path = CollectionPath::from_str("chatrooms/chatroom1/messages")?;
292 let document_path = collection_path.doc("message1")?;
293 assert_eq!(
294 document_path,
295 DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?
296 );
297
298 let collection_path = CollectionPath::from_str("chatrooms")?;
299 let document_id = DocumentId::from_str("chatroom1")?;
300 let document_path = collection_path.doc(document_id)?;
301 assert_eq!(
302 document_path,
303 DocumentPath::from_str("chatrooms/chatroom1")?
304 );
305
306 Ok(())
307 }
308
309 #[test]
310 fn test_impl_from_collection_path_for_document_path() -> anyhow::Result<()> {
311 let collection_path = CollectionPath::from_str("chatrooms")?;
312 assert_eq!(Option::<DocumentPath>::from(collection_path), None);
313
314 let collection_path = CollectionPath::from_str("chatrooms/chatroom1/messages")?;
315 assert_eq!(
316 Option::<DocumentPath>::from(collection_path),
317 Some(DocumentPath::from_str("chatrooms/chatroom1")?)
318 );
319 Ok(())
320 }
321
322 #[test]
323 fn test_impl_from_collection_id_for_collection_path() -> anyhow::Result<()> {
324 let collection_id = CollectionId::from_str("chatrooms")?;
325 assert_eq!(
326 CollectionPath::from(collection_id),
327 CollectionPath::from_str("chatrooms")?
328 );
329 Ok(())
330 }
331
332 #[test]
333 fn test_impl_from_collection_path_for_collection_id() -> anyhow::Result<()> {
334 let collection_path = CollectionPath::from_str("chatrooms")?;
335 assert_eq!(
336 CollectionId::from(collection_path),
337 CollectionId::from_str("chatrooms")?
338 );
339 Ok(())
340 }
341
342 #[test]
343 fn test_impl_from_str_and_impl_try_from_string() -> anyhow::Result<()> {
344 for (s, expected) in [("chatrooms", true), ("chatrooms/chatroom1/messages", true)] {
345 assert_eq!(CollectionPath::from_str(s).is_ok(), expected);
346 assert_eq!(CollectionPath::try_from(s).is_ok(), expected);
347 assert_eq!(CollectionPath::try_from(s.to_string()).is_ok(), expected);
348 if expected {
349 assert_eq!(CollectionPath::from_str(s)?, CollectionPath::try_from(s)?);
350 assert_eq!(
351 CollectionPath::from_str(s)?,
352 CollectionPath::try_from(s.to_string())?
353 );
354 assert_eq!(CollectionPath::from_str(s)?.to_string(), s);
355 }
356 }
357 Ok(())
358 }
359
360 #[test]
361 fn test_into_doc() -> anyhow::Result<()> {
362 let collection_path = CollectionPath::from_str("chatrooms")?;
363 let document_path = collection_path.into_doc("chatroom1")?;
364 assert_eq!(
365 document_path,
366 DocumentPath::from_str("chatrooms/chatroom1")?
367 );
368
369 let collection_path = CollectionPath::from_str("chatrooms/chatroom1/messages")?;
370 let document_path = collection_path.into_doc("message1")?;
371 assert_eq!(
372 document_path,
373 DocumentPath::from_str("chatrooms/chatroom1/messages/message1")?
374 );
375
376 let collection_path = CollectionPath::from_str("chatrooms")?;
377 let document_id = DocumentId::from_str("chatroom1")?;
378 let document_path = collection_path.into_doc(document_id)?;
379 assert_eq!(
380 document_path,
381 DocumentPath::from_str("chatrooms/chatroom1")?
382 );
383
384 Ok(())
385 }
386
387 #[test]
388 fn test_new() -> anyhow::Result<()> {
389 let collection_id = build_collection_id()?;
390 let collection_path = CollectionPath::new(None, collection_id.clone());
391 assert_eq!(collection_path.to_string(), format!("{}", collection_id));
392
393 let document_path = build_document_path()?;
394 let collection_path =
395 CollectionPath::new(Some(document_path.clone()), collection_id.clone());
396 assert_eq!(
397 collection_path.to_string(),
398 format!("{}/{}", document_path, collection_id)
399 );
400 Ok(())
401 }
402
403 fn build_collection_id() -> anyhow::Result<CollectionId> {
404 Ok(CollectionId::from_str("chatrooms")?)
405 }
406
407 fn build_document_path() -> anyhow::Result<DocumentPath> {
408 Ok(DocumentPath::from_str("chatrooms/chatroom1")?)
409 }
410}