1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use serde::{de::DeserializeOwned, Deserialize};
use crate::error::RequestError;
use crate::{Collection, PocketBase};
pub struct CollectionGetOneBuilder<'a, T: Send + Deserialize<'a>> {
client: &'a PocketBase,
collection_name: &'a str,
record_id: &'a str,
expand: Option<&'a str>,
_marker: std::marker::PhantomData<T>,
}
impl<'a> Collection<'a> {
/// Fetch a single record from the given collection.
///
/// This function returns a `CollectionGetListBuilder`, which allows you to specify
/// additional options such as filtering or expanding linked records before calling `.call().await` to
/// execute the request.
///
/// # Example
///
/// ```rust,ignore
/// use std::error::Error;
///
/// use pocketbase_rs::PocketBase;
/// use serde::Deserialize;
///
/// #[derive(Default, Deserialize, Clone)]
/// struct Article {
/// title: String,
/// content: String,
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// let mut pb = PocketBase::new("http://localhost:8090");
///
/// // ...
///
/// let article = pb
/// .collection("articles")
/// .get_one::<Article>("record_id_123")
/// .call()
/// .await?;
///
/// println!("Article: {article:?}");
///
/// Ok(())
/// }
/// ```
#[must_use]
pub const fn get_one<T: Default + DeserializeOwned + Clone + Send>(
self,
record_id: &'a str,
) -> CollectionGetOneBuilder<'a, T> {
CollectionGetOneBuilder {
client: self.client,
collection_name: self.name,
record_id,
expand: None,
_marker: std::marker::PhantomData,
}
}
}
impl<'a, T: Default + DeserializeOwned + Clone + Send> CollectionGetOneBuilder<'a, T> {
/// Auto expand record relations.
///
/// Example:
/// ```toml
/// ?expand=relField1,relField2.subRelField
/// ```
///
/// Supports up to 6-levels depth nested relations expansion.
/// The expanded relations will be appended to each individual record under the `expand` property (eg. `"expand": {"relField1": {...}, ...}`).
/// Only the relations to which the request user has permissions to **view** will be expanded.
pub const fn expand(mut self, expand: &'a str) -> Self {
self.expand = Some(expand);
self
}
/// Sends the request and returns the response.
///
/// This method finalizes the request built using the builder pattern
/// and sends it to the API endpoint. It should be called after all
/// desired parameters and configurations have been set on the builder.
pub async fn call(self) -> Result<T, RequestError> {
let url = format!(
"{}/api/collections/{}/records/{}",
self.client.base_url, self.collection_name, self.record_id
);
let request = self.expand.map_or_else(
|| self.client.request_get(&url, None),
|expand_value| {
let expand_params = vec![("expand", expand_value)];
self.client.request_get(&url, Some(expand_params))
},
);
let request = request.send().await;
let response = match request {
Ok(response) => response
.error_for_status()
.map_err(|err| match err.status() {
Some(reqwest::StatusCode::FORBIDDEN) => RequestError::Forbidden,
Some(reqwest::StatusCode::NOT_FOUND) => RequestError::NotFound,
Some(reqwest::StatusCode::TOO_MANY_REQUESTS) => RequestError::TooManyRequests,
_ => RequestError::Unhandled,
})?,
Err(error) => {
return Err(match error.status() {
Some(reqwest::StatusCode::FORBIDDEN) => RequestError::Forbidden,
Some(reqwest::StatusCode::NOT_FOUND) => RequestError::NotFound,
Some(reqwest::StatusCode::TOO_MANY_REQUESTS) => RequestError::TooManyRequests,
_ => RequestError::Unhandled,
});
}
};
// Parse JSON response
let record = response
.json::<T>()
.await
.map_err(|error| RequestError::ParseError(error.to_string()))?;
Ok(record)
}
}