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

tu6ge / oss-rs / 5829505807

pending completion
5829505807

push

github

tu6ge
Support std IO (#26)

* feat(decode)!: change init object fn

* todo

* feat(error): OssError add more info

when OssError code is SignatureDoesNotMatch ,show expect
 sign string

* feat(io): support write

* feat: blocking support

* feat: blocking read

* feat: 允许读取的数据于目标数组长度不一致

* feat: 分离 Rc 和内部数据

* feat: support Arc Object Content

* feat: 解决多次写入少量数据导致oss错误的问题

当多次写入少量数据,不符合分片的最小数量时,调用 oss 接口会导致报错

* refactor

* feat: 交互 arc 与 rc 的位置

* docs(io)

* docs(io)

* style

* chore: default close blocking

* fix

* style

* feat(io): change seek

* feat(io): change error type

* style

* feat(bucket)!: change base_bucket_info

* test(io)

* test(doc): remove deprecated

* test(io)

* test(io)

* test(io)

* style(io): clippy

* chore: support more derive

* refactor

* docs

1293 of 1293 new or added lines in 19 files covered. (100.0%)

7298 of 7685 relevant lines covered (94.96%)

9.62 hits per line

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

96.45
/src/file.rs
1
//! # OSS 文件相关操作
2✔
2
//!
3
//! [`File`] 是一个文件操作的工具包,包含上传,下载,删除功能,开发者可以方便的调用使用
4
//!
5
//! ```rust
6
//! use std::{fs, io, path::Path};
7
//!
8
//! use aliyun_oss_client::{
9
//!     config::get_url_resource,
10
//!     errors::OssError,
11
//!     file::{File, FileError, GetStd},
12
//!     types::CanonicalizedResource,
13
//!     BucketName, Client, EndPoint, KeyId, KeySecret,
14
//! };
15
//! use reqwest::Url;
16
//!
17
//! struct MyObject {
18
//!     path: String,
19
//! }
20
//!
21
//! impl MyObject {
22
//!     const KEY_ID: KeyId = KeyId::from_static("xxxxxxxxxxxxxxxx");
23
//!     const KEY_SECRET: KeySecret = KeySecret::from_static("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
24
//!     const END_POINT: EndPoint = EndPoint::SHANGHAI;
25
//!     const BUCKET: BucketName = unsafe { BucketName::from_static2("xxxxxx") };
26
//!
27
//!     fn new(path: &Path) -> Result<MyObject, io::Error> {
28
//!         Ok(MyObject {
29
//!             path: path.to_str().unwrap().to_owned(),
30
//!         })
31
//!     }
32
//! }
33
//!
34
//! impl GetStd for MyObject {
35
//!     fn get_std(&self) -> Option<(Url, CanonicalizedResource)> {
36
//!         let path = self.path.clone().try_into().unwrap();
37
//!         Some(get_url_resource(&Self::END_POINT, &Self::BUCKET, &path))
38
//!     }
39
//! }
40
//!
41
//! impl File<Client> for MyObject {
42
//!     fn oss_client(&self) -> Client {
43
//!         Client::new(
44
//!             Self::KEY_ID,
45
//!             Self::KEY_SECRET,
46
//!             Self::END_POINT,
47
//!             Self::BUCKET,
48
//!         )
49
//!     }
50
//! }
51
//!
52
//! async fn run() -> Result<(), OssError> {
53
//!     for entry in fs::read_dir("examples")? {
54
//!         let path = entry?.path();
55
//!         let path = path.as_path();
56
//!
57
//!         if !path.is_file() {
58
//!             continue;
59
//!         }
60
//!
61
//!         let obj = MyObject::new(path)?;
62
//!         let content = fs::read(path)?;
63
//!
64
//!         let res = obj
65
//!             .put_oss(content, "application/pdf")
66
//!             .await
67
//!             .map_err(OssError::from)?;
68
//!
69
//!         println!("result status: {}", res.status());
70
//!     }
71
//!
72
//!     Ok(())
73
//! }
74
//! ```
75
//! [`File`]: crate::file::File
76

77
use async_trait::async_trait;
78
use http::{
79
    header::{HeaderName, CONTENT_LENGTH, CONTENT_TYPE},
80
    HeaderValue, Method,
81
};
82
use reqwest::{Response, Url};
83

84
use crate::{
85
    bucket::Bucket,
86
    builder::{ArcPointer, BuilderError, RequestBuilder},
87
    object::{Object, ObjectList},
88
    types::object::{ObjectBase, ObjectPath},
89
    types::{CanonicalizedResource, ContentRange},
90
};
91
#[cfg(feature = "put_file")]
92
use infer::Infer;
93

94
#[cfg(test)]
95
mod test;
96

97
const ETAG: &str = "ETag";
98
const RANGE: &str = "Range";
99

100
/// # 文件的相关操作
101
///
102
/// 包括 上传,下载,删除等功能
103
#[async_trait]
104
pub trait File<Client>: GetStd
105
where
106
    Client: Files<ObjectPath>,
107
{
108
    /// 指定发起 OSS 接口调用的客户端
109
    fn oss_client(&self) -> Client;
110

111
    /// 上传文件内容到 OSS 上面
112
    async fn put_oss(&self, content: Vec<u8>, content_type: &str) -> Result<Response, FileError> {
113
        let (url, canonicalized) = self.get_std().ok_or(FileError {
114
            kind: FileErrorKind::NotFoundCanonicalizedResource,
115
        })?;
116

117
        let content_length = content.len().to_string();
118
        let headers = vec![
119
            (CONTENT_LENGTH, header_from_content_length(&content_length)?),
120
            (
121
                CONTENT_TYPE,
122
                content_type.parse().map_err(|e| FileError {
123
                    kind: FileErrorKind::InvalidContentType(e),
124
                })?,
125
            ),
126
        ];
127

128
        self.oss_client()
129
            .builder_with_header(Method::PUT, url, canonicalized, headers)?
130
            .body(content)
131
            .send_adjust_error()
132
            .await
133
            .map_err(FileError::from)
134
    }
135

136
    /// # 获取 OSS 上文件的部分或全部内容
137
    /// 参数可指定范围:
138
    /// - `..` 获取文件的所有内容,常规大小的文件,使用这个即可
139
    /// - `..100`, `100..200`, `200..` 可用于获取文件的部分内容,一般用于大文件
140
    async fn get_oss<Num, R>(&self, range: R) -> Result<Vec<u8>, FileError>
141
    where
142
        R: Into<ContentRange<Num>> + Send + Sync,
143
        ContentRange<Num>: Into<HeaderValue>,
144
    {
145
        let (url, canonicalized) = self.get_std().ok_or(FileError {
146
            kind: FileErrorKind::NotFoundCanonicalizedResource,
147
        })?;
148

149
        let list: Vec<(_, HeaderValue)> = vec![(
150
            {
151
                #[allow(clippy::unwrap_used)]
152
                RANGE.parse().unwrap()
153
            },
154
            range.into().into(),
155
        )];
156

157
        let content = self
158
            .oss_client()
159
            .builder_with_header(Method::GET, url, canonicalized, list)?
160
            .send_adjust_error()
161
            .await?
162
            .text()
163
            .await?;
164

165
        Ok(content.into_bytes())
166
    }
167

168
    /// # 从 OSS 中删除文件
169
    async fn delete_oss(&self) -> Result<(), FileError> {
170
        let (url, canonicalized) = self.get_std().ok_or(FileError {
171
            kind: FileErrorKind::NotFoundCanonicalizedResource,
172
        })?;
173

174
        self.oss_client()
175
            .builder(Method::DELETE, url, canonicalized)?
176
            .send_adjust_error()
177
            .await?;
178

179
        Ok(())
180
    }
181
}
182

183
/// 获取请求 OSS 接口需要的信息
184
pub trait GetStd {
185
    /// 获取 `Url` 和 `CanonicalizedResource`
186
    fn get_std(&self) -> Option<(Url, CanonicalizedResource)>;
187
}
188

189
impl GetStd for ObjectBase<ArcPointer> {
190
    fn get_std(&self) -> Option<(Url, CanonicalizedResource)> {
1✔
191
        Some(self.get_url_resource([]))
1✔
192
    }
1✔
193
}
194

195
impl GetStd for Object<ArcPointer> {
196
    fn get_std(&self) -> Option<(Url, CanonicalizedResource)> {
2✔
197
        Some(self.base.get_url_resource([]))
2✔
198
    }
2✔
199
}
200

201
/// 根据给定路径,获取请求 OSS 接口需要的信息
202
pub trait GetStdWithPath<Path> {
203
    /// 根据 path 获取 `Url` 和 `CanonicalizedResource`
204
    fn get_std_with_path(&self, _path: Path) -> Option<(Url, CanonicalizedResource)>;
205
}
206

207
#[doc(hidden)]
208
pub mod std_path_impl {
209
    #[cfg(feature = "blocking")]
210
    use crate::builder::RcPointer;
211
    #[cfg(feature = "blocking")]
212
    use crate::client::ClientRc;
213

214
    use super::{GetStd, GetStdWithPath};
215
    use crate::{
216
        bucket::Bucket,
217
        builder::ArcPointer,
218
        client::ClientArc,
219
        config::{get_url_resource2 as get_url_resource, BucketBase},
220
        decode::RefineObject,
221
        object::{BuildInItemError, ObjectList},
222
        types::{object::ObjectBase, CanonicalizedResource},
223
        ObjectPath,
224
    };
225
    use oss_derive::oss_gen_rc;
226
    use reqwest::Url;
227

228
    /// # 用于在 Client 上对文件进行操作
229
    ///
230
    /// 文件路径可以是 `String` 类型
231
    ///
232
    /// [`ObjectPath`]: crate::ObjectPath
233
    #[oss_gen_rc]
234
    impl GetStdWithPath<String> for ClientArc {
235
        fn get_std_with_path(&self, path: String) -> Option<(Url, CanonicalizedResource)> {
2✔
236
            let object_path = path.try_into().ok()?;
2✔
237
            Some(get_url_resource(self, self, &object_path))
2✔
238
        }
2✔
239
    }
240

241
    /// # 用于在 Client 上对文件进行操作
242
    ///
243
    /// 文件路径可以是 `&str` 类型
244
    ///
245
    /// [`ObjectPath`]: crate::ObjectPath
246
    #[oss_gen_rc]
247
    impl GetStdWithPath<&str> for ClientArc {
248
        fn get_std_with_path(&self, path: &str) -> Option<(Url, CanonicalizedResource)> {
25✔
249
            let object_path = path.try_into().ok()?;
25✔
250
            Some(get_url_resource(self, self, &object_path))
19✔
251
        }
25✔
252
    }
253

254
    /// # 用于在 Client 上对文件进行操作
255
    ///
256
    /// 文件路径可以是 [`ObjectPath`] 类型
257
    ///
258
    /// [`ObjectPath`]: crate::ObjectPath
259
    #[oss_gen_rc]
260
    impl GetStdWithPath<ObjectPath> for ClientArc {
261
        fn get_std_with_path(&self, path: ObjectPath) -> Option<(Url, CanonicalizedResource)> {
10✔
262
            Some(get_url_resource(self, self, &path))
10✔
263
        }
10✔
264
    }
265

266
    /// # 用于在 Client 上对文件进行操作
267
    ///
268
    /// 文件路径可以是 [`&ObjectPath`] 类型
269
    ///
270
    /// [`&ObjectPath`]: crate::ObjectPath
271
    #[oss_gen_rc]
272
    impl<Path: AsRef<ObjectPath>> GetStdWithPath<Path> for ClientArc {
273
        fn get_std_with_path(&self, path: Path) -> Option<(Url, CanonicalizedResource)> {
1✔
274
            Some(get_url_resource(self, self, path.as_ref()))
1✔
275
        }
1✔
276
    }
277

278
    /// # 用于在 Bucket 上对文件进行操作
279
    ///
280
    /// 文件路径可以是 `String` 类型
281
    impl<B: AsRef<BucketBase>> GetStdWithPath<String> for B {
282
        fn get_std_with_path(&self, path: String) -> Option<(Url, CanonicalizedResource)> {
1✔
283
            let path = path.try_into().ok()?;
1✔
284
            Some(self.as_ref().get_url_resource_with_path(&path))
1✔
285
        }
1✔
286
    }
287

288
    /// # 用于在 Bucket 上对文件进行操作
289
    ///
290
    /// 文件路径可以是 `&str` 类型
291
    impl<B: AsRef<BucketBase>> GetStdWithPath<&str> for B {
292
        fn get_std_with_path(&self, path: &str) -> Option<(Url, CanonicalizedResource)> {
1✔
293
            let path = path.try_into().ok()?;
1✔
294
            Some(self.as_ref().get_url_resource_with_path(&path))
1✔
295
        }
1✔
296
    }
297

298
    /// # 用于在 Bucket 上对文件进行操作
299
    ///
300
    /// 文件路径可以是 [`ObjectPath`] 类型
301
    ///
302
    /// [`ObjectPath`]: crate::ObjectPath
303
    impl<B: AsRef<BucketBase>> GetStdWithPath<ObjectPath> for B {
304
        #[inline]
305
        fn get_std_with_path(&self, path: ObjectPath) -> Option<(Url, CanonicalizedResource)> {
1✔
306
            Some(self.as_ref().get_url_resource_with_path(&path))
1✔
307
        }
1✔
308
    }
309

310
    /// # 用于在 Bucket 上对文件进行操作
311
    ///
312
    /// 文件路径可以是 [`&ObjectPath`] 类型
313
    ///
314
    /// [`&ObjectPath`]: crate::ObjectPath
315
    impl<B: AsRef<BucketBase>> GetStdWithPath<&ObjectPath> for B {
316
        #[inline]
317
        fn get_std_with_path(&self, path: &ObjectPath) -> Option<(Url, CanonicalizedResource)> {
1✔
318
            Some(self.as_ref().get_url_resource_with_path(path))
1✔
319
        }
1✔
320
    }
321

322
    /// # 用于在 Bucket 上对文件进行操作
323
    ///
324
    /// 文件路径可以是 [`ObjectBase`] 类型
325
    ///
326
    /// [`ObjectBase`]: crate::types::object::ObjectBase
327
    #[oss_gen_rc]
328
    impl GetStdWithPath<ObjectBase<ArcPointer>> for Bucket {
329
        #[inline]
330
        fn get_std_with_path(
1✔
331
            &self,
332
            base: ObjectBase<ArcPointer>,
333
        ) -> Option<(Url, CanonicalizedResource)> {
334
            Some(base.get_url_resource([]))
1✔
335
        }
1✔
336
    }
337

338
    /// # 用于在 Bucket 上对文件进行操作
339
    ///
340
    /// 文件路径可以是 [`&ObjectBase`] 类型
341
    ///
342
    /// [`&ObjectBase`]: crate::types::object::ObjectBase
343
    #[oss_gen_rc]
344
    impl GetStdWithPath<&ObjectBase<ArcPointer>> for Bucket {
345
        #[inline]
346
        fn get_std_with_path(
1✔
347
            &self,
348
            base: &ObjectBase<ArcPointer>,
349
        ) -> Option<(Url, CanonicalizedResource)> {
350
            Some(base.get_url_resource([]))
1✔
351
        }
1✔
352
    }
353

354
    /// # 用于在 ObjectList 上对文件进行操作
355
    ///
356
    /// 文件路径可以是实现 [`GetStd`] 特征的类型
357
    ///
358
    /// [`GetStd`]: crate::file::GetStd
359
    ///
360
    /// TODO remove Send + Sync
361
    impl<Item: RefineObject<BuildInItemError> + Send + Sync, U: GetStd> GetStdWithPath<U>
362
        for ObjectList<ArcPointer, Item>
363
    {
364
        #[inline]
365
        fn get_std_with_path(&self, path: U) -> Option<(Url, CanonicalizedResource)> {
366
            path.get_std()
367
        }
368
    }
369
}
370

371
/// 默认 content-type
372
pub const DEFAULT_CONTENT_TYPE: &str = "application/octet-stream";
373

374
/// # 文件集合的相关操作
375
/// 在对文件执行相关操作的时候,需要指定文件路径
376
///
377
/// 包括 上传,下载,删除等功能
378
/// 在 [`Client`],[`Bucket`], [`ObjectList`] 等结构体中均已实现,其中 Client 是在默认的 bucket 上操作文件,
379
/// 而 Bucket, ObjectList 则是在当前的 bucket 上操作文件
380
///
381
/// [`Client`]: crate::client::Client
382
/// [`Bucket`]: crate::bucket::Bucket
383
/// [`ObjectList`]: crate::object::ObjectList
384
#[async_trait]
385
pub trait Files<Path>: AlignBuilder + GetStdWithPath<Path>
386
where
387
    Path: Send + Sync + 'static,
388
{
389
    /// # 默认的文件类型
390
    /// 在上传文件时,如果找不到合适的 mime 类型,可以使用
391
    const DEFAULT_CONTENT_TYPE: &'static str = DEFAULT_CONTENT_TYPE;
392

393
    /// # 上传文件到 OSS
394
    ///
395
    /// 需指定文件的路径
396
    ///
397
    /// *如果获取不到文件类型,则使用默认的文件类型,如果您不希望这么做,可以使用 `put_content_base` 方法*
398
    #[cfg(feature = "put_file")]
399
    async fn put_file<
400
        P: Into<std::path::PathBuf> + std::convert::AsRef<std::path::Path> + Send + Sync,
401
    >(
402
        &self,
403
        file_name: P,
404
        path: Path,
405
    ) -> Result<String, FileError> {
406
        let file_content = std::fs::read(file_name).map_err(|e| FileError {
407
            kind: error_impl::FileErrorKind::FileRead(e),
408
        })?;
409

410
        let get_content_type =
411
            |content: &Vec<u8>| Infer::new().get(content).map(|con| con.mime_type());
412

413
        self.put_content(file_content, path, get_content_type).await
414
    }
415

416
    /// # 上传文件内容到 OSS
417
    ///
418
    /// 需指定要上传的文件内容
419
    /// 以及根据文件内容获取文件类型的闭包
420
    ///
421
    /// *如果获取不到文件类型,则使用默认的文件类型,如果您不希望这么做,可以使用 `put_content_base` 方法*
422
    ///
423
    /// # Examples
424
    ///
425
    /// 上传 tauri 升级用的签名文件
426
    /// ```no_run
427
    /// # #[tokio::main]
428
    /// # async fn main(){
429
    /// use infer::Infer;
430
    /// # use dotenv::dotenv;
431
    /// # dotenv().ok();
432
    /// # let client = aliyun_oss_client::Client::from_env().unwrap();
433
    /// use aliyun_oss_client::file::Files;
434
    ///
435
    /// fn sig_match(buf: &[u8]) -> bool {
436
    ///     return buf.len() >= 3 && buf[0] == 0x64 && buf[1] == 0x57 && buf[2] == 0x35;
437
    /// }
438
    /// let mut infer = Infer::new();
439
    /// infer.add("application/pgp-signature", "sig", sig_match);
440
    ///
441
    /// let get_content_type = |content: &Vec<u8>| match infer.get(content) {
442
    ///     Some(con) => Some(con.mime_type()),
443
    ///     None => None,
444
    /// };
445
    /// let content: Vec<u8> = String::from("dW50cnVzdGVkIGNvbW1lbnQ6IHNpxxxxxxxxx").into_bytes();
446
    /// let res = client
447
    ///     .put_content(content, "xxxxxx.msi.zip.sig", get_content_type)
448
    ///     .await;
449
    /// assert!(res.is_ok());
450
    /// # }
451
    /// ```
452
    async fn put_content<F>(
453
        &self,
454
        content: Vec<u8>,
455
        path: Path,
456
        get_content_type: F,
457
    ) -> Result<String, FileError>
458
    where
459
        F: Fn(&Vec<u8>) -> Option<&'static str> + Send + Sync,
460
    {
461
        let content_type = get_content_type(&content).unwrap_or(Self::DEFAULT_CONTENT_TYPE);
462

463
        let content = self.put_content_base(content, content_type, path).await?;
464

465
        let result = content
466
            .headers()
467
            .get(ETAG)
468
            .ok_or(FileError {
469
                kind: FileErrorKind::EtagNotFound,
470
            })?
471
            .to_str()
472
            .map_err(|e| FileError {
473
                kind: FileErrorKind::InvalidEtag(e),
474
            })?;
475

476
        // TODO change to result[1..33].to_string()
477
        // 不能使用该方案,返回的etag长度不固定
478
        Ok(result.to_string())
479
    }
480

481
    /// 最核心的上传文件到 OSS 的方法
482
    async fn put_content_base(
4✔
483
        &self,
4✔
484
        content: Vec<u8>,
4✔
485
        content_type: &str,
486
        path: Path,
4✔
487
    ) -> Result<Response, FileError> {
11✔
488
        let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
8✔
489
            kind: FileErrorKind::NotFoundCanonicalizedResource,
4✔
490
        })?;
1✔
491

492
        let content_length = content.len().to_string();
3✔
493
        let headers = vec![
6✔
494
            (CONTENT_LENGTH, header_from_content_length(&content_length)?),
3✔
495
            (
2✔
496
                CONTENT_TYPE,
3✔
497
                content_type.parse().map_err(|e| FileError {
5✔
498
                    kind: FileErrorKind::InvalidContentType(e),
1✔
499
                })?,
2✔
500
            ),
1✔
501
        ];
502

503
        self.builder_with_header(Method::PUT, url, canonicalized, headers)?
6✔
504
            .body(content)
2✔
505
            //.timeout(std::time::Duration::new(3, 0))
506
            .send_adjust_error()
507
            .await
3✔
508
            .map_err(FileError::from)
509
    }
