use std::sync::Arc;
use crate::resource::{Resource, ResourceMethod};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use deboa::{
client::serde::RequestBody,
errors::{DeboaError, RequestError},
request::DeboaRequest,
response::DeboaResponse,
url::IntoUrl,
Client, Result,
};
use http::{
header::{self, CONTENT_TYPE, HOST},
HeaderMap, HeaderName, HeaderValue, Method,
};
use serde::Serialize;
use url::Url;
pub mod resource;
#[cfg(test)]
mod tests;
pub struct Vamo {
client: Client,
base_url: Url,
method: Method,
path: String,
headers: HeaderMap,
body: Arc<[u8]>,
}
impl Vamo {
pub fn new<U: IntoUrl>(url: U) -> Result<Vamo> {
let base_url = url.into_url()?;
let mut headers = HeaderMap::new();
let host = base_url.host_str();
if host.is_none() {
return Err(DeboaError::Request(RequestError::UrlParse {
message: "Invalid URL: Missing host.".to_string(),
}));
}
let host_header = HeaderValue::from_str(
base_url
.host_str()
.unwrap(),
);
if let Err(e) = host_header {
return Err(DeboaError::Header { message: e.to_string() });
}
headers.insert(HOST, host_header.unwrap());
let content_type_header = HeaderValue::from_str("application/json");
if let Err(e) = content_type_header {
return Err(DeboaError::Header { message: e.to_string() });
}
headers.insert(CONTENT_TYPE, content_type_header.unwrap());
Ok(Vamo {
client: Client::default(),
base_url,
path: String::new(),
method: Method::GET,
headers,
body: Arc::new([]),
})
}
#[inline]
pub fn client(&mut self, client: Client) -> &mut Self {
self.client = client;
self
}
#[inline]
pub fn header(&mut self, key: HeaderName, value: &str) -> &mut Self {
self.headers
.insert(key, HeaderValue::from_str(value).unwrap());
self
}
#[inline]
pub fn body_as<T: RequestBody, B: Serialize>(
&mut self,
body_type: T,
body: B,
) -> Result<&mut Self> {
self.body = body_type
.serialize(body)?
.into();
Ok(self)
}
#[inline]
pub fn get(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self.method = Method::GET;
self
}
#[inline]
pub fn post(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self.method = Method::POST;
self
}
#[inline]
pub fn put(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self.method = Method::PUT;
self
}
#[inline]
pub fn patch(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self.method = Method::PATCH;
self
}
#[inline]
pub fn delete(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self.method = Method::DELETE;
self
}
#[inline]
pub fn bearer_auth(&mut self, token: &str) -> &mut Self {
self.header(header::AUTHORIZATION, format!("Bearer {token}").as_str());
self
}
#[inline]
pub fn basic_auth(&mut self, username: &str, password: &str) -> &mut Self {
self.header(
header::AUTHORIZATION,
format!("Basic {}", STANDARD.encode(format!("{username}:{password}"))).as_str(),
);
self
}
#[inline]
pub async fn send(&mut self) -> Result<DeboaResponse> {
let mut base_url = self
.base_url
.clone();
let path_and_query = self
.path
.split_once('?');
let path = if let Some((path, query)) = path_and_query {
base_url.set_query(Some(query));
path
} else {
&self.path
};
let base_path = self.base_url.path();
if base_path == "/" {
base_url.set_path(path);
} else {
base_url.set_path(&format!("{}{}", base_path, path));
}
let request = DeboaRequest::from(base_url.as_str())?
.method(self.method.clone())
.headers(self.headers.clone())
.bytes(&self.body)
.build()?;
self.client
.execute(request)
.await
}
}
impl<R: Resource + Serialize> ResourceMethod<R> for Vamo {
fn load(&mut self, resource: &mut R) -> Result<&mut Self> {
self.path = format!("/{}/{}", resource.name(), resource.id());
self.method = Method::GET;
Ok(self)
}
fn create(&mut self, resource: &mut R) -> Result<&mut Self> {
self.path = format!("/{}", resource.name());
self.method = Method::POST;
self.body = resource
.body_type()
.serialize(&resource)?
.into();
Ok(self)
}
fn update(&mut self, resource: &mut R) -> Result<&mut Self> {
self.path = format!("/{}/{}", resource.name(), resource.id());
self.method = Method::PUT;
self.body = resource
.body_type()
.serialize(&resource)?
.into();
Ok(self)
}
fn edit(&mut self, resource: &mut R) -> Result<&mut Self> {
self.path = format!("/{}/{}", resource.name(), resource.id());
self.method = Method::PATCH;
self.body = resource
.body_type()
.serialize(&resource)?
.into();
Ok(self)
}
fn remove(&mut self, resource: &mut R) -> Result<&mut Self> {
self.path = format!("/{}/{}", resource.name(), resource.id());
self.method = Method::DELETE;
Ok(self)
}
}