• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tu6ge / oss-rs / 5863951548

pending completion
5863951548

Pull #24

github

tu6ge
feat(client): Reduce constraints on generic param
Pull Request #24: Branch0.12

39 of 39 new or added lines in 4 files covered. (100.0%)

6180 of 6422 relevant lines covered (96.23%)

9.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.89
/src/client.rs
1
//! # 对 reqwest 进行了简单的封装,加上了 OSS 的签名验证功能
×
2

3
use crate::auth::AuthBuilder;
4
#[cfg(feature = "blocking")]
5
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
6
#[cfg(test)]
7
use crate::builder::Middleware;
8
use crate::builder::{ArcPointer, BuilderError, ClientWithMiddleware, RequestBuilder};
9
use crate::config::{get_bucket, get_endpoint, get_env, BucketBase, Config, InvalidConfig};
10
use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4};
11
use crate::file::AlignBuilder;
12
use crate::types::{
13
    object::{InvalidObjectPath, ObjectBase, ObjectPath},
14
    BucketName, CanonicalizedResource, EndPoint, KeyId, KeySecret,
15
};
16

17
use chrono::{DateTime, Utc};
18
use http::{
19
    header::{HeaderMap, HeaderName},
20
    HeaderValue, Method,
21
};
22
use reqwest::Url;
23
use std::env;
24
#[cfg(all(feature = "blocking", test))]
25
use std::rc::Rc;
26
#[cfg(test)]
27
use std::sync::Arc;
28
use std::time::Duration;
29

30
/// # 构造请求的客户端结构体
31
#[non_exhaustive]
32
pub struct Client<M = ClientWithMiddleware> {
33
    auth_builder: AuthBuilder,
34
    client_middleware: M,
35
    pub(crate) endpoint: EndPoint,
36
    pub(crate) bucket: BucketName,
37
    timeout: Option<Duration>,
38
}
39

40
impl<M: Default> Default for Client<M> {
41
    fn default() -> Self {
34✔
42
        Self {
34✔
43
            auth_builder: AuthBuilder::default(),
34✔
44
            client_middleware: M::default(),
34✔
45
            endpoint: EndPoint::default(),
34✔
46
            bucket: BucketName::default(),
34✔
47
            timeout: Option::default(),
34✔
48
        }
49
    }
34✔
50
}
51

52
impl<M: Clone> Clone for Client<M> {
53
    fn clone(&self) -> Self {
1✔
54
        Self {
1✔
55
            auth_builder: self.auth_builder.clone(),
1✔
56
            client_middleware: self.client_middleware.clone(),
1✔
57
            endpoint: self.endpoint.clone(),
1✔
58
            bucket: self.bucket.clone(),
1✔
59
            timeout: self.timeout,
1✔
60
        }
61
    }
1✔
62
}
63

64
impl<M> AsMut<Option<Duration>> for Client<M> {
65
    fn as_mut(&mut self) -> &mut Option<Duration> {
66
        &mut self.timeout
67
    }
68
}
69

70
impl<M> AsRef<EndPoint> for Client<M> {
71
    fn as_ref(&self) -> &EndPoint {
33✔
72
        &self.endpoint
33✔
73
    }
33✔
74
}
75
impl<M> AsRef<BucketName> for Client<M> {
76
    fn as_ref(&self) -> &BucketName {
33✔
77
        &self.bucket
78
    }
33✔
79
}
80

