firestore_db_and_auth/documents/
list.rs

1use super::*;
2use bytes::Bytes;
3use core::pin::Pin;
4use futures::{
5    stream::{self, Stream},
6    task::{Context, Poll},
7    Future,
8};
9use std::boxed::Box;
10
11/// List all documents of a given collection.
12///
13/// Please note that this API acts as an iterator of same-like documents.
14/// This type is not suitable if you want to list documents of different types.
15///
16/// Example:
17/// ```no_run
18/// # use futures::{FutureExt, StreamExt};
19/// use serde::{Serialize, Deserialize};
20/// #[derive(Debug, Serialize, Deserialize)]
21/// struct DemoDTO { a_string: String, an_int: u32, }
22///
23/// use firestore_db_and_auth::documents;
24/// # tokio_test::block_on(async {
25/// # use firestore_db_and_auth::{credentials::Credentials, ServiceSession, errors::Result};
26/// # use firestore_db_and_auth::credentials::doctest_credentials;
27/// # let session = ServiceSession::new(doctest_credentials().await).await.unwrap();
28///
29/// let mut stream = documents::list(&session, "tests");
30/// while let Some(Ok(doc_result)) = stream.next().await {
31///     // The data is wrapped in a Result<> because fetching new data could have failed
32///     // A tuple is returned on success with the document itself and and metadata
33///     // with .name, .create_time, .update_time fields.
34///     let (doc, _metadata) = doc_result;
35///     let doc: DemoDTO = doc;
36///     println!("{:?}", doc);
37/// }
38/// # })
39/// ```
40///
41/// ## Arguments
42/// * 'auth' The authentication token
43/// * 'collection_id' The document path / collection; For example "my_collection" or "a/nested/collection"
44pub fn list<T, AUTH>(
45    auth: &AUTH,
46    collection_id: impl Into<String>,
47) -> Pin<Box<dyn Stream<Item = Result<(T, dto::Document)>> + Send>>
48where
49    for<'b> T: Deserialize<'b> + 'static,
50    AUTH: FirebaseAuthBearer + Clone + Send + Sync + 'static,
51{
52    let auth = auth.clone();
53    let collection_id = collection_id.into();
54
55    Box::pin(stream::unfold(
56        ListInner {
57            url: firebase_url(auth.project_id(), &collection_id),
58            auth,
59            next_page_token: None,
60            documents: vec![],
61            current: 0,
62            done: false,
63            collection_id: collection_id.to_string(),
64        },
65        |this| async move {
66            let mut this = this.clone();
67            if this.done {
68                return None;
69            }
70
71            if this.documents.len() <= this.current {
72                let url = match &this.next_page_token {
73                    Some(next_page_token) => format!("{}pageToken={}", this.url, next_page_token),
74                    None => this.url.clone(),
75                };
76
77                let result = get_new_data(&this.collection_id, &url, &this.auth).await;
78                match result {
79                    Err(e) => {
80                        this.done = true;
81                        return Some((Err(e), this));
82                    }
83                    Ok(v) => match v.documents {
84                        None => return None,
85                        Some(documents) => {
86                            this.documents = documents;
87                            this.current = 0;
88                            this.next_page_token = v.next_page_token;
89                        }
90                    },
91                }
92            }
93
94            let doc = this.documents.get(this.current).unwrap().clone();
95
96            this.current += 1;
97
98            if this.documents.len() <= this.current && this.next_page_token.is_none() {
99                this.done = true;
100            }
101
102            let result = document_to_pod(&doc, None);
103            match result {
104                Err(e) => Some((Err(e), this)),
105                Ok(pod) => Some((
106                    Ok((
107                        pod,
108                        dto::Document {
109                            update_time: doc.update_time.clone(),
110                            create_time: doc.create_time.clone(),
111                            name: doc.name.clone(),
112                            fields: None,
113                        },
114                    )),
115                    this,
116                )),
117            }
118        },
119    ))
120}
121
122async fn get_new_data<'a>(
123    collection_id: &str,
124    url: &str,
125    auth: &'a impl FirebaseAuthBearer,
126) -> Result<dto::ListDocumentsResponse> {
127    let resp = auth
128        .client()
129        .get(url)
130        .bearer_auth(auth.access_token().await)
131        .send()
132        .await?;
133
134    let resp = extract_google_api_error_async(resp, || collection_id.to_owned()).await?;
135
136    let json: dto::ListDocumentsResponse = resp.json().await?;
137    Ok(json)
138}
139
140#[derive(Clone)]
141struct ListInner<AUTH> {
142    auth: AUTH,
143    next_page_token: Option<String>,
144    documents: Vec<dto::Document>,
145    current: usize,
146    done: bool,
147    url: String,
148    collection_id: String,
149}