opendal/services/b2/
error.rs1use bytes::Buf;
19use http::Response;
20use serde::Deserialize;
21
22use crate::raw::*;
23use crate::*;
24
25#[derive(Default, Debug, Deserialize)]
27#[allow(dead_code)]
28struct B2Error {
29 status: u32,
30 code: String,
31 message: String,
32}
33
34pub(super) fn parse_error(resp: Response<Buffer>) -> Error {
36 let (parts, body) = resp.into_parts();
37 let bs = body.to_bytes();
38
39 let (mut kind, mut retryable) = match parts.status.as_u16() {
40 403 => (ErrorKind::PermissionDenied, false),
41 404 => (ErrorKind::NotFound, false),
42 304 | 412 => (ErrorKind::ConditionNotMatch, false),
43 401 => (ErrorKind::PermissionDenied, true),
45 429 => (ErrorKind::RateLimited, true),
46 500 | 502 | 503 | 504 => (ErrorKind::Unexpected, true),
47 _ => (ErrorKind::Unexpected, false),
48 };
49
50 let (message, b2_err) = serde_json::from_reader::<_, B2Error>(bs.clone().reader())
51 .map(|b2_err| (format!("{b2_err:?}"), Some(b2_err)))
52 .unwrap_or_else(|_| (String::from_utf8_lossy(&bs).into_owned(), None));
53
54 if let Some(b2_err) = b2_err {
55 (kind, retryable) = parse_b2_error_code(b2_err.code.as_str()).unwrap_or((kind, retryable));
56 };
57
58 let mut err = Error::new(kind, message);
59
60 err = with_error_response_context(err, parts);
61
62 if retryable {
63 err = err.set_temporary();
64 }
65
66 err
67}
68
69pub(crate) fn parse_b2_error_code(code: &str) -> Option<(ErrorKind, bool)> {
71 match code {
72 "already_hidden" => Some((ErrorKind::AlreadyExists, false)),
73 "no_such_file" => Some((ErrorKind::NotFound, false)),
74 _ => None,
75 }
76}
77
78#[cfg(test)]
79mod test {
80 use http::StatusCode;
81
82 use super::*;
83
84 #[test]
85 fn test_parse_b2_error_code() {
86 let code = "already_hidden";
87 assert_eq!(
88 parse_b2_error_code(code),
89 Some((crate::ErrorKind::AlreadyExists, false))
90 );
91
92 let code = "no_such_file";
93 assert_eq!(
94 parse_b2_error_code(code),
95 Some((crate::ErrorKind::NotFound, false))
96 );
97
98 let code = "not_found";
99 assert_eq!(parse_b2_error_code(code), None);
100 }
101
102 #[tokio::test]
103 async fn test_parse_error() {
104 let err_res = vec![
105 (
106 r#"{"status": 403, "code": "access_denied", "message":"The provided customer-managed encryption key is wrong."}"#,
107 ErrorKind::PermissionDenied,
108 StatusCode::FORBIDDEN,
109 ),
110 (
111 r#"{"status": 404, "code": "not_found", "message":"File is not in B2 Cloud Storage."}"#,
112 ErrorKind::NotFound,
113 StatusCode::NOT_FOUND,
114 ),
115 (
116 r#"{"status": 401, "code": "bad_auth_token", "message":"The auth token used is not valid. Call b2_authorize_account again to either get a new one, or an error message describing the problem."}"#,
117 ErrorKind::PermissionDenied,
118 StatusCode::UNAUTHORIZED,
119 ),
120 ];
121
122 for res in err_res {
123 let bs = bytes::Bytes::from(res.0);
124 let body = Buffer::from(bs);
125 let resp = Response::builder().status(res.2).body(body).unwrap();
126
127 let err = parse_error(resp);
128
129 assert_eq!(err.kind(), res.1);
130 }
131 }
132}