81
impl<M: Default> Client<M> {
82
    /// 使用基本配置信息初始化 Client
83
    pub fn new(
46✔
84
        access_key_id: KeyId,
85
        access_key_secret: KeySecret,
86
        endpoint: EndPoint,
87
        bucket: BucketName,
88
    ) -> Self {
89
        let mut auth_builder = AuthBuilder::default();
46✔
90
        auth_builder.key(access_key_id);
46✔
91
        auth_builder.secret(access_key_secret);
46✔
92

93
        Self::from_builder(auth_builder, endpoint, bucket)
46✔
94
    }
46✔
95

96
    /// - bucket: bar
97
    /// - endpoint: qingdao
98
    #[cfg(test)]
99
    pub fn test_init() -> Self {
9✔
100
        Self::new(
9✔
101
            "foo1".into(),
9✔
102
            "foo2".into(),
9✔
103
            EndPoint::CN_QINGDAO,
9✔
104
            "bar".try_into().unwrap(),
9✔
105
        )
106
    }
9✔
107

108
    /// 使用 [`Config`] 中的配置初始化 Client
109
    ///
110
    /// [`Config`]: crate::config::Config
111
    pub fn from_config(config: Config) -> Self {
3✔
112
        let (key, secret, bucket, endpoint) = config.get_all();
3✔
113

114
        let mut auth_builder = AuthBuilder::default();
3✔
115
        auth_builder.key(key);
3✔
116
        auth_builder.secret(secret);
3✔
117

118
        Self::from_builder(auth_builder, endpoint, bucket)
3✔
119
    }
3✔
120

121
    /// # 通过环境变量初始化 Client
122
    ///
123
    /// 如果在 Aliyun ECS 上,可将环境变量 `ALIYUN_OSS_INTERNAL`
124
    /// 设置为 `true` / `1` / `yes` / `Y` ,即可使用 internal 网络请求 OSS 接口
125
    ///
126
    /// 示例
127
    /// ```rust
128
    /// use std::env::set_var;
129
    /// set_var("ALIYUN_KEY_ID", "foo1");
130
    /// set_var("ALIYUN_KEY_SECRET", "foo2");
131
    /// set_var("ALIYUN_ENDPOINT", "qingdao");
132
    /// set_var("ALIYUN_BUCKET", "foo4");
133
    ///
134
    /// # use aliyun_oss_client::client::Client;
135
    /// use aliyun_oss_client::builder::ClientWithMiddleware;
136
    /// let client = Client::<ClientWithMiddleware>::from_env();
137
    /// assert!(client.is_ok());
138
    /// ```
139
    pub fn from_env() -> Result<Self, InvalidConfig> {
7✔
140
        let key_id = get_env("ALIYUN_KEY_ID")?;
7✔
141
        let key_secret = get_env("ALIYUN_KEY_SECRET")?;
7✔
142
        let endpoint = get_env("ALIYUN_ENDPOINT")?;
7✔
143
        let bucket = get_env("ALIYUN_BUCKET")?;
7✔
144

145
        let mut auth_builder = AuthBuilder::default();
7✔
146
        auth_builder.key(key_id);
7✔
147
        auth_builder.secret(key_secret);
7✔
148

149
        let mut endpoint = get_endpoint(&endpoint)?;
7✔
150

151
        if let Ok(is_internal) = env::var("ALIYUN_OSS_INTERNAL") {
7✔
152
            if is_internal == TRUE1
5✔
153
                || is_internal == TRUE2
4✔
154
                || is_internal == TRUE3
3✔
155
                || is_internal == TRUE4
2✔
156
            {
157
                endpoint.set_internal(true);
4✔
158
            }
159
        }
7✔
160

161
        Ok(Self::from_builder(
7✔
162
            auth_builder,
7✔
163
            endpoint,
7✔
164
            get_bucket(&bucket)?,
7✔
165
        ))
×
166
    }
7✔
167

168
    #[doc(hidden)]
169
    #[inline]
170
    pub fn from_builder(auth_builder: AuthBuilder, endpoint: EndPoint, bucket: BucketName) -> Self {
57✔
171
        Self {
57✔
172
            auth_builder,
173
            client_middleware: M::default(),
57✔
174
            endpoint,
175
            bucket,
176
            timeout: None,
57✔
177
        }
178
    }
57✔
179
}
180

181
impl<M> Client<M> {
182
    pub(crate) fn get_bucket_name(&self) -> &BucketName {
5✔
183
        &self.bucket
184
    }
5✔
185

186
    /// 返回默认可用区,默认 bucket 的 BucketBase
187
    pub fn get_bucket_base(&self) -> BucketBase {
13✔
188
        BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned())
13✔
189
    }
