#![deny(missing_docs)]
#![warn(clippy::nursery)]
#![warn(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(dead_code)]
pub use error::*;
pub use records::auth::{AuthStore, AuthStoreRecord};
use reqwest::RequestBuilder;
pub use reqwest::multipart::{Form, Part};
use serde::{Deserialize, Serialize};
pub mod error;
pub(crate) mod records;
pub struct Collection<'a> {
pub(crate) client: &'a mut PocketBase,
pub(crate) name: &'a str,
}
impl PocketBase {
pub fn collection(&mut self, collection_name: &'static str) -> Collection {
assert!(
!collection_name.is_empty(),
"Collection name cannot be empty"
);
assert!(
collection_name
.chars()
.all(|c| c.is_alphanumeric() || c == '_'),
"Collection name contains invalid characters. Only alphanumeric characters and underscores are allowed"
);
Collection {
client: self,
name: collection_name,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecordList<T> {
pub page: i32,
pub per_page: i32,
pub total_items: i32,
pub total_pages: i32,
pub items: Vec<T>,
}
#[derive(Deserialize, Debug)]
pub(crate) struct ErrorResponse {
pub code: u16,
pub message: String,
pub data: Option<serde_json::Value>,
}
#[derive(Clone)]
pub struct PocketBase {
pub(crate) base_url: String,
pub(crate) auth_store: Option<AuthStore>,
pub(crate) reqwest_client: reqwest::Client,
}
impl std::fmt::Debug for PocketBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PocketBase")
.field("base_url", &self.base_url)
.field(
"auth_store",
&self.auth_store.as_ref().map(|_| "***REDACTED***"),
)
.field("reqwest_client", &"Client")
.finish()
}
}
impl PocketBase {
#[must_use]
pub fn new(base_url: &str) -> Self {
let trimmed_url = base_url.trim_end_matches('/');
assert!(
trimmed_url.starts_with("http://") || trimmed_url.starts_with("https://"),
"Invalid base_url: must start with http:// or https://"
);
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.connect_timeout(std::time::Duration::from_secs(10))
.build()
.expect("Failed to create HTTP client");
Self {
base_url: trimmed_url.to_string(),
auth_store: None,
reqwest_client: client,
}
}
#[must_use]
pub fn new_with_client(base_url: &str, client: reqwest::Client) -> Self {
let trimmed_url = base_url.trim_end_matches('/');
assert!(
trimmed_url.starts_with("http://") || trimmed_url.starts_with("https://"),
"Invalid base_url: must start with http:// or https://"
);
Self {
base_url: trimmed_url.to_string(),
auth_store: None,
reqwest_client: client,
}
}
#[must_use]
pub fn auth_store(&self) -> Option<AuthStore> {
self.auth_store.clone()
}
#[must_use]
pub fn token(&self) -> Option<String> {
self.auth_store
.as_ref()
.map(|auth_store| auth_store.token.clone())
}
#[must_use]
pub fn base_url(&self) -> String {
self.base_url.clone()
}
pub(crate) fn update_auth_store(&mut self, new_auth_store: AuthStore) {
self.auth_store = Some(new_auth_store);
}
}
impl PocketBase {
pub(crate) fn with_authorization_token(
&self,
request_builder: reqwest::RequestBuilder,
) -> reqwest::RequestBuilder {
if let Some(auth_store) = self.auth_store() {
request_builder.bearer_auth(auth_store.token)
} else {
request_builder
}
}
pub(crate) fn request_post(&self, endpoint: &str) -> RequestBuilder {
let request_builder = self.reqwest_client.post(endpoint);
self.with_authorization_token(request_builder)
}
pub(crate) fn request_patch_json<T: Default + Serialize + Clone + Send>(
&self,
endpoint: &str,
params: &T,
) -> RequestBuilder {
let request_builder = self.reqwest_client.patch(endpoint).json(¶ms);
self.with_authorization_token(request_builder)
}
pub(crate) fn request_post_json<T: Default + Serialize + Clone + Send>(
&self,
endpoint: &str,
params: &T,
) -> RequestBuilder {
let request_builder = self.reqwest_client.post(endpoint).json(¶ms);
self.with_authorization_token(request_builder)
}
pub(crate) fn request_post_form(&self, endpoint: &str, form: Form) -> RequestBuilder {
let request_builder = self.reqwest_client.post(endpoint).multipart(form);
self.with_authorization_token(request_builder)
}
pub(crate) fn request_get(
&self,
endpoint: &str,
params: Option<Vec<(&str, &str)>>,
) -> RequestBuilder {
let mut request_builder = self
.reqwest_client
.get(endpoint)
.header("Accept", "application/json");
if let Some(params) = params {
request_builder = request_builder.query(¶ms);
}
self.with_authorization_token(request_builder)
}
pub(crate) fn request_delete(&self, endpoint: &str) -> RequestBuilder {
let request_builder = self.reqwest_client.delete(endpoint);
self.with_authorization_token(request_builder)
}
}