pocketbase_rs/records/crud/
get_list.rs

1use serde::{Deserialize, de::DeserializeOwned};
2
3use crate::PocketBase;
4use crate::error::RequestError;
5use crate::{Collection, RecordList};
6
7pub struct CollectionGetListBuilder<'a, T: Send + Deserialize<'a>> {
8    client: &'a PocketBase,
9    collection_name: &'a str,
10    page: Option<String>,
11    per_page: Option<String>,
12    sort: Option<&'a str>,
13    expand: Option<&'a str>,
14    filter: Option<&'a str>,
15    skip_total: bool,
16    _marker: std::marker::PhantomData<T>,
17}
18
19impl<'a> Collection<'a> {
20    /// Fetch a paginated records list from the given collection.
21    ///
22    /// # Example
23    /// ```rust,ignore
24    /// #[derive(Default, Deserialize, Clone)]
25    /// struct Article {
26    ///     id: String,
27    ///     title: String,
28    ///     content: String,
29    /// }
30    ///
31    /// let articles = pb
32    ///     .collection("articles")
33    ///     .get_list::<Article>()
34    ///     .sort("-created,id")
35    ///     .call()
36    ///     .await?;
37    ///
38    /// for article in articles.items {
39    ///     println!("{article:?}");
40    /// }
41    /// ```
42    #[must_use]
43    pub const fn get_list<T: Default + DeserializeOwned + Clone + Send>(
44        self,
45    ) -> CollectionGetListBuilder<'a, T> {
46        CollectionGetListBuilder {
47            client: self.client,
48            collection_name: self.name,
49            page: None,
50            per_page: None,
51            sort: None,
52            expand: None,
53            filter: None,
54            skip_total: false,
55            _marker: std::marker::PhantomData,
56        }
57    }
58}
59
60impl<'a, T: Default + DeserializeOwned + Clone + Send> CollectionGetListBuilder<'a, T> {
61    /// The page (aka. offset) of the paginated list (default to 1).
62    pub fn page(mut self, page: u16) -> Self {
63        self.page = Some(page.to_string());
64        self
65    }
66
67    /// Set the max returned records per page (default: 30, max: 500).
68    pub fn per_page(mut self, per_page: u16) -> Self {
69        self.per_page = Some(per_page.to_string());
70        self
71    }
72
73    /// Specify the records order attribute(s).
74    /// Add `-`/`+` (default) in front of the attribute for DESC / ASC order.
75    ///
76    /// # Example
77    /// ```rust,ignore
78    /// .sort("-created,id") // DESC by created, ASC by id
79    /// ```
80    pub const fn sort(mut self, sort: &'a str) -> Self {
81        self.sort = Some(sort);
82        self
83    }
84
85    /// Filter the returned records.
86    ///
87    /// Supports operators: `=`, `!=`, `>`, `>=`, `<`, `<=`, `~`, `!~`
88    /// and their "any/at least one" variants with `?` prefix.
89    /// Combine with `&&` (AND), `||` (OR), and `(...)` for grouping.
90    ///
91    /// # Example
92    /// ```rust,ignore
93    /// .filter("language='en' && created>'1970-01-01'")
94    /// ```
95    pub const fn filter(mut self, filter: &'a str) -> Self {
96        self.filter = Some(filter);
97        self
98    }
99
100    /// Auto expand record relations (up to 6-levels deep).
101    ///
102    /// Expanded relations are appended under the `expand` property.
103    /// Only relations the user has view permissions for will be expanded.
104    ///
105    /// # Example
106    /// ```rust,ignore
107    /// .expand("author")
108    /// ```
109    pub const fn expand(mut self, expand: &'a str) -> Self {
110        self.expand = Some(expand);
111        self
112    }
113
114    /// Skip total count query for better performance.
115    ///
116    /// When enabled, `totalItems` and `totalPages` will be `-1`.
117    /// Useful for cursor pagination or when totals aren't needed.
118    pub const fn skip_total(mut self, skip_total: bool) -> Self {
119        self.skip_total = skip_total;
120        self
121    }
122
123    /// Execute the request and return the paginated results.
124    pub async fn call(self) -> Result<RecordList<T>, RequestError> {
125        let url = format!(
126            "{}/api/collections/{}/records",
127            self.client.base_url, self.collection_name
128        );
129
130        let mut query_parameters: Vec<(&str, &str)> = vec![];
131
132        if let Some(page) = self.page.as_deref() {
133            query_parameters.push(("page", page));
134        }
135
136        if let Some(per_page) = self.per_page.as_deref() {
137            query_parameters.push(("perPage", per_page));
138        }
139
140        if let Some(sort) = self.sort {
141            query_parameters.push(("sort", sort));
142        }
143
144        if let Some(filter) = self.filter {
145            query_parameters.push(("filter", filter));
146        }
147
148        if let Some(expand) = self.expand {
149            query_parameters.push(("expand", expand));
150        }
151
152        let request = self
153            .client
154            .request_get(&url, Some(query_parameters))
155            .send()
156            .await;
157
158        let response = match request {
159            Ok(response) => response
160                .error_for_status()
161                .map_err(|err| match err.status() {
162                    Some(reqwest::StatusCode::FORBIDDEN) => RequestError::Forbidden,
163                    Some(reqwest::StatusCode::NOT_FOUND) => RequestError::NotFound,
164                    Some(reqwest::StatusCode::TOO_MANY_REQUESTS) => RequestError::TooManyRequests,
165                    _ => RequestError::Unhandled,
166                })?,
167            Err(error) => {
168                return Err(match error.status() {
169                    Some(reqwest::StatusCode::FORBIDDEN) => RequestError::Forbidden,
170                    Some(reqwest::StatusCode::NOT_FOUND) => RequestError::NotFound,
171                    Some(reqwest::StatusCode::TOO_MANY_REQUESTS) => RequestError::TooManyRequests,
172                    _ => RequestError::Unhandled,
173                });
174            }
175        };
176
177        // Parse JSON response
178        let records = response
179            .json::<RecordList<T>>()
180            .await
181            .map_err(|error| RequestError::ParseError(error.to_string()))?;
182
183        Ok(records)
184    }
185}