use std::option::Option;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use lazy_static::lazy_static;
use reqwest::Client;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use serde_json::Value;
#[derive(Serialize, Deserialize, Debug)]
struct AdminResponse {
token: String,
admin: Admin,
}
#[derive(Serialize, Deserialize, Debug)]
struct Admin {
id: String,
created: String,
updated: String,
email: String,
avatar: u8,
}
#[derive(Deserialize)]
struct PbAuthError {
code: u16,
message: String,
data: ErrorData,
}
#[derive(Serialize, Deserialize, Debug)]
struct ErrorData {
password: Option<PasswordError>,
}
#[derive(Serialize, Deserialize, Debug)]
struct PasswordError {
code: String,
message: String,
}
#[derive(Debug, Clone)]
pub struct PbAdminClient {
url: String,
username: Option<String>,
password: Option<String>,
token: Option<String>,
}
#[derive(Debug)]
struct PbError {
detail: String
}
impl PbError {
fn new(msg: &str) -> PbError {
PbError { detail: msg.to_string() }
}
}
impl Display for PbError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.detail)
}
}
lazy_static! {
static ref REQ_CLIENT: Arc<Client> = Arc::new(Client::new());
}
impl Error for PbError {}
#[derive(Serialize, Debug)]
struct PasswordAuth {
identity: String,
password: String
}
impl PbAdminClient {
pub fn new(url: &str) -> Self {
PbAdminClient {
url: url.trim_end_matches("/").to_string(),
password: None,
username: None,
token: None,
}
}
pub async fn auth_with_password(mut self, username: &str, password: &str) -> Result<Self, Box<dyn Error>> {
self.password = Option::from(username.to_string());
self.username = Option::from(password.to_string());
let auth_input = PasswordAuth {
identity: username.to_string(),
password: password.to_string()
};
let req = REQ_CLIENT.post(format!("{}/api/admins/auth-with-password", self.url))
.json(&auth_input).send().await?;
let code = req.status();
if code == 400 {
let err: PbAuthError = req.json().await?;
return Err(Box::new(PbError::new(&err.message)))
}
let req: AdminResponse = req.json().await?;
self.token = Option::from(req.token);
return Ok(self);
}
pub fn collection(&self, collection: &str) -> Result<PbCollection, Box<dyn Error>> {
if self.token.is_none() {
return Err(Box::new(PbError::new("unauthenticated")));
}
let pbcol = PbCollection {
url: self.url.clone(),
token: self.token.clone().unwrap(),
collection: collection.to_string()
};
Ok(pbcol)
}
}
#[derive(Debug)]
pub struct PbCollection {
url: String,
token: String,
collection: String
}
impl PbCollection {
pub fn get_list(self, page: u16, per_page: u16) -> PbGetList {
PbGetList {
url: self.url,
token: self.token,
collection: self.collection,
page,
per_page,
sort: None,
filter: None,
expand: None,
fields: None,
skip_total: None,
}
}
pub fn get_one(self, record_id: &str) -> PbViewRecord {
PbViewRecord {
url: self.url,
token: self.token,
collection: self.collection,
expand: None,
fields: None,
record_id: record_id.to_string(),
}
}
pub fn create<T: Serialize>( self, record: T) -> PbCreateRecord<T> {
PbCreateRecord {
url: self.url,
token: self.token,
collection: self.collection,
record,
expand: None,
fields: None
}
}
pub fn update<T: Serialize>( self, record_id: &str, record: T) -> PbUpdateRecord<T> {
PbUpdateRecord {
url: self.url,
token: self.token,
collection: self.collection,
record,
expand: None,
fields: None,
record_id: record_id.to_string(),
}
}
pub fn delete( self, record_id: &str) -> PbDeleteRecord {
PbDeleteRecord {
url: self.url,
token: self.token,
collection: self.collection,
record_id: record_id.to_string()
}
}
}
pub struct PbDeleteRecord {
url: String,
token: String,
collection: String,
record_id: String,
}
impl PbDeleteRecord {
pub async fn call (self) -> Result<(), Box<dyn Error>> {
let req = REQ_CLIENT.delete(format!("{}/api/collections/{}/records/{}", self.url, self.collection, self.record_id))
.header("authorization", format!("Bearer {}", self.token))
.send().await?;
if req.status() == 204 {
Ok(())
}else {
let obj: GetListFail = req.json().await?;
Err(obj.into())
}
}
}
pub struct PbUpdateRecord<T> {
url: String,
token: String,
collection: String,
record: T,
expand: Option<String>,
fields: Option<String>,
record_id: String
}
impl<T: Serialize> PbUpdateRecord<T> {
pub fn expand(mut self, expand: &str) -> Self {
self.expand = Option::from(expand.to_string());
self
}
pub fn fields(mut self, fields: &str) -> Self {
self.fields = Option::from(fields.to_string());
self
}
pub async fn call<Y: DeserializeOwned> (self) -> Result<Y, Box<dyn Error>> {
let mut query: String = "?".to_string();
if self.expand.is_some() {
if query.eq("?"){
query += &format!("expand={}", self.expand.unwrap());
}else {
query += &format!("&expand={}", self.expand.unwrap());
}
}
if self.fields.is_some() {
if query.eq("?"){
query += &format!("fields={}", self.fields.unwrap());
}else {
query += &format!("&fields={}", self.fields.unwrap());
}
}
let req = REQ_CLIENT.patch(format!("{}/api/collections/{}/records/{}{}", self.url, self.collection, self.record_id, query))
.header("authorization", format!("Bearer {}", self.token))
.json(&self.record)
.send().await?;
if req.status() == 200 {
Ok(req.json().await?)
}else {
let obj: GetListFail = req.json().await?;
Err(obj.into())
}
}
}
pub struct PbCreateRecord<T> {
url: String,
token: String,
collection: String,
record: T,
expand: Option<String>,
fields: Option<String>,
}
impl<T: Serialize> PbCreateRecord<T> {
pub fn expand(mut self, expand: &str) -> Self {
self.expand = Option::from(expand.to_string());
self
}
pub fn fields(mut self, fields: &str) -> Self {
self.fields = Option::from(fields.to_string());
self
}
pub async fn call<Y: DeserializeOwned> (self) -> Result<Y, Box<dyn Error>> {
let mut query: String = "?".to_string();
if self.expand.is_some() {
if query.eq("?"){
query += &format!("expand={}", self.expand.unwrap());
}else {
query += &format!("&expand={}", self.expand.unwrap());
}
}
if self.fields.is_some() {
if query.eq("?"){
query += &format!("fields={}", self.fields.unwrap());
}else {
query += &format!("&fields={}", self.fields.unwrap());
}
}
let req = REQ_CLIENT.post(format!("{}/api/collections/{}/records{}", self.url, self.collection, query))
.header("authorization", format!("Bearer {}", self.token))
.json(&self.record)
.send().await?;
if req.status() == 200 {
Ok(req.json().await?)
}else {
let obj: GetListFail = req.json().await?;
Err(obj.into())
}
}
}
pub struct PbViewRecord {
url: String,
token: String,
collection: String,
expand: Option<String>,
fields: Option<String>,
record_id: String,
}
impl PbViewRecord {
pub fn expand(mut self, expand: &str) -> Self {
self.expand = Option::from(expand.to_string());
self
}
pub fn fields(mut self, fields: &str) -> Self {
self.fields = Option::from(fields.to_string());
self
}
pub async fn call<T: DeserializeOwned> (self) -> Result<T, Box<dyn Error>> {
let mut query: String = "?".to_string();
if self.expand.is_some() {
if query.eq("?"){
query += &format!("expand={}", self.expand.unwrap());
}else {
query += &format!("&expand={}", self.expand.unwrap());
}
}
if self.fields.is_some() {
if query.eq("?"){
query += &format!("fields={}", self.fields.unwrap());
}else {
query += &format!("&fields={}", self.fields.unwrap());
}
}
let req = REQ_CLIENT.get(format!("{}/api/collections/{}/records/{}{}", self.url, self.collection, self.record_id, query))
.header("authorization", format!("Bearer {}", self.token))
.send().await?;
if req.status() == 200 {
Ok(req.json().await?)
}else {
let obj: GetListFail = req.json().await?;
Err(obj.into())
}
}
}
pub struct PbGetList {
url: String,
token: String,
collection: String,
page: u16,
per_page: u16,
sort: Option<String>,
filter: Option<String>,
expand: Option<String>,
fields: Option<String>,
skip_total: Option<bool>
}
impl PbGetList {
pub fn page(mut self, page: u16) -> Self {
self.page = page;
self
}
pub fn per_page(mut self, per_page: u16) -> Self {
self.per_page = per_page;
self
}
pub fn sort(mut self, sort: &str) -> Self {
self.sort = Option::from(sort.to_string());
self
}
pub fn filter(mut self, filter: &str) -> Self {
self.filter = Option::from(filter.to_string());
self
}
pub fn expand(mut self, expand: &str) -> Self {
self.expand = Option::from(expand.to_string());
self
}
pub fn fields(mut self, fields: &str) -> Self {
self.fields = Option::from(fields.to_string());
self
}
pub fn skip_total(mut self, skip_total: bool) -> Self {
self.skip_total = Option::from(skip_total);
self
}
pub async fn call<T: DeserializeOwned> (self) -> Result<GetListSuccess<T>, Box<dyn Error>> {
let mut query: String = "?".to_string();
query += &format!("page={}", self.page);
query += &format!("&perPage={}", self.per_page);
if self.sort.is_some() {
query += &format!("&sort={}", self.sort.unwrap());
}
if self.filter.is_some() {
query += &format!("&filter={}", self.filter.unwrap());
}
if self.expand.is_some() {
query += &format!("&expand={}", self.expand.unwrap());
}
if self.fields.is_some() {
query += &format!("&fields=({})", self.fields.unwrap());
}
if self.skip_total.is_some() {
query += &format!("&skipTotal={}", self.skip_total.unwrap());
}
let req = REQ_CLIENT.get(format!("{}/api/collections/{}/records{}", self.url, self.collection, query))
.header("authorization", format!("Bearer {}", self.token))
.send().await?;
if req.status() == 200 {
Ok(req.json().await?)
}else {
let obj: GetListFail = req.json().await?;
Err(obj.into())
}
}
}
#[derive(Deserialize, Debug, Serialize)]
pub struct GetListSuccess<T> {
pub page: u16,
#[serde(rename = "perPage")]
pub per_page: u16,
#[serde(rename = "totalItems")]
pub total_items: u16,
#[serde(rename = "totalPages")]
pub total_pages: u16,
pub items: Vec<T>
}
#[derive(Deserialize, Debug, Serialize)]
pub struct GetListFail {
pub code: u16,
pub message: String,
pub data: Value
}
impl Display for GetListFail {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for GetListFail {}
#[cfg(test)]
mod tests {}