A wrapper around reqwest to make working with Pocketbase a breeze
at develop 4.3 kB view raw
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}