Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
17 kB · 708 lines
Rust
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD};use reqwest::{Method, header::HeaderMap};use serde::{Deserialize, Serialize, de::DeserializeOwned};use serde_json::{Map, Value, json};
use crate::{ Error, JsonObject, PocketBase, auth::{ AuthMethodList, AuthRefreshBuilder, AuthWithOtpBuilder, AuthWithPasswordBuilder, ImpersonateBuilder, },};
const NO_PARAMS: &[&str] = &[];
pub struct CreateRequestBuilder<'a, T: Serialize> { record_service: RecordService<'a>, body: T,}
pub struct DeleteRequestBuilder<'a> { record_service: RecordService<'a>, id: &'a str,}
#[derive(Clone, Debug)]pub struct ListRequestBuilder<'a> { record_service: RecordService<'a>, expand_fields: Option<&'a str>, filter_fields: Option<&'a str>, filter_records: Option<&'a str>, page: usize, per_page: usize, skip_total: Option<bool>, sort: Option<&'a str>,}
#[derive(Clone, Debug)]pub struct FullListRequestBuilder<'a> { record_service: RecordService<'a>, batch: Option<usize>, expand_fields: Option<&'a str>, filter_fields: Option<&'a str>, filter_records: Option<&'a str>, skip_total: Option<bool>, order_by: Option<&'a str>,}
#[derive(Clone, Debug)]pub struct RecordService<'a> { pub(super) client: &'a PocketBase, pub(super) collection: &'a str,}
#[derive(Clone, Debug, Default, Deserialize, Eq, Serialize, PartialEq)]#[serde(rename_all = "camelCase")]pub struct ResultList<T> { items: Vec<T>, page: usize, per_page: usize, total_items: isize, total_pages: isize,}
#[derive(Clone, Debug)]pub struct ViewRequestBuilder<'a> { record_service: RecordService<'a>, expand_fields: Option<&'a str>, filter_fields: Option<&'a str>, id: &'a str,}
#[derive(Clone, Debug)]pub struct UpdateRequestBuilder<'a, T: Serialize> { record_service: RecordService<'a>, body: T, expand_fields: Option<&'a str>, filter_fields: Option<&'a str>, id: &'a str,}
impl<'a, T: Serialize + Clone> CreateRequestBuilder<'a, T> { pub fn object<X: Serialize + Clone>(self, obj: X) -> CreateRequestBuilder<'a, X> { CreateRequestBuilder { record_service: self.record_service, body: obj, } }
pub async fn send<R: DeserializeOwned>(self) -> Result<R, Error> { let params: &[()] = &[];
self.record_service .client .send( &format!( "/api/collections/{}/records", self.record_service.collection ), Method::POST, HeaderMap::new(), ¶ms, Some(&self.body), ) .await }}
impl<T: Extend<(String, Value)> + Serialize> CreateRequestBuilder<'_, T> { pub fn fields<Iter: IntoIterator<Item = (String, Value)>>(mut self, patch: Iter) -> Self { self.body.extend(patch); self }}
impl DeleteRequestBuilder<'_> { pub async fn send<T: DeserializeOwned>(self) -> Result<T, Error> { let params: &[()] = &[]; self.record_service .client .send( &format!( "/api/collections/{}/records/{}", self.record_service.collection, self.id ), Method::DELETE, HeaderMap::new(), ¶ms, None::<&()>, ) .await // TODO: Dart SDK also implements the following behaviour. // // If the current [`AuthStore.record`] matches with the deleted id, then on // success the client [`AuthStore`] will be also cleared. }}
impl<'a> FullListRequestBuilder<'a> { pub fn batch_size(mut self, batch: usize) -> Self { self.batch = Some(batch); self }
pub fn expand_fields(mut self, field_expression: &'a str) -> Self { self.expand_fields = Some(field_expression); self }
pub fn filter_fields(mut self, field_expression: &'a str) -> Self { self.filter_fields = Some(field_expression); self }
pub fn filter_records(mut self, filter_expression: &'a str) -> Self { self.filter_records = Some(filter_expression); self }
pub fn skip_total(mut self, skip_total: bool) -> Self { self.skip_total = Some(skip_total); self }
pub fn order_by(mut self, sort_expression: &'a str) -> Self { self.order_by = Some(sort_expression); self }
pub async fn send<T: DeserializeOwned>(self) -> Result<Vec<T>, Error> { let mut list = ListRequestBuilder { record_service: self.record_service, expand_fields: self.expand_fields, filter_fields: self.filter_fields, filter_records: self.filter_records, page: 1, per_page: self.batch.unwrap_or(500), skip_total: Some(true), sort: self.order_by, };
let mut ret = Vec::new(); loop { let page = list.clone().send().await?; let persist = page.items.len() == list.per_page; ret.extend(page.items); if !persist { break; } list.page += 1; } Ok(ret) }}
impl<'a> ListRequestBuilder<'a> { pub fn expand_fields(mut self, field_expression: &'a str) -> Self { self.expand_fields = Some(field_expression); self }
pub fn filter_fields(mut self, field_expression: &'a str) -> Self { self.filter_fields = Some(field_expression); self }
pub fn filter(mut self, filter: &'a str) -> Self { self.filter_records = Some(filter); self }
pub fn skip_total(mut self, skip_total: bool) -> Self { self.skip_total = Some(skip_total); self }
pub fn sort(mut self, sort: &'a str) -> Self { self.sort = Some(sort); self }
pub async fn send<T: DeserializeOwned>(self) -> Result<ResultList<T>, Error> { let mut storage = vec![self.page.to_string(), self.per_page.to_string()]; if let Some(skip_total) = self.skip_total { storage.push(skip_total.to_string()); } let mut params: Vec<(&str, &str)> = vec![("page", &storage[0]), ("perPage", &storage[1])]; if let Some(filter) = self.filter_records { params.push(("filter", filter)); } if let Some(sort) = self.sort { params.push(("sort", sort)); } if let Some(expand) = self.expand_fields { params.push(("expand", expand)); } if let Some(fields) = self.filter_fields { params.push(("fields", fields)); } if self.skip_total.is_some() { params.push(("skipTotal", &storage[2])); }
self.record_service .client .send( &format!( "/api/collections/{}/records", self.record_service.collection ), Method::GET, HeaderMap::new(), ¶ms, None::<&()>, ) .await }}
impl<'a> RecordService<'a> { /// Refreshes the current authenticated auth record instance and returns a new /// token and record data. /// /// On success this method automatically updates the client's AuthStore. pub fn auth_refresh(&self) -> AuthRefreshBuilder<'a> { AuthRefreshBuilder { record_service: self.clone(), expand_fields: None, filter_fields: None, } }
/// Authenticate an auth record via OTP. /// /// On success this method automatically updates the client's [`AuthStore`]. pub fn auth_with_otp(&'a self, otp_id: &'a str, password: &'a str) -> AuthWithOtpBuilder<'a> { AuthWithOtpBuilder { record_service: self.clone(), otp_id, password, expand_fields: None, filter_fields: None, } }
/// Authenticate an auth record by its username/email and password and returns /// a new auth token and record data. /// /// On success this method automatically updates the client's [`AuthStore`]. pub fn auth_with_password( &self, identity: &'a str, password: &'a str, ) -> AuthWithPasswordBuilder<'a> { AuthWithPasswordBuilder { record_service: self.clone(), identity, password, expand_fields: None, filter_fields: None, } }
/// Confirms auth record new email address. /// /// If the current [`AuthStore.record`] matches with the record from the /// token, then on success the client [`AuthStore`] will be also cleared. pub async fn confirm_email_change(&self, token: &str, password: &str) -> Result<(), Error> { let () = self .client .send( &format!("/api/collections/{}/confirm-email-change", self.collection), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "token": token, "password": password })), ) .await?;
let parts: Vec<_> = token.split(".").collect(); if parts.len() != 3 { return Ok(()); } let Ok(payload_str) = String::from_utf8(BASE64_STANDARD_NO_PAD.decode(parts[1]).unwrap()) else { return Ok(()); }; let Ok(payload) = serde_json::from_str::<JsonObject>(&payload_str) else { return Ok(()); };
{ let inner = self.client.inner.read().unwrap(); if let Some(auth) = inner.auth_store.as_ref() { if auth.record.data.get("id") == payload.get("id") && auth.record.data.get("collectionId") == payload.get("collectionId") { self.client.auth_clear(); } } } Ok(()) }
/// Confirms auth record password reset request. pub async fn confirm_password_reset( &self, token: &str, password: &str, password_confirm: &str, ) -> Result<(), Error> { self.client .send( &format!( "/api/collections/{}/confirm-password-reset", self.collection ), Method::POST, HeaderMap::new(), NO_PARAMS, Some( &json!({ "token": token, "password": password, "passwordConfirm": password_confirm }), ), ) .await }
/// Confirms auth record email verification request. /// /// On success this method automatically updates the client's [`AuthStore`]. pub async fn confirm_verification(&self, token: &str) -> Result<(), Error> { let () = self .client .send( &format!("/api/collections/{}/confirm-verification", self.collection), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "token": token })), ) .await?;
let parts: Vec<_> = token.split(".").collect(); if parts.len() != 3 { return Ok(()); } let Ok(payload_str) = String::from_utf8(BASE64_STANDARD_NO_PAD.decode(parts[1]).unwrap()) else { return Ok(()); }; let Ok(payload) = serde_json::from_str::<JsonObject>(&payload_str) else { return Ok(()); };
{ let mut inner = self.client.inner.write().unwrap(); if let Some(auth) = inner.auth_store.as_mut() { if !auth .record .data .get("verified") .map(|v| v.as_bool().unwrap_or(false)) .unwrap_or(false) && auth.record.data.get("id") == payload.get("id") && auth.record.data.get("collectionId") == payload.get("collectionId") { let _ = auth.record.data.insert("verified".to_string(), true.into()); } } } Ok(()) }
/// Creates a new item. pub fn create(&self) -> CreateRequestBuilder<'a, JsonObject> { CreateRequestBuilder { record_service: self.clone(), body: Map::new(), } }
/// Deletes a single record model by its id. pub fn delete(&self, id: &'a str) -> DeleteRequestBuilder<'a> { DeleteRequestBuilder { record_service: self.clone(), id, } }
/// Returns a list with all items batch fetched at once. pub fn get_full_list(&self) -> FullListRequestBuilder<'a> { FullListRequestBuilder { record_service: self.clone(), batch: None, filter_records: None, order_by: None, expand_fields: None, filter_fields: None, skip_total: None, } }
/// Returns paginated items list. pub fn get_list(&self, page: usize, per_page: usize) -> ListRequestBuilder<'a> { ListRequestBuilder { record_service: self.clone(), page, per_page, filter_records: None, sort: None, expand_fields: None, filter_fields: None, skip_total: None, } }
/// Returns single item by its id. /// /// Throws `404` [`Error`] in case an empty id is provided. pub fn get_one(&'a self, id: &'a str) -> ViewRequestBuilder<'a> { ViewRequestBuilder { record_service: self.clone(), id, expand_fields: None, filter_fields: None, } }
/// Authenticates with the specified recordId and returns a new client with /// the received auth token in a memory store. /// /// This action currently requires superusers privileges. pub fn impersonate(&'a self, id: &'a str) -> ImpersonateBuilder<'a> { ImpersonateBuilder { record_service: self.clone(), id, duration: None, expand_fields: None, filter_fields: None, } }
/// Returns all available application auth methods. pub async fn list_auth_methods(&'a self) -> Result<AuthMethodList, Error> { self.client .send( &format!("/api/collections/{}/auth-methods", self.collection), Method::GET, HeaderMap::new(), NO_PARAMS, None::<&()>, ) .await }
/// Sends auth record email change request to the provided email. pub async fn request_email_change(&self, new_email: &str) -> Result<(), Error> { self.client .send( &format!("/api/collections/{}/request-email-change", self.collection), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "newEmail": new_email })), ) .await }
/// Sends auth record OTP request to the provided email. pub async fn request_otp(&self, email: &str) -> Result<String, Error> { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct OtpIdResponse { otp_id: String, }
let response: OtpIdResponse = self .client .send( &format!("/api/collections/{}/request-otp", self.collection), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "email": email })), ) .await?;
Ok(response.otp_id) }
/// Sends auth record password reset request. pub async fn request_password_reset(&self, email: &str) -> Result<(), Error> { self.client .send( &format!( "/api/collections/{}/request-password-reset", self.collection ), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "email": email })), ) .await }
/// Sends auth record verification email request. pub async fn request_verification(&self, email: &str) -> Result<String, Error> { self.client .send( &format!("/api/collections/{}/request-verification", self.collection), Method::POST, HeaderMap::new(), NO_PARAMS, Some(&json!({ "email": email })), ) .await }
/// Updates a single record model by its id. pub fn update(&self, id: &'a str) -> UpdateRequestBuilder<'a, JsonObject> { UpdateRequestBuilder { record_service: self.clone(), id, expand_fields: None, filter_fields: None, body: Map::new(), } }}
impl<T> ResultList<T> { pub fn items(&self) -> &[T] { &self.items }
pub fn into_vec(self) -> Vec<T> { self.items }
pub fn page(&self) -> usize { self.page }
pub fn per_page(&self) -> usize { self.per_page }}
impl<T> IntoIterator for ResultList<T> { type Item = T; type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter { self.items.into_iter() }}
impl<'a, T> IntoIterator for &'a ResultList<T> { type Item = &'a T; type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter { self.items.iter() }}
impl<'a, T> IntoIterator for &'a mut ResultList<T> { type Item = &'a mut T; type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter { self.items.iter_mut() }}
impl<'a> ViewRequestBuilder<'a> { pub fn expand_fields(mut self, field_expression: &'a str) -> Self { self.expand_fields = Some(field_expression); self }
pub fn filter_fields(mut self, field_expression: &'a str) -> Self { self.filter_fields = Some(field_expression); self }
pub async fn send<T: DeserializeOwned>(self) -> Result<T, Error> { let mut params = Vec::new(); if let Some(expand) = self.expand_fields { params.push(("expand", expand)); } if let Some(fields) = self.filter_fields { params.push(("fields", fields)); }
self.record_service .client .send( &format!( "/api/collections/{}/records/{}", self.record_service.collection, self.id ), Method::GET, HeaderMap::new(), ¶ms, None::<&()>, ) .await }}
impl<'a, T: Serialize> UpdateRequestBuilder<'a, T> { pub fn expand_fields(mut self, field_expression: &'a str) -> Self { self.expand_fields = Some(field_expression); self }
pub fn filter_fields(mut self, field_expression: &'a str) -> Self { self.filter_fields = Some(field_expression); self }
pub fn object<X: Serialize>(self, obj: X) -> UpdateRequestBuilder<'a, X> { UpdateRequestBuilder { record_service: self.record_service, body: obj, expand_fields: self.expand_fields, filter_fields: self.filter_fields, id: self.id, } }
pub async fn send<R: DeserializeOwned>(self) -> Result<R, Error> { let mut params = Vec::new(); if let Some(expand) = self.expand_fields { params.push(("expand", expand)); } if let Some(fields) = self.filter_fields { params.push(("fields", fields)); }
self.record_service .client .send( &format!( "/api/collections/{}/records/{}", self.record_service.collection, self.id ), Method::PATCH, HeaderMap::new(), ¶ms, Some(&self.body), ) .await // TODO: The following behaviour is also implemented in the Dart SDK. // // If the current [`AuthStore.record`] matches with the updated id, then on // success the client [`AuthStore`] will be updated with the result model. }}
impl<T: Extend<(String, Value)> + Serialize> UpdateRequestBuilder<'_, T> { pub fn fields<Iter: IntoIterator<Item = (String, Value)>>(mut self, patch: Iter) -> Self { self.body.extend(patch); self }}