13✔
190

191
    /// 获取默认的 bucket 的 url
192
    pub fn get_bucket_url(&self) -> Url {
7✔
193
        self.get_bucket_base().to_url()
7✔
194
    }
7✔
195

196
    pub(crate) fn get_key(&self) -> &KeyId {
1✔
197
        self.auth_builder.get_key()
1✔
198
    }
1✔
199
    pub(crate) fn get_secret(&self) -> &KeySecret {
1✔
200
        self.auth_builder.get_secret()
1✔
201
    }
1✔
202

203
    pub(crate) fn get_endpoint(&self) -> &EndPoint {
6✔
204
        &self.endpoint
6✔
205
    }
6✔
206

207
    /// 获取默认的可用区的 url
208
    pub fn get_endpoint_url(&self) -> Url {
3✔
209
        self.endpoint.to_url()
3✔
210
    }
3✔
211

212
    /// 设置 timeout
213
    pub fn timeout(&mut self, timeout: Duration) {
3✔
214
        self.timeout = Some(timeout);
3✔
215
    }
3✔
216

217
    /// 根据默认的 bucket,endpoint 和提供的文件路径,获取 ObjectBase
218
    #[inline]
219
    pub fn get_object_base<P>(&self, path: P) -> Result<ObjectBase, InvalidObjectPath>
3✔
220
    where
221
        P: TryInto<ObjectPath>,
222
        P::Error: Into<InvalidObjectPath>,
223
    {
224
        ObjectBase::<ArcPointer>::from_bucket(self.get_bucket_base(), path)
3✔
225
    }
3✔
226
}
227

228
#[cfg(not(test))]
229
#[inline]
230
fn now() -> DateTime<Utc> {
8✔
231
    Utc::now()
8✔
232
}
8✔
233

234
#[cfg(test)]
235
fn now() -> DateTime<Utc> {
30✔
236
    use chrono::NaiveDateTime;
237
    let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap();
30✔
238
    DateTime::from_utc(naive, Utc)
30✔
239
}
30✔
240

241
/// 异步 Client 别名
242
pub type ClientArc = Client<ClientWithMiddleware>;
243

244
impl Client {
245
    /// # 用于模拟请求 OSS 接口
246
    /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作
247
    #[cfg(test)]
248
    pub(crate) fn middleware(mut self, middleware: Arc<dyn Middleware>) -> Self {
13✔
249
        self.client_middleware.middleware(middleware);
13✔
250
        self
13✔
251
    }
13✔
252
}
253