14✔
510

511
    /// # 获取 OSS 上文件的部分或全部内容
512
    async fn get_object<Num, R>(&self, path: Path, range: R) -> Result<Vec<u8>, FileError>
10✔
513
    where
514
        R: Into<ContentRange<Num>> + Send + Sync,
515
        ContentRange<Num>: Into<HeaderValue>,
516
    {
14✔
517
        let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
10✔
518
            kind: FileErrorKind::NotFoundCanonicalizedResource,
5✔
519
        })?;
1✔
520

521
        let list: Vec<(_, HeaderValue)> = vec![(
8✔
522
            {
523
                #[allow(clippy::unwrap_used)]
524
                RANGE.parse().unwrap()
4✔
525
            },
526
            range.into().into(),
4✔
527
        )];
528

529
        let content = self
20✔
530
            .builder_with_header(Method::GET, url, canonicalized, list)?
4✔
531
            .send_adjust_error()
532
            .await?
4✔
533
            .text()
534
            .await?;
4✔
535

536
        Ok(content.into_bytes())
4✔
537
    }
15✔
538

539
    /// # 删除 OSS 上的文件
540
    async fn delete_object(&self, path: Path) -> Result<(), FileError> {
13✔
541
        let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
6✔
542
            kind: FileErrorKind::NotFoundCanonicalizedResource,
3✔
543
        })?;
