use async_trait::async_trait;
use http::Method;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Body, IntoUrl,
};
use std::error::Error;
#[cfg(feature = "blocking")]
use std::rc::Rc;
use std::{fmt::Display, sync::Arc, time::Duration};
use crate::auth::AuthError;
#[cfg(feature = "blocking")]
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
use crate::{
client::Client as AliClient,
config::{BucketBase, InvalidConfig},
errors::OssService,
};
use reqwest::{Client, Request, Response};
#[cfg(test)]
pub(crate) mod test;
pub trait PointerFamily
where
Self::Bucket: std::fmt::Debug + Clone + Default,
{
type PointerType;
type Bucket;
}
#[derive(Default, Debug)]
pub struct ArcPointer;
impl PointerFamily for ArcPointer {
type PointerType = Arc<AliClient<ClientWithMiddleware>>;
type Bucket = Arc<BucketBase>;
}
#[cfg(feature = "blocking")]
#[derive(Default, Debug)]
pub struct RcPointer;
#[cfg(feature = "blocking")]
impl PointerFamily for RcPointer {
type PointerType = Rc<AliClient<BlockingClientWithMiddleware>>;
type Bucket = Rc<BucketBase>;
}
#[derive(Default, Clone)]
pub struct ClientWithMiddleware {
inner: Client,
middleware: Option<Arc<dyn Middleware>>,
}
#[async_trait]
pub trait Middleware: 'static + Send + Sync {
async fn handle(&self, request: Request) -> Result<Response, BuilderError>;
}
impl ClientWithMiddleware {
pub fn new(inner: Client) -> Self {
Self {
inner,
middleware: None,
}
}
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
RequestBuilder {
inner: self.inner.request(method, url),
middleware: self.middleware.clone(),
}
}
pub fn middleware(&mut self, middleware: Arc<dyn Middleware>) {
self.middleware = Some(middleware);
}
}
pub struct RequestBuilder {
inner: reqwest::RequestBuilder,
middleware: Option<Arc<dyn Middleware>>,
}
impl RequestBuilder {
#[allow(dead_code)]
pub(crate) fn header<K, V>(self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
RequestBuilder {
inner: self.inner.header(key, value),
..self
}
}
pub(crate) fn headers(self, headers: HeaderMap) -> Self {
RequestBuilder {
inner: self.inner.headers(headers),
..self
}
}
pub(crate) fn body<T: Into<Body>>(self, body: T) -> Self {
RequestBuilder {
inner: self.inner.body(body),
..self
}
}
pub(crate) fn timeout(self, timeout: Duration) -> Self {
RequestBuilder {
inner: self.inner.timeout(timeout),
..self
}
}
#[allow(dead_code)]
pub(crate) fn build(self) -> reqwest::Result<Request> {
self.inner.build()
}
pub async fn send(self) -> Result<Response, BuilderError> {
match self.middleware {
Some(m) => {
m.handle(self.inner.build().map_err(BuilderError::from)?)
.await
}
None => self.inner.send().await.map_err(BuilderError::from),
}
}
pub async fn send_adjust_error(self) -> Result<Response, BuilderError> {
match self.middleware {
Some(m) => {
m.handle(self.inner.build().map_err(BuilderError::from)?)
.await
}
None => check_http_status(self.inner.send().await.map_err(BuilderError::from)?)
.await
.map_err(BuilderError::from),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct BuilderError {
pub(crate) kind: BuilderErrorKind,
}
impl BuilderError {
#[cfg(test)]
pub(crate) fn bar() -> Self {
Self {
kind: BuilderErrorKind::Bar,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub(crate) enum BuilderErrorKind {
Reqwest(Box<reqwest::Error>),
OssService(Box<OssService>),
Auth(Box<AuthError>),
Config(Box<InvalidConfig>),
#[cfg(test)]
Bar,
}
impl Display for BuilderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use BuilderErrorKind::*;
match &self.kind {
Reqwest(_) => "reqwest error".fmt(f),
OssService(_) => "http status is not success".fmt(f),
Auth(_) => "aliyun auth failed".fmt(f),
Config(_) => "oss config error".fmt(f),
#[cfg(test)]
Bar => "bar".fmt(f),
}
}
}
impl Error for BuilderError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use BuilderErrorKind::*;
match &self.kind {
Reqwest(e) => Some(e),
OssService(e) => Some(e),
Auth(e) => Some(e),
Config(e) => Some(e),
#[cfg(test)]
Bar => None,
}
}
}
impl From<reqwest::Error> for BuilderError {
fn from(value: reqwest::Error) -> Self {
Self {
kind: BuilderErrorKind::Reqwest(Box::new(value)),
}
}
}
impl From<OssService> for BuilderError {
fn from(value: OssService) -> Self {
Self {
kind: BuilderErrorKind::OssService(Box::new(value)),
}
}
}
impl From<AuthError> for BuilderError {
fn from(value: AuthError) -> Self {
Self {
kind: BuilderErrorKind::Auth(Box::new(value)),
}
}
}
impl From<InvalidConfig> for BuilderError {
fn from(value: InvalidConfig) -> Self {
Self {
kind: BuilderErrorKind::Config(Box::new(value)),
}
}
}
pub(crate) async fn check_http_status(response: Response) -> Result<Response, BuilderError> {
if response.status().is_success() {
return Ok(response);
}
let url = response.url().clone();
let status = response.status();
let text = response.text().await?;
Err(OssService::new2(text, &status, url).into())
}