aliyun_oss_client/
errors.rs

1//! 异常处理模块
2
3use http::StatusCode;
4use reqwest::Url;
5use std::{
6    fmt::{self, Display},
7    io,
8};
9use thiserror::Error;
10
11#[cfg(feature = "decode")]
12use crate::decode::{InnerItemError, InnerListError};
13use crate::{
14    auth::AuthError,
15    bucket::{BucketError, ExtractItemError},
16    builder::BuilderError,
17    config::InvalidConfig,
18    file::FileError,
19    object::{ExtractListError, ObjectListError},
20    types::{
21        object::{InvalidObjectDir, InvalidObjectPath},
22        InvalidBucketName, InvalidEndPoint,
23    },
24};
25
26/// aliyun-oss-client Error
27#[derive(Debug)]
28#[non_exhaustive]
29pub struct OssError {
30    kind: OssErrorKind,
31}
32
33impl Display for OssError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        self.kind.fmt(f)
36    }
37}
38impl std::error::Error for OssError {
39    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40        use OssErrorKind::*;
41        match &self.kind {
42            Io(e) => Some(e),
43            #[cfg(test)]
44            Dotenv(e) => Some(e),
45            Builder(e) => Some(e),
46            EndPoint(e) => Some(e),
47            BucketName(e) => Some(e),
48            Config(e) => Some(e),
49            ObjectPath(e) => Some(e),
50            ObjectDir(e) => Some(e),
51            BuildInItemError(e) => Some(e),
52            InnerList(e) => e.get_source(),
53            InnerItem(e) => e.get_source(),
54            ExtractList(e) => Some(e),
55            ExtractItem(e) => Some(e),
56            File(e) => Some(e),
57            Auth(e) => Some(e),
58            Bucket(e) => Some(e),
59            ObjectList(e) => Some(e),
60        }
61    }
62}
63
64impl<T: Into<OssErrorKind>> From<T> for OssError {
65    fn from(value: T) -> Self {
66        Self { kind: value.into() }
67    }
68}
69
70/// 内置的 Error 集合
71#[derive(Debug, Error)]
72#[non_exhaustive]
73enum OssErrorKind {
74    #[error("io error")]
75    Io(#[from] io::Error),
76
77    #[doc(hidden)]
78    #[error("dotenv error")]
79    #[cfg(test)]
80    Dotenv(#[from] dotenv::Error),
81
82    #[doc(hidden)]
83    #[error("builder error")]
84    Builder(#[from] BuilderError),
85
86    #[doc(hidden)]
87    #[error("invalid endpoint")]
88    EndPoint(#[from] InvalidEndPoint),
89
90    #[doc(hidden)]
91    #[error("invalid bucket name")]
92    BucketName(#[from] InvalidBucketName),
93
94    #[doc(hidden)]
95    #[error("invalid config")]
96    Config(#[from] InvalidConfig),
97
98    #[doc(hidden)]
99    #[error("invalid object path")]
100    ObjectPath(#[from] InvalidObjectPath),
101
102    #[doc(hidden)]
103    #[error("invalid object dir")]
104    ObjectDir(#[from] InvalidObjectDir),
105
106    #[doc(hidden)]
107    #[error("build in item error")]
108    BuildInItemError(#[from] crate::object::BuildInItemError),
109
110    #[cfg(feature = "decode")]
111    #[doc(hidden)]
112    #[error("decode into list error")]
113    InnerList(InnerListError),
114
115    #[cfg(feature = "decode")]
116    #[doc(hidden)]
117    #[error("decode into list error")]
118    InnerItem(InnerItemError),
119
120    #[doc(hidden)]
121    #[error("extract list error")]
122    ExtractList(#[from] ExtractListError),
123
124    #[doc(hidden)]
125    #[error("extract item error")]
126    ExtractItem(#[from] ExtractItemError),
127
128    #[error("file error")]
129    File(#[from] FileError),
130
131    #[error("auth error")]
132    Auth(#[from] AuthError),
133
134    // bucket 还有其他 Error
135    #[error("bucket error")]
136    Bucket(#[from] BucketError),
137
138    #[error("object list error")]
139    ObjectList(#[from] ObjectListError),
140}
141
142#[cfg(feature = "decode")]
143impl From<InnerListError> for OssErrorKind {
144    fn from(value: InnerListError) -> Self {
145        Self::InnerList(value)
146    }
147}
148
149#[cfg(feature = "decode")]
150impl From<InnerItemError> for OssErrorKind {
151    fn from(value: InnerItemError) -> Self {
152        Self::InnerItem(value)
153    }
154}
155
156/// # 保存并返回 OSS 服务端返回是数据
157/// 当服务器返回的状态码不在 200<=x 且 x<300 范围时,则会返回此错误
158///
159/// 如果解析 xml 格式错误,则会返回默认值,默认值的 status = 200
160#[derive(Debug, Error, PartialEq, Eq, Hash)]
161pub struct OssService {
162    pub(crate) code: String,
163    status: StatusCode,
164    message: String,
165    request_id: String,
166    url: Url,
167}
168
169impl Default for OssService {
170    fn default() -> Self {
171        Self {
172            code: "Undefined".to_owned(),
173            status: StatusCode::default(),
174            message: "Parse aliyun response xml error message failed.".to_owned(),
175            request_id: "XXXXXXXXXXXXXXXXXXXXXXXX".to_owned(),
176            url: {
177                #[allow(clippy::unwrap_used)]
178                "https://oss.aliyuncs.com".parse().unwrap()
179            },
180        }
181    }
182}
183
184impl fmt::Display for OssService {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        f.debug_struct("OssService")
187            .field("code", &self.code)
188            .field("status", &self.status)
189            .field("message", &self.message)
190            .field("request_id", &self.request_id)
191            .field("url", &self.url.as_str())
192            .finish()
193    }
194}
195
196impl AsRef<Url> for OssService {
197    fn as_ref(&self) -> &Url {
198        &self.url
199    }
200}
201
202impl<'a> OssService {
203    /// 解析 oss 的错误信息
204    pub fn new(source: &'a str, status: &StatusCode, url: &Url) -> Self {
205        match (
206            source.find("<Code>"),
207            source.find("</Code>"),
208            source.find("<Message>"),
209            source.find("</Message>"),
210            source.find("<RequestId>"),
211            source.find("</RequestId>"),
212        ) {
213            (
214                Some(code0),
215                Some(code1),
216                Some(message0),
217                Some(message1),
218                Some(request_id0),
219                Some(request_id1),
220            ) => {
221                let code = &source[code0 + 6..code1].to_owned();
222                let mut message = source[message0 + 9..message1].to_owned();
223                if code == "SignatureDoesNotMatch" {
224                    message.push_str(&format!(
225                        "expect sign string is \"{}\"",
226                        Self::sign_string(source)
227                    ));
228                }
229                Self {
230                    code: code.to_owned(),
231                    status: *status,
232                    message,
233                    request_id: source[request_id0 + 11..request_id1].to_owned(),
234                    url: url.to_owned(),
235                }
236            }
237            _ => Self::default(),
238        }
239    }
240
241    /// 解析 oss 的错误信息
242    pub fn new2(source: String, status: &StatusCode, url: Url) -> Self {
243        match (
244            source.find("<Code>"),
245            source.find("</Code>"),
246            source.find("<Message>"),
247            source.find("</Message>"),
248            source.find("<RequestId>"),
249            source.find("</RequestId>"),
250        ) {
251            (
252                Some(code0),
253                Some(code1),
254                Some(message0),
255                Some(message1),
256                Some(request_id0),
257                Some(request_id1),
258            ) => {
259                let code = &source[code0 + 6..code1].to_owned();
260                let mut message = source[message0 + 9..message1].to_owned();
261                if code == "SignatureDoesNotMatch" {
262                    message.push_str(&format!(
263                        "expect sign string is \"{}\"",
264                        Self::sign_string(&source)
265                    ));
266                }
267                Self {
268                    code: code.to_owned(),
269                    status: *status,
270                    message,
271                    request_id: source[request_id0 + 11..request_id1].to_owned(),
272                    url,
273                }
274            }
275            _ => Self::default(),
276        }
277    }
278
279    /// 返回报错接口的 url
280    pub fn url(&self) -> &Url {
281        &self.url
282    }
283
284    fn sign_string(s: &str) -> &str {
285        if let (Some(start), Some(end)) = (s.find("<StringToSign>"), s.find("</StringToSign>")) {
286            &s[start + 14..end]
287        } else {
288            &s[0..0]
289        }
290    }
291}
292
293impl From<OssService> for std::io::Error {
294    fn from(err: OssService) -> Self {
295        use std::io::ErrorKind;
296        let kind = if err.status.is_client_error() {
297            ErrorKind::PermissionDenied
298        } else if err.status.is_server_error() {
299            ErrorKind::ConnectionReset
300        } else {
301            ErrorKind::ConnectionAborted
302        };
303        Self::new(kind, err)
304    }
305}
306
307/// 内置的 Result
308pub type OssResult<T> = Result<T, OssError>;
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn oss_service_display() {
316        assert_eq!(
317            format!(
318                "{}",
319                OssService {
320                    code: "abc".to_owned(),
321                    status: StatusCode::OK,
322                    message: "mes1".to_owned(),
323                    request_id: "xx".to_owned(),
324                    url: "https://oss.aliyuncs.com".parse().unwrap()
325                }
326            ),
327            "OssService { code: \"abc\", status: 200, message: \"mes1\", request_id: \"xx\", url: \"https://oss.aliyuncs.com/\" }"
328        );
329    }
330
331    #[test]
332    fn oss_service_default() {
333        let oss = OssService::default();
334        assert_eq!(oss.code, "Undefined".to_string());
335        assert_eq!(oss.status, StatusCode::OK);
336        assert_eq!(
337            oss.message,
338            "Parse aliyun response xml error message failed.".to_owned()
339        );
340        assert_eq!(oss.request_id, "XXXXXXXXXXXXXXXXXXXXXXXX".to_owned());
341    }
342
343    #[test]
344    fn test_oss_service_fmt() {
345        let oss_err = OssService {
346            code: "OSS_TEST_CODE".to_string(),
347            status: StatusCode::default(),
348            message: "foo_msg".to_string(),
349            request_id: "foo_req_id".to_string(),
350            url: "https://oss.aliyuncs.com".parse().unwrap(),
351        };
352
353        assert_eq!(
354            format!("{}", oss_err),
355            "OssService { code: \"OSS_TEST_CODE\", status: 200, message: \"foo_msg\", request_id: \"foo_req_id\", url: \"https://oss.aliyuncs.com/\" }"
356            .to_string()
357        );
358        let url = oss_err.as_ref();
359        assert_eq!(*url, Url::parse("https://oss.aliyuncs.com").unwrap());
360        let url = oss_err.url();
361        assert_eq!(*url, Url::parse("https://oss.aliyuncs.com").unwrap());
362    }
363
364    #[test]
365    fn test_oss_service_new() {
366        let content = r#"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
367    <Error>
368        <Code>RequestTimeTooSkewed</Code>
369        <Message>bar</Message>
370        <RequestId>63145DB90BFD85303279D56B</RequestId>
371        <HostId>xxx.oss-cn-shanghai.aliyuncs.com</HostId>
372        <MaxAllowedSkewMilliseconds>900000</MaxAllowedSkewMilliseconds>
373        <RequestTime>2022-09-04T07:11:33.000Z</RequestTime>
374        <ServerTime>2022-09-04T08:11:37.000Z</ServerTime>
375    </Error>
376    "#;
377        let url = "https://oss.aliyuncs.com".parse().unwrap();
378        let service = OssService::new(content, &StatusCode::default(), &url);
379        assert_eq!(service.code, format!("RequestTimeTooSkewed"));
380        assert_eq!(service.message, format!("bar"));
381        assert_eq!(service.request_id, format!("63145DB90BFD85303279D56B"))
382    }
383
384    #[test]
385    fn test_sign_match() {
386        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
387        <Error>
388          <Code>SignatureDoesNotMatch</Code>
389          <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
390          <RequestId>64C9CF1C8B62C239371D3E6B</RequestId>
391          <HostId>xxx.oss-cn-shanghai.aliyuncs.com</HostId>
392          <OSSAccessKeyId>9js44GwYF9P2ZFs4</OSSAccessKeyId>
393          <SignatureProvided>ZZ3e/hrGjFpOxRkDg+ugKVGMyoc=</SignatureProvided>
394          <StringToSign>PUT
395
396
397Wed, 02 Aug 2023 03:35:56 GMT
398/xxx/aaabbb.txt?partNumber=1</StringToSign>
399          <StringToSignBytes>50 55 54 0A 0A 0A 57 65 64 2C 20 30 32 20 41 75 67 20 32 30 32 33 20 30 33 3A 33 35 3A 35 36 20 47 4D 54 0A 2F 68 6F 6E 67 6C 65 69 31 32 33 2F 61 61 61 62 62 62 2E 74 78 74 3F 70 61 72 74 4E 75 6D 62 65 72 3D 31 </StringToSignBytes>
400          <EC>0002-00000040</EC>
401        </Error>"#;
402        let url = "https://oss.aliyuncs.com".parse().unwrap();
403        let service = OssService::new(xml, &StatusCode::default(), &url);
404        assert_eq!(service.code, format!("SignatureDoesNotMatch"));
405        assert_eq!(service.message, format!("The request signature we calculated does not match the signature you provided. Check your key and signing method.expect sign string is \"PUT\n\n\nWed, 02 Aug 2023 03:35:56 GMT\n/xxx/aaabbb.txt?partNumber=1\""));
406        assert_eq!(service.request_id, format!("64C9CF1C8B62C239371D3E6B"));
407
408        let service = OssService::new2(xml.to_owned(), &StatusCode::default(), url);
409        assert_eq!(service.code, format!("SignatureDoesNotMatch"));
410        assert_eq!(service.message, format!("The request signature we calculated does not match the signature you provided. Check your key and signing method.expect sign string is \"PUT\n\n\nWed, 02 Aug 2023 03:35:56 GMT\n/xxx/aaabbb.txt?partNumber=1\""));
411        assert_eq!(service.request_id, format!("64C9CF1C8B62C239371D3E6B"))
412    }
413
414    #[test]
415    fn oss_service_new() {
416        let url = "https://oss.aliyuncs.com".parse().unwrap();
417        assert_eq!(
418            OssService::new("abc", &StatusCode::OK, &url),
419            OssService::default()
420        );
421
422        assert_eq!(
423            OssService::new2("abc".to_string(), &StatusCode::OK, url),
424            OssService::default()
425        );
426    }
427
428    #[test]
429    fn sign_string() {
430        assert_eq!(OssService::sign_string("aaa"), "");
431    }
432
433    #[test]
434    fn from_oss_service() {
435        use std::io::{Error, ErrorKind};
436        let url: Url = "https://oss.aliyuncs.com".parse().unwrap();
437        let oss = OssService {
438            code: "aaa".to_string(),
439            message: "aaa".to_string(),
440            status: StatusCode::BAD_REQUEST,
441            request_id: "bbb".to_string(),
442            url: url.clone(),
443        };
444        let io_err = Error::from(oss);
445        assert_eq!(io_err.kind(), ErrorKind::PermissionDenied);
446
447        let oss = OssService {
448            code: "aaa".to_string(),
449            message: "aaa".to_string(),
450            status: StatusCode::BAD_GATEWAY,
451            request_id: "bbb".to_string(),
452            url: url.clone(),
453        };
454        let io_err = Error::from(oss);
455        assert_eq!(io_err.kind(), ErrorKind::ConnectionReset);
456
457        let oss = OssService {
458            code: "aaa".to_string(),
459            message: "aaa".to_string(),
460            status: StatusCode::NOT_MODIFIED,
461            request_id: "bbb".to_string(),
462            url: url.clone(),
463        };
464        let io_err = Error::from(oss);
465        assert_eq!(io_err.kind(), ErrorKind::ConnectionAborted);
466    }
467}