1✔
544

545
        self.builder(Method::DELETE, url, canonicalized)?
6✔
546
            .send_adjust_error()
547
            .await?;
8✔
548

549
        Ok(())
1✔
550
    }
9✔
551
}
552

553
fn header_from_content_length(content: &str) -> Result<HeaderValue, FileError> {
12✔
554
    HeaderValue::from_str(content).map_err(|e| FileError {
14✔
555
        kind: FileErrorKind::InvalidContentLength(e),
1✔
556
    })
1✔
557
}
12✔
558

559
/// # 为更多的类型实现 上传,下载,删除等功能
560
///
561
/// 在 [`Client`],[`Bucket`], [`ObjectList`] 等结构体中均已实现,其中 Client 是在默认的 bucket 上操作文件,
562
/// 而 Bucket, ObjectList 则是在当前的 bucket 上操作文件
563
///
564
/// [`Client`]: crate::client::Client
565
/// [`Bucket`]: crate::bucket::Bucket
566
/// [`ObjectList`]: crate::object::ObjectList
567
impl<P: Send + Sync + 'static, T: AlignBuilder + GetStdWithPath<P>> Files<P> for T {}
568

569
/// 文件模块的 Error
570
#[derive(Debug)]
16✔
571
pub struct FileError {
572
    kind: error_impl::FileErrorKind,
8✔
573
}
574

