A wrapper around reqwest to make working with Pocketbase a breeze
1use std::sync::{Arc, RwLock};
2
3use reqwest::{
4 Client, Method,
5 header::{HeaderMap, HeaderValue},
6};
7use serde::{Serialize, de::DeserializeOwned};
8use serde_json::{Map, Value};
9use thiserror::Error;
10use url::Url;
11
12pub mod auth;
13pub mod health;
14pub mod record;
15
16use crate::{auth::AuthStore, health::HealthService, record::RecordService};
17
18/// Type alias for reqwest's error type, that should be generally used if an
19/// error is returned.
20type Error = reqwest::Error;
21
22/// Type alias for a type that can represent general JSON objects.
23type JsonObject = Map<String, Value>;
24
25#[derive(Clone, Debug)]
26/// Main PocketBase API client.
27pub struct PocketBase {
28 inner: Arc<RwLock<PocketBaseInner>>,
29}
30
31#[derive(Clone, Debug)]
32/// Inner storage for the PocketBase API client.
33struct PocketBaseInner {
34 /// Storage of the authenticated user and the authenticated token, if any.
35 auth_store: Option<AuthStore>,
36 /// The PocketBase backend base url address (e.g. `http://127.0.0.1:8090`).
37 base_url: Url,
38 /// Language code that will be sent with the requests to the server as
39 /// `Accept-Language` header.
40 ///
41 /// Will be set to `en-US` by default.
42 lang: String,
43 /// Reqwest [`Client`] used.
44 reqwest: Client,
45}
46
47impl PocketBase {
48 /// Clears the previously stored token and record auth data.
49 pub fn auth_clear<'a>(&'a self) {
50 let mut write = self.inner.write().unwrap();
51 write.auth_store = None;
52 }
53
54 /// Returns the [`RecordService`] associated to the specified collection.
55 pub fn collection<'a>(&'a self, collection: &'a str) -> RecordService<'a> {
56 RecordService {
57 client: self,
58 collection,
59 }
60 }
61
62 /// Return an instance of the service that handles the Health APIs.
63 pub fn health(&self) -> HealthService {
64 HealthService {
65 client: self,
66 fields: None,
67 }
68 }
69
70 /// Get the language code that will be sent with the requests to the server as
71 /// `Accept-Language` header.
72 pub fn lang(&self) -> String {
73 self.inner.read().unwrap().lang.clone()
74 }
75
76 /// Create a new [`PocketBase`] instance using the provided base URL for the
77 /// PocketBase instance (e.g. `http://127.0.0.1:8090`).
78 pub fn new(base_url: &str) -> Result<Self, url::ParseError> {
79 let base_url = Url::parse(base_url)?;
80 let inner = PocketBaseInner {
81 reqwest: Client::new(),
82 base_url,
83 lang: "en_US".to_owned(),
84 auth_store: None,
85 };
86 Ok(Self {
87 inner: Arc::new(RwLock::new(inner)),
88 })
89 }
90
91 /// Internal method used to send requests to the Pockatbase instance.
92 async fn send<Q, B, T>(
93 &self,
94 path: &str,
95 method: Method,
96 headers: HeaderMap,
97 query: &Q,
98 body: Option<&B>,
99 ) -> Result<T, Error>
100 where
101 Q: Serialize + ?Sized,
102 B: Serialize + ?Sized,
103 T: DeserializeOwned,
104 {
105 // Get a reading lock on the inner data
106 let request = {
107 let read = self.inner.read().unwrap();
108
109 // Create the full URL
110 let mut url = read.base_url.clone();
111 url.set_path(path);
112
113 let mut headers = headers;
114 if !headers.contains_key("Authorization") && read.auth_store.is_some() {
115 headers.insert(
116 "Authorization",
117 HeaderValue::from_str(&read.auth_store.as_ref().unwrap().token).unwrap(),
118 );
119 }
120 if !headers.contains_key("Accept-Language") {
121 headers.insert(
122 "Accept-Language",
123 HeaderValue::from_str(&read.lang).unwrap(),
124 );
125 }
126
127 // Create the request to the PocketBase API
128 read.reqwest
129 .request(method, url)
130 .headers(headers)
131 .query(query)
132 };
133
134 // Add the body (as JSON) only if it's not None
135 let response = if let Some(body) = body {
136 request.json(body)
137 } else {
138 request
139 }
140 // Send the request and await the response
141 .send()
142 .await?;
143
144 // Parse the response body or return an error if something went wrong
145 response.error_for_status()?.json().await
146 }
147
148 /// Internal method to set the authentication store when using one of the
149 /// authentication methods.
150 fn with_auth_store(&self, auth: AuthStore) {
151 let mut write = self.inner.write().unwrap();
152 write.auth_store = Some(auth);
153 }
154
155 /// Set the language code that will be sent with the requests to the server as
156 /// `Accept-Language` header.
157 ///
158 /// Will be`en-US`, if not set.
159 pub fn with_lang(self, lang: String) -> Self {
160 {
161 let mut write = self.inner.write().unwrap();
162 write.lang = lang;
163 }
164 self
165 }
166}