use std::str::FromStr;
use std::sync::Arc;
use crate::data::Data;
use crate::error::{Error, Result, ValueError};
use crate::provider::Provider;
use crate::signer::sign_request_v4;
use crate::utils::{check_bucket_name, urlencode, _VALID_ENDPOINT};
use crate::Credentials;
use hyper::{header, header::HeaderValue, HeaderMap};
use hyper::{Method, Uri};
use reqwest::{Body, Response};
#[cfg(feature = "bucket")]
use super::{Bucket, BucketArgs};
pub struct MinioBuilder {
endpoint: Option<String>,
region: String,
agent: String,
secure: bool,
virtual_hosted: bool,
multi_chunked_encoding: bool,
provider: Option<Box<dyn Provider>>,
client: Option<reqwest::Client>,
}
impl MinioBuilder {
pub fn new() -> Self {
MinioBuilder {
endpoint: None,
secure: true,
virtual_hosted: false,
multi_chunked_encoding: true,
region: "us-east-1".to_string(),
agent: "MinIO (Linux; x86_64) minio-rs".to_string(),
provider: None,
client: None,
}
}
#[deprecated(note = "Please use the `endpoint` instead")]
pub fn host<T: Into<String>>(mut self, host: T) -> Self {
let host: String = host.into();
if host.starts_with("http://") {
self.secure = false;
self.endpoint = Some(host[7..].into());
} else if host.starts_with("https://") {
self.secure = true;
self.endpoint = Some(host[8..].into());
} else {
self.endpoint = Some(host);
}
self
}
pub fn endpoint<T: Into<String>>(mut self, endpoint: T) -> Self {
self.endpoint = Some(endpoint.into());
self
}
pub fn region<T: Into<String>>(mut self, region: T) -> Self {
self.region = region.into();
self
}
pub fn agent<T: Into<String>>(mut self, agent: T) -> Self {
self.agent = agent.into();
self
}
pub fn secure(mut self, secure: bool) -> Self {
self.secure = secure;
self
}
pub fn client(mut self, client: reqwest::Client) -> Self {
self.client = Some(client);
self
}
pub fn virtual_hosted_style(mut self, virtual_hosted_style: bool) -> Self {
self.virtual_hosted = virtual_hosted_style;
self
}
pub fn multi_chunked_encoding(mut self, multi_chunked_encoding: bool) -> Self {
self.multi_chunked_encoding = multi_chunked_encoding;
self
}
pub fn provider<P>(mut self, provider: P) -> Self
where
P: Provider + 'static,
{
self.provider = Some(Box::new(provider));
self
}
pub fn build(self) -> std::result::Result<Minio, ValueError> {
let endpoint = self.endpoint.ok_or("Miss endpoint")?;
if !_VALID_ENDPOINT.is_match(&endpoint) {
return Err("Invalid endpoint".into());
}
let provider = self.provider.ok_or("Miss provide")?;
let agent: HeaderValue = self
.agent
.parse()
.map_err(|_| ValueError::from("Invalid agent"))?;
let client2 = self.client.unwrap_or_else(|| {
let mut headers = header::HeaderMap::new();
headers.insert(header::USER_AGENT, agent.clone());
reqwest::Client::builder()
.default_headers(headers)
.https_only(self.secure)
.build()
.unwrap()
});
Ok(Minio {
inner: Arc::new(MinioRef {
endpoint,
secure: self.secure,
client2,
virtual_hosted: self.virtual_hosted,
multi_chunked: self.multi_chunked_encoding,
region: self.region,
agent,
provider,
}),
})
}
}
#[derive(Clone)]
pub struct Minio {
inner: Arc<MinioRef>,
}
struct MinioRef {
endpoint: String,
virtual_hosted: bool,
multi_chunked: bool,
secure: bool,
client2: reqwest::Client,
region: String,
agent: HeaderValue,
provider: Box<dyn Provider>,
}
impl Minio {
pub fn builder() -> MinioBuilder {
MinioBuilder::new()
}
pub(crate) fn multi_chunked(&self) -> bool {
self.inner.multi_chunked
}
pub fn region(&self) -> &str {
self.inner.region.as_ref()
}
#[inline]
pub(super) async fn fetch_credentials(&self) -> Credentials {
self.inner.provider.fetch().await
}
async fn _url_open(
&self,
method: Method,
uri: String,
headers: HeaderMap,
body: Body,
) -> Result<Response> {
let request = self
.inner
.client2
.request(method, uri)
.headers(headers)
.body(body)
.send()
.await?;
Ok(request)
}
#[inline]
pub(super) fn scheme(&self) -> &str {
if self.inner.secure {
"https"
} else {
"http"
}
}
pub(super) fn _build_uri(&self, bucket: Option<String>, key: Option<String>) -> String {
let scheme = self.scheme();
let endpoint = self.inner.endpoint.as_str();
match bucket {
Some(b) => {
let mut uri = if self.inner.virtual_hosted {
format!("{scheme}://{b}.{endpoint}")
} else {
format!("{scheme}://{endpoint}/{b}",)
};
if let Some(key) = key {
uri.push('/');
uri.push_str(&urlencode(&key, true));
}
uri
}
None => format!("{scheme}://{endpoint}"),
}
}
pub async fn _execute<B: Into<Data<crate::error::Error>>>(
&self,
method: Method,
region: &str,
bucket_name: Option<String>,
object_name: Option<String>,
data: B,
headers: Option<HeaderMap>,
query_params: Option<String>,
) -> Result<Response> {
if let Some(bucket_name) = &bucket_name {
check_bucket_name(bucket_name)?;
}
if let Some(object_name) = &object_name {
if object_name.is_empty() {
Err(ValueError::from("Object name cannot be empty."))?
}
if bucket_name.is_none() {
Err(ValueError::from("Miss bucket name."))?
}
}
let uri = self._build_uri(bucket_name, object_name);
let uri = if let Some(query) = query_params {
format!("{}?{}", uri, query)
} else {
uri
};
let mut data = data.into();
if !self.inner.multi_chunked {
data = data.convert().await?;
}
let mut headers = headers.unwrap_or(HeaderMap::new());
headers.insert(header::USER_AGENT, self.inner.agent.clone());
let credentials = self.fetch_credentials().await;
let uri = Uri::from_str(&uri).map_err(|e| Error::ValueError(e.to_string()))?;
let (uri, body) = sign_request_v4(
&method,
&uri,
&mut headers,
region,
data,
credentials.access_key(),
credentials.secret_key(),
)?;
self._url_open(method, uri, headers, body).await
}
#[inline]
pub fn executor(&self, method: Method) -> super::BaseExecutor {
super::BaseExecutor::new(method, self)
}
#[cfg(feature = "bucket")]
pub fn bucket<B>(&self, bucket: B) -> Bucket
where
B: Into<BucketArgs>,
{
Bucket {
client: self.clone(),
bucket: bucket.into(),
}
}
}