575
impl FileError {
576
    #[cfg(test)]
577
    pub(crate) fn test_new() -> Self {
1✔
578
        Self {
1✔
579
            kind: error_impl::FileErrorKind::EtagNotFound,
1✔
580
        }
581
    }
1✔
582
}
583

584
/// 文件模块的 Error 实现方法
585
mod error_impl {
586
    use std::{error::Error, fmt::Display, io::ErrorKind};
587

588
    use http::header::InvalidHeaderValue;
589

590
    use crate::builder::{reqwest_to_io, BuilderError};
591

592
    use super::FileError;
593

594
    impl Display for FileError {
595
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9✔
596
            use FileErrorKind::*;
597
            match &self.kind {
9✔
598
                #[cfg(feature = "put_file")]
599
                FileRead(_) => write!(f, "file read failed"),
1✔
600
                InvalidContentLength(_) => write!(f, "invalid content length"),
1✔
601
                InvalidContentType(_) => write!(f, "invalid content type"),
1✔
602
                Build(to) => write!(f, "{to}"),
1✔
603
                Reqwest(_) => write!(f, "reqwest error"),
1✔
604
                EtagNotFound => write!(f, "failed to get etag"),
2✔
605
                InvalidEtag(_) => write!(f, "invalid etag"),
1✔
606
                NotFoundCanonicalizedResource => write!(f, "not found canonicalized-resource"),
1✔
607
            }
608
        }
9✔
609
    }