254
impl AlignBuilder for Client<ClientWithMiddleware> {
255
    /// # 构造自定义的接口请求方法
256
    /// 比如在上传完文件时,返回自己期望的数据,而不是仅返回 etag 信息
257
    ///
258
    /// ## 例子是一个获取 object 元信息的接口
259
    /// ```
260
    /// use aliyun_oss_client::{errors::OssError, file::AlignBuilder, Client, Method};
261
    /// use dotenv::dotenv;
262
    ///
263
    /// async fn run() -> Result<(), OssError> {
264
    ///     dotenv().ok();
265
    ///     let client = Client::from_env().unwrap();
266
    ///
267
    ///     let (url, resource) = client
268
    ///         .get_object_base("9AB932LY.jpeg")?
269
    ///         .get_url_resource([]);
270
    ///
271
    ///     let headers = vec![(
272
    ///         "If-Unmodified-Since".parse().unwrap(),
273
    ///         "Sat, 01 Jan 2022 18:01:01 GMT".parse().unwrap(),
274
    ///     )];
275
    ///
276
    ///     let builder = client.builder_with_header(Method::HEAD, url, resource, headers)?;
277
    ///
278
    ///     let response = builder.send().await?;
279
    ///
280
    ///     println!("status: {:?}", response.status());
281
    ///
282
    ///     Ok(())
283
    /// }
284
    /// ```
285
    /// ## 参数
286
    /// - method 接口请求方式
287
    /// - url 要请求的接口,包含 query 参数等信息
288
    /// - resource 阿里云接口需要提供的统一的信息,[`CanonicalizedResource`] 提供了 bucket ,object 等各种生成方式,如果无法满足
289
    /// 还可以自己用 trait 来自定义
290
    ///
291
    /// ## 返回值
292
    /// 返回值是一个封装了 reqwest::Builder 构造器,[`RequestBuilder`], 提供两个方法 `send` 和 `send_adjust_error`
293
    ///
294
    /// - `send` 方法,直接返回 `reqwest::Response`
295
    /// - `send_adjust_error` 方法,会对 api 返回结果进行处理,如果 HTTP 状态码正常(200>= && <300) 则,返回 Ok,
296
    /// 否则,会对返回的 xml 异常数据进行解析,返回 Err([`OssService`])
297
    ///
298
    /// [`RequestBuilder`]: crate::builder::RequestBuilder
299
    /// [`OssService`]: crate::errors::OssService
300
    /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource
301
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
21✔
302
        &self,
303
        method: Method,
304
        url: Url,
305
        resource: CanonicalizedResource,
306
        headers: H,
307
    ) -> Result<RequestBuilder, BuilderError> {
308
        // dbg!(url.clone());
309
        // dbg!(resource.clone());
310
        let mut auth_builder = self.auth_builder.clone();
21✔
311
        auth_builder.method(&method);
21✔
312
        auth_builder.date(now());
21✔
313
        auth_builder.canonicalized_resource(resource);
21✔
314
        auth_builder.extend_headers(HeaderMap::from_iter(headers));
21✔
315

316
        let mut builder = self
21✔
317
            .client_middleware
318
            .request(method, url)
319
            .headers(auth_builder.get_headers()?);
21✔
320

321
        if let Some(timeout) = self.timeout {
21✔
322
            builder = builder.timeout(timeout);
1✔
323
        };
324

325
        Ok(builder)
21✔
326
    }
21✔
327
}
328

329
#[cfg(all(feature = "blocking", test))]
330
use crate::blocking::builder::Middleware as BlockingMiddleware;
331
#[cfg(feature = "blocking")]
332
use crate::blocking::builder::RequestBuilder as BlockingRequestBuilder;
333

334
/// 同步的 Client 别名
335
#[cfg(feature = "blocking")]
336
pub type ClientRc = Client<BlockingClientWithMiddleware>;
337

338
#[cfg(feature = "blocking")]
339
impl Client<BlockingClientWithMiddleware> {
340
    /// # 用于模拟请求 OSS 接口
341
    /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作
342
    #[cfg(test)]
343
    pub(crate) fn middleware(mut self, middleware: Rc<dyn BlockingMiddleware>) -> Self {
11✔
344
        self.client_middleware.middleware(middleware);
11✔
345
        self
11✔
346
    }
11✔
347
}
348

349
#[cfg(feature = "blocking")]
350
impl crate::file::blocking::AlignBuilder for Client<BlockingClientWithMiddleware> {
351
    /// # 向 OSS 发送请求的封装
352
    /// 参数包含请求的:
353
    ///
354
    /// - method
355
    /// - url
356
    /// - headers (可选)
357
    /// - [CanonicalizedResource](https://help.aliyun.com/document_detail/31951.html#section-rvv-dx2-xdb)
358
    ///
359
    /// 返回值是一个 reqwest 的请求创建器 `reqwest::blocking::RequestBuilder`
360
    ///
361
    /// 返回后,可以再加请求参数,然后可选的进行发起请求
362
    #[inline]
363
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
17✔
364
        &self,
365
        method: Method,
366
        url: Url,
367
        resource: CanonicalizedResource,
368
        headers: H,
369
    ) -> Result<BlockingRequestBuilder, BuilderError> {
370
        let method = method;
17✔
371
        let mut auth_builder = self.auth_builder.clone();
17✔
372
        auth_builder.method(&method);
17✔
373
        auth_builder.date(now());
17✔
374
        auth_builder.canonicalized_resource(resource);
17✔
375
        auth_builder.extend_headers(HeaderMap::from_iter(headers));
17✔
376

377
        let mut builder = self
17✔
378
            .client_middleware
379
            .request(method, url)
380
            .headers(auth_builder.get_headers()?);
17✔
381

382
        if let Some(timeout) = self.timeout {
17✔
383
            builder = builder.timeout(timeout);
1✔
384
        };
385

386
        Ok(builder)
17✔
387
    }
