1use std::str::FromStr;
2use std::sync::Arc;
3
4use crate::data::Data;
5use crate::error::{Error, Result, ValueError};
6use crate::provider::Provider;
7use crate::signer::sign_request_v4;
8use crate::utils::{check_bucket_name, urlencode, _VALID_ENDPOINT};
9use crate::Credentials;
10use hyper::{header, header::HeaderValue, HeaderMap};
11use hyper::{Method, Uri};
12use reqwest::{Body, Response};
13
14use super::{Bucket, BucketArgs};
15
16pub struct MinioBuilder {
18 endpoint: Option<String>,
19 region: String,
23 agent: String,
24 secure: bool,
25 virtual_hosted: bool,
26 multi_chunked_encoding: bool,
27 provider: Option<Box<dyn Provider>>,
28 client: Option<reqwest::Client>,
29}
30
31impl MinioBuilder {
32 pub fn new() -> Self {
33 MinioBuilder {
34 endpoint: None,
35 secure: true,
36 virtual_hosted: false,
37 multi_chunked_encoding: true,
38 region: "us-east-1".to_string(),
39 agent: "MinIO (Linux; x86_64) minio-rs".to_string(),
40 provider: None,
41 client: None,
42 }
43 }
44
45 #[deprecated(note = "Please use the `endpoint` instead")]
47 pub fn host<T: Into<String>>(mut self, host: T) -> Self {
48 let host: String = host.into();
49 if host.starts_with("http://") {
50 self.secure = false;
51 self.endpoint = Some(host[7..].into());
52 } else if host.starts_with("https://") {
53 self.secure = true;
54 self.endpoint = Some(host[8..].into());
55 } else {
56 self.endpoint = Some(host);
57 }
58 self
59 }
60
61 pub fn endpoint<T: Into<String>>(mut self, endpoint: T) -> Self {
63 self.endpoint = Some(endpoint.into());
64 self
65 }
66
67 pub fn region<T: Into<String>>(mut self, region: T) -> Self {
71 self.region = region.into();
72 self
73 }
74
75 pub fn agent<T: Into<String>>(mut self, agent: T) -> Self {
79 self.agent = agent.into();
80 self
81 }
82
83 pub fn secure(mut self, secure: bool) -> Self {
87 self.secure = secure;
88 self
89 }
90
91 pub fn client(mut self, client: reqwest::Client) -> Self {
93 self.client = Some(client);
94 self
95 }
96
97 pub fn virtual_hosted_style(mut self, virtual_hosted_style: bool) -> Self {
106 self.virtual_hosted = virtual_hosted_style;
107 self
108 }
109
110 pub fn multi_chunked_encoding(mut self, multi_chunked_encoding: bool) -> Self {
114 self.multi_chunked_encoding = multi_chunked_encoding;
115 self
116 }
117
118 pub fn provider<P>(mut self, provider: P) -> Self
122 where
123 P: Provider + 'static,
124 {
125 self.provider = Some(Box::new(provider));
126 self
127 }
128
129 pub fn build(self) -> std::result::Result<Minio, ValueError> {
130 let endpoint = self.endpoint.ok_or("Miss endpoint")?;
131 if !_VALID_ENDPOINT.is_match(&endpoint) {
132 return Err("Invalid endpoint".into());
133 }
134 let provider = self.provider.ok_or("Miss provide")?;
135
136 let agent: HeaderValue = self
137 .agent
138 .parse()
139 .map_err(|_| ValueError::from("Invalid agent"))?;
140
141 let client2 = self.client.unwrap_or_else(|| {
142 let mut headers = header::HeaderMap::new();
143 headers.insert(header::USER_AGENT, agent.clone());
144 reqwest::Client::builder()
145 .default_headers(headers)
146 .https_only(self.secure)
147 .max_tls_version(reqwest::tls::Version::TLS_1_2)
148 .build()
149 .unwrap()
150 });
151 Ok(Minio {
152 inner: Arc::new(MinioRef {
153 endpoint,
154 secure: self.secure,
155 client2,
156 virtual_hosted: self.virtual_hosted,
157 multi_chunked: self.multi_chunked_encoding,
158 region: self.region,
159 agent,
160 provider,
161 }),
162 })
163 }
164}
165
166#[derive(Clone)]
184pub struct Minio {
185 inner: Arc<MinioRef>,
186}
187
188struct MinioRef {
189 endpoint: String,
190 virtual_hosted: bool,
191 multi_chunked: bool,
192 secure: bool,
193 client2: reqwest::Client,
194 region: String,
195 agent: HeaderValue,
196 provider: Box<dyn Provider>,
197}
198
199impl Minio {
200 pub fn builder() -> MinioBuilder {
202 MinioBuilder::new()
203 }
204
205 pub(crate) fn multi_chunked(&self) -> bool {
207 self.inner.multi_chunked
208 }
209
210 pub fn region(&self) -> &str {
211 self.inner.region.as_ref()
212 }
213
214 fn _get_region<T: Into<String>>(&self, bucket_name: Option<T>) -> String {
215 self.inner.region.clone()
216 }
217
218 #[inline]
219 pub(super) async fn fetch_credentials(&self) -> Credentials {
220 self.inner.provider.fetch().await
221 }
222
223 async fn _url_open(
225 &self,
226 method: Method,
227 uri: String,
228 headers: HeaderMap,
229 body: Body,
230 ) -> Result<Response> {
231 let request = self
232 .inner
233 .client2
234 .request(method, uri)
235 .headers(headers)
236 .body(body)
237 .send()
238 .await?;
239 Ok(request)
240 }
241
242 #[inline]
243 pub(super) fn scheme(&self) -> &str {
244 if self.inner.secure {
245 "https"
246 } else {
247 "http"
248 }
249 }
250
251 pub(super) fn _build_uri(&self, bucket: Option<String>, key: Option<String>) -> String {
255 let scheme = self.scheme();
256 let endpoint = self.inner.endpoint.as_str();
257 match bucket {
258 Some(b) => {
259 let mut uri = if self.inner.virtual_hosted {
260 format!("{scheme}://{b}.{endpoint}")
261 } else {
262 format!("{scheme}://{endpoint}/{b}",)
263 };
264 if let Some(key) = key {
265 uri.push('/');
266 uri.push_str(&urlencode(&key, true));
267 }
268 uri
269 }
270 None => format!("{scheme}://{endpoint}"),
271 }
272 }
273
274 pub async fn _execute<B: Into<Data<crate::error::Error>>>(
275 &self,
276 method: Method,
277 region: &str,
278 bucket_name: Option<String>,
279 object_name: Option<String>,
280 data: B,
281 headers: Option<HeaderMap>,
282 query_params: Option<String>,
283 ) -> Result<Response> {
284 if let Some(bucket_name) = &bucket_name {
286 check_bucket_name(bucket_name)?;
287 }
288 if let Some(object_name) = &object_name {
290 if object_name.is_empty() {
291 Err(ValueError::from("Object name cannot be empty."))?
292 }
293 if bucket_name.is_none() {
294 Err(ValueError::from("Miss bucket name."))?
295 }
296 }
297 let uri = self._build_uri(bucket_name, object_name);
299
300 let uri = if let Some(query) = query_params {
302 format!("{}?{}", uri, query)
303 } else {
304 uri
305 };
306 let mut data = data.into();
307 if !self.inner.multi_chunked {
308 data = data.convert().await?;
309 }
310 let mut headers = headers.unwrap_or(HeaderMap::new());
311 headers.insert(header::USER_AGENT, self.inner.agent.clone());
312 let credentials = self.fetch_credentials().await;
313 let uri = Uri::from_str(&uri).map_err(|e| Error::ValueError(e.to_string()))?;
314 let (uri, body) = sign_request_v4(
315 &method,
316 &uri,
317 &mut headers,
318 region,
319 data,
320 credentials.access_key(),
321 credentials.secret_key(),
322 )?;
323 self._url_open(method, uri, headers, body).await
324 }
325
326 #[inline]
327 pub fn executor(&self, method: Method) -> super::BaseExecutor {
328 super::BaseExecutor::new(method, self)
329 }
330
331 pub fn bucket<B>(&self, bucket: B) -> Bucket
333 where
334 B: Into<BucketArgs>,
335 {
336 Bucket {
337 client: self.clone(),
338 bucket: bucket.into(),
339 }
340 }
341}