610

611
    impl Error for FileError {
612
        fn source(&self) -> Option<&(dyn Error + 'static)> {
8✔
613
            use FileErrorKind::*;
614
            match &self.kind {
8✔
615
                #[cfg(feature = "put_file")]
616
                FileRead(e) => Some(e),
1✔
617
                InvalidContentLength(e) | InvalidContentType(e) => Some(e),
2✔
618
                Build(e) => e.source(),
1✔
619
                Reqwest(e) => Some(e),
1✔
620
                InvalidEtag(e) => Some(e),
1✔
621
                EtagNotFound | NotFoundCanonicalizedResource => None,
2✔
622
            }
623
        }
8✔
624
    }
625

626
    #[derive(Debug)]
8✔
627
    pub(super) enum FileErrorKind {
628
        #[cfg(feature = "put_file")]
629
        FileRead(std::io::Error),
1✔
630
        InvalidContentLength(InvalidHeaderValue),
1✔
631
        InvalidContentType(InvalidHeaderValue),
1✔
632
        Build(BuilderError),
1✔
633
        Reqwest(reqwest::Error),
1✔
634
        EtagNotFound,
635
        InvalidEtag(http::header::ToStrError),
1✔
636
        NotFoundCanonicalizedResource,
637
    }
638

639
    impl From<BuilderError> for FileError {
640
        fn from(value: BuilderError) -> Self {
16✔
641
            Self {
16✔
642
                kind: FileErrorKind::Build(value),
16✔
643
            }
644
        }
16✔
645
    }