17✔
388
}
389

390
#[cfg(test)]
391
mod tests {
392
    use http::Method;
393

394
    use super::*;
395
    use crate::{
396
        builder::ArcPointer,
397
        config::{BucketBase, Config},
398
        file::AlignBuilder,
399
        types::object::ObjectBase,
400
        BucketName,
401
    };
402

403
    use std::time::Duration;
404

405
    #[test]
406
    fn from_config() {
2✔
407
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
408
        let client = ClientArc::from_config(config);
1✔
409

410
        assert_eq!(client.bucket, "foo4".parse::<BucketName>().unwrap());
1✔
411
    }
2✔
412

413
    #[test]
414
    fn timeout() {
2✔
415
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
416
        let mut client = ClientArc::from_config(config);
1✔
417

418
        assert!(client.timeout.is_none());
1✔
419

420
        client.timeout(Duration::new(10, 0));
1✔
421

422
        assert!(client.timeout.is_some());
1✔
423

424
        assert_eq!(client.timeout, Some(Duration::new(10, 0)));
1✔
425
    }
2✔
426

427
    #[test]
428
    fn test_timeout_with_builder() {
2✔
429
        let mut client = ClientArc::test_init();
1✔
430
        client.timeout(Duration::new(11, 0));
1✔
431
        let (url, resource) = client
1✔
432
            .get_object_base("9AB932LY.jpeg")
433
            .unwrap()
434
            .get_url_resource([]);
1✔
435
        let builder = client.builder_with_header(Method::HEAD, url, resource, []);
1✔
436
        let builder = builder.unwrap();
1✔
437

438
        let request = builder.build().unwrap();
1✔
439
        let timeout = request.timeout().unwrap().to_owned();
1✔
440
        assert_eq!(timeout, Duration::new(11, 0));
1✔
441
    }
2✔
442

443
    #[cfg(feature = "blocking")]
444
    #[test]
445
    fn test_timeout_with_builder_blocking() {
2✔
446
        use crate::file::blocking::AlignBuilder;
447

448
        let mut client = ClientRc::test_init();
1✔
449
        client.timeout(Duration::new(11, 0));
1✔
450
        let (url, resource) = client
1✔
451
            .get_object_base("9AB932LY.jpeg")
452
            .unwrap()
453
            .get_url_resource([]);
1✔
454
        let builder = client.builder_with_header(Method::HEAD, url, resource, []);
1✔
455
        let builder = builder.unwrap();
1✔
456

457
        let request = builder.build().unwrap();
1✔
458
        let timeout = request.timeout().unwrap().to_owned();
1✔
459
        assert_eq!(timeout, Duration::new(11, 0));
1✔
460
    }
2✔
461

462
    #[test]
463
    fn get_object_base() {
2✔
464
        use std::sync::Arc;
465

466
        let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap();
1✔
467
        let client = ClientArc::from_config(config);
1✔
468

469
        let base = client.get_object_base("file111").unwrap();
1✔
470

471
        let base2 = ObjectBase::<ArcPointer>::new(
1✔
472
            Arc::new(BucketBase::new(
1✔
473
                "foo4".parse().unwrap(),
1✔
474
                "qingdao".parse().unwrap(),
1✔
475
            )),
476
            "file111",
477
        )
478
        .unwrap();
479
        assert!(base == base2);
1✔
480
    }
2✔
481
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc