use std::sync::{Arc, RwLock}; use reqwest::{ Client, Method, header::{HeaderMap, HeaderValue}, }; use serde::{Serialize, de::DeserializeOwned}; use serde_json::{Map, Value}; use thiserror::Error; use url::Url; pub mod auth; pub mod health; pub mod record; use crate::{auth::AuthStore, health::HealthService, record::RecordService}; /// Type alias for reqwest's error type, that should be generally used if an /// error is returned. type Error = reqwest::Error; /// Type alias for a type that can represent general JSON objects. type JsonObject = Map; #[derive(Clone, Debug)] /// Main PocketBase API client. pub struct PocketBase { inner: Arc>, } #[derive(Clone, Debug)] /// Inner storage for the PocketBase API client. struct PocketBaseInner { /// Storage of the authenticated user and the authenticated token, if any. auth_store: Option, /// The PocketBase backend base url address (e.g. `http://127.0.0.1:8090`). base_url: Url, /// Language code that will be sent with the requests to the server as /// `Accept-Language` header. /// /// Will be set to `en-US` by default. lang: String, /// Reqwest [`Client`] used. reqwest: Client, } impl PocketBase { /// Clears the previously stored token and record auth data. pub fn auth_clear<'a>(&'a self) { let mut write = self.inner.write().unwrap(); write.auth_store = None; } /// Returns the [`RecordService`] associated to the specified collection. pub fn collection<'a>(&'a self, collection: &'a str) -> RecordService<'a> { RecordService { client: self, collection, } } /// Return an instance of the service that handles the Health APIs. pub fn health(&self) -> HealthService { HealthService { client: self, fields: None, } } /// Get the language code that will be sent with the requests to the server as /// `Accept-Language` header. pub fn lang(&self) -> String { self.inner.read().unwrap().lang.clone() } /// Create a new [`PocketBase`] instance using the provided base URL for the /// PocketBase instance (e.g. `http://127.0.0.1:8090`). pub fn new(base_url: &str) -> Result { let base_url = Url::parse(base_url)?; let inner = PocketBaseInner { reqwest: Client::new(), base_url, lang: "en_US".to_owned(), auth_store: None, }; Ok(Self { inner: Arc::new(RwLock::new(inner)), }) } /// Internal method used to send requests to the Pockatbase instance. async fn send( &self, path: &str, method: Method, headers: HeaderMap, query: &Q, body: Option<&B>, ) -> Result where Q: Serialize + ?Sized, B: Serialize + ?Sized, T: DeserializeOwned, { // Get a reading lock on the inner data let request = { let read = self.inner.read().unwrap(); // Create the full URL let mut url = read.base_url.clone(); url.set_path(path); let mut headers = headers; if !headers.contains_key("Authorization") && read.auth_store.is_some() { headers.insert( "Authorization", HeaderValue::from_str(&read.auth_store.as_ref().unwrap().token).unwrap(), ); } if !headers.contains_key("Accept-Language") { headers.insert( "Accept-Language", HeaderValue::from_str(&read.lang).unwrap(), ); } // Create the request to the PocketBase API read.reqwest .request(method, url) .headers(headers) .query(query) }; // Add the body (as JSON) only if it's not None let response = if let Some(body) = body { request.json(body) } else { request } // Send the request and await the response .send() .await?; // Parse the response body or return an error if something went wrong response.error_for_status()?.json().await } /// Internal method to set the authentication store when using one of the /// authentication methods. fn with_auth_store(&self, auth: AuthStore) { let mut write = self.inner.write().unwrap(); write.auth_store = Some(auth); } /// Set the language code that will be sent with the requests to the server as /// `Accept-Language` header. /// /// Will be`en-US`, if not set. pub fn with_lang(self, lang: String) -> Self { { let mut write = self.inner.write().unwrap(); write.lang = lang; } self } }