646

647
    impl From<reqwest::Error> for FileError {
648
        fn from(value: reqwest::Error) -> Self {
1✔
649
            Self {
1✔
650
                kind: FileErrorKind::Reqwest(value),
1✔
651
            }
652
        }
1✔
653
    }
654

655
    impl From<FileError> for std::io::Error {
656
        fn from(FileError { kind }: FileError) -> Self {
3✔
657
            kind.into()
3✔
658
        }
3✔
659
    }
660

661
    impl From<FileErrorKind> for std::io::Error {
662
        fn from(value: FileErrorKind) -> Self {
3✔
663
            match value {
3✔
664
                #[cfg(feature = "put_file")]
665
                FileErrorKind::FileRead(e) => e,
1✔
666
                FileErrorKind::InvalidContentLength(_) => {
667
                    Self::new(ErrorKind::InvalidData, "invalid content length")
1✔
668
                }
669
                FileErrorKind::InvalidContentType(_) => {
670
                    Self::new(ErrorKind::InvalidData, "invalid content type")
1✔
671
                }
672
                FileErrorKind::Build(e) => e.into(),
×
673
                FileErrorKind::Reqwest(e) => reqwest_to_io(e),
×
674
                FileErrorKind::EtagNotFound => Self::new(ErrorKind::Interrupted, "etag not found"),
×
675
                FileErrorKind::InvalidEtag(_) => Self::new(ErrorKind::Interrupted, "invalid etag"),
×
676
                FileErrorKind::NotFoundCanonicalizedResource => {
677
                    Self::new(ErrorKind::InvalidData, "not found canonicalized resource")
×
678
                }
679
            }
680
        }
3✔
681
    }
682
}
683

684
/// # 对齐 [`Client`],[`Bucket`], [`ObjectList`] 等结构体的 trait
685
///
686
/// 用于他们方便的实现 [`Files`] trait
687
///
688
/// [`Files`]: self::Files
689
/// [`Client`]: crate::client::Client
690
/// [`Bucket`]: crate::bucket::Bucket
691
/// [`ObjectList`]: crate::object::ObjectList
692
pub trait AlignBuilder: Send + Sync {
693
    /// 根据具体的 API 接口参数,返回请求的构建器(不带 headers)
694
    #[inline]
695
    fn builder(
17✔
696
        &self,
697
        method: Method,
698
        url: Url,
699
        resource: CanonicalizedResource,
700
    ) -> Result<RequestBuilder, BuilderError> {
701
        self.builder_with_header(method, url, resource, [])
17✔
702
    }
17✔
703

704
    /// 根据具体的 API 接口参数,返回请求的构建器
705
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
706
        &self,
707
        method: Method,
708
        url: Url,
709
        resource: CanonicalizedResource,
710
        headers: H,
711
    ) -> Result<RequestBuilder, BuilderError>;
