use crate::auth::AuthBuilder;
#[cfg(feature = "blocking")]
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
#[cfg(test)]
use crate::builder::Middleware;
use crate::builder::{ArcPointer, BuilderError, ClientWithMiddleware, RequestBuilder};
use crate::config::{BucketBase, Config, InvalidConfig, InvalidObjectPath, ObjectBase, ObjectPath};
use crate::file::AlignBuilder;
use crate::types::{BucketName, CanonicalizedResource, EndPoint, KeyId, KeySecret};
use chrono::{DateTime, Utc};
use http::{
header::{HeaderMap, HeaderName},
HeaderValue, Method,
};
use reqwest::Url;
use std::env;
#[cfg(all(feature = "blocking", test))]
use std::rc::Rc;
#[cfg(test)]
use std::sync::Arc;
use std::time::Duration;
#[non_exhaustive]
#[derive(Default, Clone)]
pub struct Client<M = ClientWithMiddleware>
where
M: Default + Clone,
{
auth_builder: AuthBuilder,
client_middleware: M,
endpoint: EndPoint,
bucket: BucketName,
timeout: Option<Duration>,
}
impl<M: Default + Clone> AsMut<Option<Duration>> for Client<M> {
fn as_mut(&mut self) -> &mut Option<Duration> {
&mut self.timeout
}
}
impl<M: Default + Clone> AsRef<EndPoint> for Client<M> {
fn as_ref(&self) -> &EndPoint {
&self.endpoint
}
}
impl<M: Default + Clone> AsRef<BucketName> for Client<M> {
fn as_ref(&self) -> &BucketName {
&self.bucket
}
}
impl<M: Default + Clone> Client<M> {
pub fn new(
access_key_id: KeyId,
access_key_secret: KeySecret,
endpoint: EndPoint,
bucket: BucketName,
) -> Self {
let mut auth_builder = AuthBuilder::default();
auth_builder.key(access_key_id);
auth_builder.secret(access_key_secret);
Self::from_builder(auth_builder, endpoint, bucket)
}
pub fn from_config(config: Config) -> Self {
let (key, secret, bucket, endpoint) = config.get_all();
let mut auth_builder = AuthBuilder::default();
auth_builder.key(key);
auth_builder.secret(secret);
Self::from_builder(auth_builder, endpoint, bucket)
}
pub fn from_env() -> Result<Self, InvalidConfig> {
let key_id = env::var("ALIYUN_KEY_ID").map_err(InvalidConfig::from)?;
let key_secret = env::var("ALIYUN_KEY_SECRET").map_err(InvalidConfig::from)?;
let endpoint = env::var("ALIYUN_ENDPOINT").map_err(InvalidConfig::from)?;
let bucket = env::var("ALIYUN_BUCKET").map_err(InvalidConfig::from)?;
let mut auth_builder = AuthBuilder::default();
auth_builder.key(key_id);
auth_builder.secret(key_secret);
Ok(Self::from_builder(
auth_builder,
endpoint.try_into().map_err(InvalidConfig::from)?,
bucket.try_into().map_err(InvalidConfig::from)?,
))
}
#[doc(hidden)]
#[inline]
pub fn from_builder(auth_builder: AuthBuilder, endpoint: EndPoint, bucket: BucketName) -> Self {
Self {
auth_builder,
client_middleware: M::default(),
endpoint,
bucket,
timeout: None,
}
}
pub(crate) fn get_bucket_name(&self) -> &BucketName {
&self.bucket
}
pub fn get_bucket_base(&self) -> BucketBase {
BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned())
}
pub fn get_bucket_url(&self) -> Url {
self.get_bucket_base().to_url()
}
pub(crate) fn get_endpoint(&self) -> &EndPoint {
&self.endpoint
}
pub fn get_endpoint_url(&self) -> Url {
self.endpoint.to_url()
}
pub fn timeout(&mut self, timeout: Duration) {
self.timeout = Some(timeout);
}
}
#[cfg(not(test))]
#[inline]
fn now() -> DateTime<Utc> {
Utc::now()
}
#[cfg(test)]
fn now() -> DateTime<Utc> {
use chrono::NaiveDateTime;
let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap();
DateTime::from_utc(naive, Utc)
}
pub type ClientArc = Client<ClientWithMiddleware>;
impl Client {
#[cfg(test)]
pub(crate) fn middleware(mut self, middleware: Arc<dyn Middleware>) -> Self {
self.client_middleware.middleware(middleware);
self
}
#[inline]
pub fn get_object_base<P>(&self, path: P) -> Result<ObjectBase, InvalidObjectPath>
where
P: TryInto<ObjectPath>,
<P as TryInto<ObjectPath>>::Error: Into<InvalidObjectPath>,
{
ObjectBase::<ArcPointer>::from_bucket(self.get_bucket_base(), path)
}
}
impl AlignBuilder for Client<ClientWithMiddleware> {
fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
&self,
method: Method,
url: Url,
resource: CanonicalizedResource,
headers: H,
) -> Result<RequestBuilder, BuilderError> {
let mut auth_builder = self.auth_builder.clone();
auth_builder.method(&method);
auth_builder.date(now());
auth_builder.canonicalized_resource(resource);
auth_builder.extend_headers(HeaderMap::from_iter(headers));
let mut builder = self
.client_middleware
.request(method, url)
.headers(auth_builder.get_headers()?);
if let Some(timeout) = self.timeout {
builder = builder.timeout(timeout);
};
Ok(builder)
}
}
#[cfg(all(feature = "blocking", test))]
use crate::blocking::builder::Middleware as BlockingMiddleware;
#[cfg(feature = "blocking")]
use crate::blocking::builder::RequestBuilder as BlockingRequestBuilder;
#[cfg(feature = "blocking")]
pub type ClientRc = Client<BlockingClientWithMiddleware>;
#[cfg(feature = "blocking")]
impl Client<BlockingClientWithMiddleware> {
#[cfg(test)]
pub(crate) fn middleware(mut self, middleware: Rc<dyn BlockingMiddleware>) -> Self {
self.client_middleware.middleware(middleware);
self
}
}
#[cfg(feature = "blocking")]
impl crate::file::blocking::AlignBuilder for Client<BlockingClientWithMiddleware> {
#[inline]
fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
&self,
method: Method,
url: Url,
resource: CanonicalizedResource,
headers: H,
) -> Result<BlockingRequestBuilder, BuilderError> {
let method = method;
let mut auth_builder = self.auth_builder.clone();
auth_builder.method(&method);
auth_builder.date(now());
auth_builder.canonicalized_resource(resource);
auth_builder.extend_headers(HeaderMap::from_iter(headers));
let mut builder = self
.client_middleware
.request(method, url)
.headers(auth_builder.get_headers()?);
if let Some(timeout) = self.timeout {
builder = builder.timeout(timeout);
};
Ok(builder)
}
}
#[cfg(test)]
mod tests {
use super::ClientArc;
use crate::{
builder::ArcPointer,
config::{BucketBase, Config, ObjectBase},
BucketName,
};
use std::time::Duration;
#[test]
fn from_config() {
let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
let client = ClientArc::from_config(config);
assert_eq!(client.bucket, "foo4".parse::<BucketName>().unwrap());
}
#[test]
fn timeout() {
let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
let mut client = ClientArc::from_config(config);
assert!(client.timeout.is_none());
client.timeout(Duration::new(10, 0));
assert!(client.timeout.is_some());
assert_eq!(client.timeout, Some(Duration::new(10, 0)));
}
#[test]
fn get_object_base() {
use std::sync::Arc;
let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
let client = ClientArc::from_config(config);
let base = client.get_object_base("file111").unwrap();
let base2 = ObjectBase::<ArcPointer>::new(
Arc::new(BucketBase::new(
"foo4".parse().unwrap(),
"qingdao".parse().unwrap(),
)),
"file111",
)
.unwrap();
assert!(base == base2);
}
}