712
}
713

714
impl AlignBuilder for Bucket {
715
    #[inline]
716
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
2✔
717
        &self,
718
        method: Method,
719
        url: Url,
720
        resource: CanonicalizedResource,
721
        headers: H,
722
    ) -> Result<RequestBuilder, BuilderError> {
723
        self.client()
2✔
724
            .builder_with_header(method, url, resource, headers)
725
    }
2✔
726
}
727

728
impl<Item: Send + Sync> AlignBuilder for ObjectList<ArcPointer, Item> {
729
    #[inline]
730
    fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
731
        &self,
732
        method: Method,
733
        url: Url,
734
        resource: CanonicalizedResource,
735
        headers: H,
736
    ) -> Result<RequestBuilder, BuilderError> {
737
        self.client()
738
            .builder_with_header(method, url, resource, headers)
739
    }
740
}
741

742
#[cfg(feature = "blocking")]
743
pub use blocking::Files as BlockingFiles;
744

745
use self::error_impl::FileErrorKind;
746

747
/// 同步的文件模块
748
#[cfg(feature = "blocking")]
749
pub mod blocking {
750

751
    use super::{
752
        error_impl::FileErrorKind, header_from_content_length, FileError, GetStdWithPath, ETAG,
753
        RANGE,
754
    };
755
    use crate::{
756
        blocking::builder::RequestBuilder,
757
        bucket::Bucket,
758
        builder::{BuilderError, RcPointer},
759
        object::ObjectList,
760
        types::{CanonicalizedResource, ContentRange},
761
    };
762
    use http::{
763
        header::{HeaderName, CONTENT_LENGTH, CONTENT_TYPE},
764
        HeaderValue, Method,
765
    };
766
    #[cfg(feature = "put_file")]
767
    use infer::Infer;
768
    use reqwest::{blocking::Response, Url};
769

770
    /// # 文件集合的相关操作
771
    /// 在对文件执行相关操作的时候,需要指定文件路径
772
    pub trait Files<Path>: AlignBuilder + GetStdWithPath<Path> {
773
        /// # 默认的文件类型
774
        /// 在上传文件时,如果找不到合适的 mime 类型,可以使用
775
        const DEFAULT_CONTENT_TYPE: &'static str = super::DEFAULT_CONTENT_TYPE;
776

777
        /// # 上传文件到 OSS
778
        ///
779
        /// 需指定文件的路径
780
        #[cfg(feature = "put_file")]
781
        fn put_file<P: Into<std::path::PathBuf> + std::convert::AsRef<std::path::Path>>(
782
            &self,
783
            file_name: P,
784
            path: Path,
785
        ) -> Result<String, FileError> {
786
            let file_content = std::fs::read(file_name).map_err(|e| FileError {
787
                kind: FileErrorKind::FileRead(e),
788
            })?;
789

790
            let get_content_type =
791
                |content: &Vec<u8>| Infer::new().get(content).map(|con| con.mime_type());
792

793
            self.put_content(file_content, path, get_content_type)
794
        }
795

796
        /// # 上传文件内容到 OSS
797
        ///
798
        /// 需指定要上传的文件内容
799
        /// 以及根据文件内容获取文件类型的闭包
800
        ///
801
        /// # Examples
802
        ///
803
        /// 上传 tauri 升级用的签名文件
804
        /// ```no_run
805
        /// # fn main(){
806
        /// use infer::Infer;
807
        /// # use dotenv::dotenv;
808
        /// # dotenv().ok();
809
        /// # let client = aliyun_oss_client::ClientRc::from_env().unwrap();
810
        /// use crate::aliyun_oss_client::file::BlockingFiles;
811
        ///
812
        /// fn sig_match(buf: &[u8]) -> bool {
813
        ///     return buf.len() >= 3 && buf[0] == 0x64 && buf[1] == 0x57 && buf[2] == 0x35;
814
        /// }
815
        /// let mut infer = Infer::new();
816
        /// infer.add("application/pgp-signature", "sig", sig_match);
817
        ///
818
        /// let get_content_type = |content: &Vec<u8>| match infer.get(content) {
819
        ///     Some(con) => Some(con.mime_type()),
820
        ///     None => None,
821
        /// };
822
        /// let content: Vec<u8> = String::from("dW50cnVzdGVkIGNvbW1lbnQ6IHNpxxxxxxxxx").into_bytes();
823
        /// let res = client.put_content(content, "xxxxxx.msi.zip.sig", get_content_type);
824
        /// assert!(res.is_ok());
825
        /// # }
826
        /// ```
827
        fn put_content<F>(
828
            &self,
829
            content: Vec<u8>,
830
            path: Path,
831
            get_content_type: F,
832
        ) -> Result<String, FileError>
833
        where
834
            F: Fn(&Vec<u8>) -> Option<&'static str>,
835
        {
836
            let content_type = get_content_type(&content).unwrap_or(Self::DEFAULT_CONTENT_TYPE);
837

838
            let content = self.put_content_base(content, content_type, path)?;
839

840
            let result = content
841
                .headers()
842
                .get(ETAG)
843
                .ok_or(FileError {
844
                    kind: FileErrorKind::EtagNotFound,
845
                })?
846
                .to_str()
847
                .map_err(|e| FileError {
848
                    kind: FileErrorKind::InvalidEtag(e),
849
                })?;
850

851
            Ok(result.to_string())
852
        }
853

854
        /// 最原始的上传文件的方法
855
        fn put_content_base(
3✔
856
            &self,
857
            content: Vec<u8>,
858
            content_type: &str,
859
            path: Path,
860
        ) -> Result<Response, FileError> {
861
            let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
6✔
862
                kind: FileErrorKind::NotFoundCanonicalizedResource,
3✔
863
            })?;
1✔
864

865
            let content_length = content.len().to_string();
2✔
866
            let headers = vec![
4✔
867
                (CONTENT_LENGTH, header_from_content_length(&content_length)?),
2✔
868
                (
1✔
869
                    CONTENT_TYPE,
2✔
870
                    content_type.parse().map_err(|e| FileError {
4✔
871
                        kind: FileErrorKind::InvalidContentType(e),
1✔
872
                    })?,
2✔
873
                ),
1✔
874
            ];
875

876
            let response = self
1✔
877
                .builder_with_header(Method::PUT, url, canonicalized, headers)?
1✔
878
                .body(content);
1✔
879

880
            response.send_adjust_error().map_err(FileError::from)
1✔
881
        }
3✔
882

883
        /// # 获取文件内容
884
        fn get_object<Num, R>(&self, path: Path, range: R) -> Result<Vec<u8>, FileError>
5✔
885
        where
886
            R: Into<ContentRange<Num>>,
887
            ContentRange<Num>: Into<HeaderValue>,
888
        {
889
            let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
10✔
890
                kind: FileErrorKind::NotFoundCanonicalizedResource,
5✔
891
            })?;
1✔
892

893
            let headers: Vec<(_, HeaderValue)> = vec![(
8✔
894
                {
895
                    #[allow(clippy::unwrap_used)]
896
                    RANGE.parse().unwrap()
4✔
897
                },
898
                range.into().into(),
4✔
899
            )];
900

901
            Ok(self
4✔
902
                .builder_with_header(Method::GET, url, canonicalized, headers)?
4✔
903
                .send_adjust_error()?
×
904
                .text()?
×
905
                .into_bytes())
906
        }
5✔
907

908
        /// # 删除 OSS 上的文件
909
        fn delete_object(&self, path: Path) -> Result<(), FileError> {
2✔
910
            let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError {
4✔
911
                kind: FileErrorKind::NotFoundCanonicalizedResource,
2✔
912
            })?;
1✔
913

914
            self.builder(Method::DELETE, url, canonicalized)?
1✔
915
                .send_adjust_error()?;
1✔
916

917
            Ok(())
1✔
918
        }
2✔
919
    }
920

921
    impl<P, T: AlignBuilder + GetStdWithPath<P>> Files<P> for T {}
922

923
    /// 对 Client 中的请求构建器进行抽象
924
    pub trait AlignBuilder {
925
        /// 根据具体的 API 接口参数,返回请求的构建器(不带 headers)
926
        #[inline]
927
        fn builder(
13✔
928
            &self,
929
            method: Method,
930
            url: Url,
931
            resource: CanonicalizedResource,
932
        ) -> Result<RequestBuilder, BuilderError> {
933
            self.builder_with_header(method, url, resource, [])
13✔
934
        }
13✔
935

936
        /// 根据具体的 API 接口参数,返回请求的构建器
937
        fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
938
            &self,
939
            method: Method,
940
            url: Url,
941
            resource: CanonicalizedResource,
942
            headers: H,
943
        ) -> Result<RequestBuilder, BuilderError>;
944
    }
945

946
    /// # 对齐 Client, Bucket, ObjectList 等结构体的 trait
947
    ///
948
    /// 用于他们方便的实现 [`File`](./trait.File.html) trait
949
    impl AlignBuilder for Bucket<RcPointer> {
950
        fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
1✔
951
            &self,
952
            method: Method,
953
            url: Url,
954
            resource: CanonicalizedResource,
955
            headers: H,
956
        ) -> Result<RequestBuilder, BuilderError> {
957
            self.client()
1✔
958
                .builder_with_header(method, url, resource, headers)
959
        }
1✔
960
    }
961

962
    impl AlignBuilder for ObjectList<RcPointer> {
963
        fn builder_with_header<H: IntoIterator<Item = (HeaderName, HeaderValue)>>(
1✔
964
            &self,
965
            method: Method,
966
            url: Url,
967
            resource: CanonicalizedResource,
968
            headers: H,
969
        ) -> Result<RequestBuilder, BuilderError> {
970
            self.client()
1✔
971
                .builder_with_header(method, url, resource, headers)
972
        }
1✔
973
    }
974
}
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