• 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

77.39
/src/builder.rs
1
//! 封装了 reqwest::RequestBuilder 模块
2✔
2

3
use async_trait::async_trait;
4
use http::Method;
5
use reqwest::{
6
    header::{HeaderMap, HeaderName, HeaderValue},
7
    Body, IntoUrl,
8
};
9
#[cfg(feature = "blocking")]
10
use std::rc::Rc;
11
use std::{
12
    error::Error,
13
    io::{self, ErrorKind},
14
};
15
use std::{fmt::Display, sync::Arc, time::Duration};
16

17
use crate::auth::AuthError;
18
#[cfg(feature = "blocking")]
19
use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware;
20
use crate::{
21
    client::Client as AliClient,
22
    config::{BucketBase, InvalidConfig},
23
    errors::OssService,
24
};
25
use reqwest::{Client, Request, Response};
26

27
#[cfg(test)]
28
pub(crate) mod test;
29

30
pub trait PointerFamily: private::Sealed
31
where
32
    Self::Bucket: std::fmt::Debug + Clone + Default + std::hash::Hash,
33
    Self::PointerType: Default,
34
{
35
    type PointerType;
36
    type Bucket;
37
}
38

39
mod private {
40
    pub trait Sealed {}
41
}
42

43
#[derive(Default, Debug)]
1✔
44
pub struct ArcPointer;
45

46
impl private::Sealed for ArcPointer {}
47

48
impl PointerFamily for ArcPointer {
49
    type PointerType = Arc<AliClient<ClientWithMiddleware>>;
50
    type Bucket = Arc<BucketBase>;
51
}
52

53
#[cfg(feature = "blocking")]
54
#[derive(Default, Debug)]
1✔
55
pub struct RcPointer;
56

57
#[cfg(feature = "blocking")]
58
impl private::Sealed for RcPointer {}
59

60
#[cfg(feature = "blocking")]
61
impl PointerFamily for RcPointer {
62
    type PointerType = Rc<AliClient<BlockingClientWithMiddleware>>;
63
    type Bucket = Rc<BucketBase>;
64
}
65

66
#[derive(Default, Clone, Debug)]
188✔
67
pub struct ClientWithMiddleware {
68
    inner: Client,
94✔
69
    middleware: Option<Arc<dyn Middleware>>,
94✔
70
}
71

72
#[async_trait]
73
pub trait Middleware: 'static + Send + Sync + std::fmt::Debug {
74
    async fn handle(&self, request: Request) -> Result<Response, BuilderError>;
75
}
76

77
impl ClientWithMiddleware {
78
    pub fn new(inner: Client) -> Self {
×
79
        Self {
×
80
            inner,
81
            middleware: None,
×
82
        }
83
    }
×
84

85
    pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
37✔
86
        RequestBuilder {
37✔
87
            inner: self.inner.request(method, url),
37✔
88
            middleware: self.middleware.clone(),
37✔
89
        }
90
    }
37✔
91

92
    pub fn middleware(&mut self, middleware: Arc<dyn Middleware>) {
18✔
93
        self.middleware = Some(middleware);
18✔
94
    }
18✔
95
}
96

97
pub struct RequestBuilder {
98
    inner: reqwest::RequestBuilder,
99
    middleware: Option<Arc<dyn Middleware>>,
100
}
101

102
impl RequestBuilder {
103
    #[allow(dead_code)]
104
    pub(crate) fn header<K, V>(self, key: K, value: V) -> Self
105
    where
106
        HeaderName: TryFrom<K>,
107
        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
108
        HeaderValue: TryFrom<V>,
109
        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
110
    {
111
        RequestBuilder {
112
            inner: self.inner.header(key, value),
113
            ..self
114
        }
115
    }
116

117
    pub(crate) fn headers(self, headers: HeaderMap) -> Self {
37✔
118
        RequestBuilder {
37✔
119
            inner: self.inner.headers(headers),
37✔
120
            ..self
121
        }
122
    }
37✔
123

124
    pub(crate) fn body<T: Into<Body>>(self, body: T) -> Self {
10✔
125
        RequestBuilder {
10✔
126
            inner: self.inner.body(body),
10✔
127
            ..self
128
        }
129
    }
10✔
130

131
    pub(crate) fn timeout(self, timeout: Duration) -> Self {
1✔
132
        RequestBuilder {
1✔
133
            inner: self.inner.timeout(timeout),
1✔
134
            ..self
135
        }
136
    }
1✔
137

138
    #[allow(dead_code)]
139
    pub(crate) fn build(self) -> reqwest::Result<Request> {
3✔
140
        self.inner.build()
3✔
141
    }
3✔
142

143
    /// 发送请求,获取响应后,直接返回 Response
144
    pub async fn send(self) -> Result<Response, BuilderError> {
×
145
        match self.middleware {
146
            Some(m) => {
147
                m.handle(self.inner.build().map_err(BuilderError::from)?)
148
                    .await
149
            }
150
            None => self.inner.send().await.map_err(BuilderError::from),
151
        }
152
    }
×
153

154
    /// 发送请求,获取响应后,解析 xml 文件,如果有错误,返回 Err 否则返回 Response
155
    pub async fn send_adjust_error(self) -> Result<Response, BuilderError> {
115✔
156
        match self.middleware {
34✔
157
            Some(m) => {
21✔
158
                m.handle(self.inner.build().map_err(BuilderError::from)?)
42✔
159
                    .await
21✔
160
            }
21✔
161
            None => check_http_status(self.inner.send().await.map_err(BuilderError::from)?)
85✔
162
                .await
14✔
163
                .map_err(BuilderError::from),
164
        }
165
    }
68✔
166
}
167

168
#[derive(Debug)]
24✔
169
#[non_exhaustive]
170
pub struct BuilderError {
171
    pub(crate) kind: BuilderErrorKind,
12✔
172
}
173

174
impl BuilderError {
175
    #[cfg(test)]
176
    pub(crate) fn bar() -> Self {
2✔
177
        Self {
2✔
178
            kind: BuilderErrorKind::Bar,
2✔
179
        }
180
    }
2✔
181

182
    pub(crate) fn from_reqwest(reqwest: reqwest::Error) -> Self {
×
183
        Self {
×
184
            kind: BuilderErrorKind::Reqwest(Box::new(reqwest)),
×
185
        }
186
    }
×
187
}
188

189
#[derive(Debug)]
12✔
190
#[non_exhaustive]
191
pub(crate) enum BuilderErrorKind {
192
    Reqwest(Box<reqwest::Error>),
1✔
193

194
    OssService(Box<OssService>),
6✔
195

196
    Auth(Box<AuthError>),
1✔
197

198
    Config(Box<InvalidConfig>),
1✔
199

200
    #[cfg(test)]
201
    Bar,
202
}
203

204
impl Display for BuilderError {
205
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8✔
206
        use BuilderErrorKind::*;
207
        match &self.kind {
8✔
208
            Reqwest(_) => "reqwest error".fmt(f),
1✔
209
            OssService(_) => "http status is not success".fmt(f),
1✔
210
            Auth(_) => "aliyun auth failed".fmt(f),
1✔
211
            Config(_) => "oss config error".fmt(f),
1✔
212
            #[cfg(test)]
213
            Bar => "bar".fmt(f),
4✔
214
        }
215
    }
8✔
216
}
217

218
impl Error for BuilderError {
219
    fn source(&self) -> Option<&(dyn Error + 'static)> {
5✔
220
        use BuilderErrorKind::*;
221
        match &self.kind {
5✔
222
            Reqwest(e) => Some(e),
1✔
223
            OssService(e) => Some(e),
1✔
224
            Auth(e) => Some(e),
1✔
225
            Config(e) => Some(e),
1✔
226
            #[cfg(test)]
227
            Bar => None,
1✔
228
        }
229
    }
5✔
230
}
231

232
impl From<reqwest::Error> for BuilderError {
233
    fn from(value: reqwest::Error) -> Self {
1✔
234
        Self {
1✔
235
            kind: BuilderErrorKind::Reqwest(Box::new(value)),
1✔
236
        }
237
    }
1✔
238
}
239
impl From<OssService> for BuilderError {
240
    fn from(value: OssService) -> Self {
23✔
241
        Self {
23✔
242
            kind: BuilderErrorKind::OssService(Box::new(value)),
23✔
243
        }
244
    }
23✔
245
}
246

247
impl From<AuthError> for BuilderError {
248
    fn from(value: AuthError) -> Self {
1✔
249
        Self {
1✔
250
            kind: BuilderErrorKind::Auth(Box::new(value)),
1✔
251
        }
252
    }
1✔
253
}
254
impl From<InvalidConfig> for BuilderError {
255
    fn from(value: InvalidConfig) -> Self {
1✔
256
        Self {
1✔
257
            kind: BuilderErrorKind::Config(Box::new(value)),
1✔
258
        }
259
    }
1✔
260
}
261

262
impl From<BuilderError> for io::Error {
263
    fn from(BuilderError { kind }: BuilderError) -> Self {
×
264
        match kind {
×
265
            BuilderErrorKind::Reqwest(req) => reqwest_to_io(*req),
×
266
            BuilderErrorKind::OssService(e) => Self::from(*e),
×
267
            BuilderErrorKind::Auth(auth) => Self::new(ErrorKind::PermissionDenied, auth),
×
268
            BuilderErrorKind::Config(conf) => Self::new(ErrorKind::InvalidInput, conf),
×
269
            #[cfg(test)]
270
            BuilderErrorKind::Bar => unreachable!("only used in tests"),
×
271
        }
272
    }
×
273
}
274

275
pub(crate) fn reqwest_to_io(req: reqwest::Error) -> io::Error {
×
276
    let kind = if req.is_timeout() {
×
277
        ErrorKind::TimedOut
×
278
    } else if req.is_connect() {
×
279
        ErrorKind::ConnectionAborted
×
280
    } else {
281
        ErrorKind::Other
×
282
    };
283
    io::Error::new(kind, req)
×
284
}
×
285

286
pub(crate) async fn check_http_status(response: Response) -> Result<Response, BuilderError> {
47✔
287
    if response.status().is_success() {
16✔
288
        return Ok(response);
2✔
289
    }
290
    let url = response.url().clone();
14✔
291
    let status = response.status();
14✔
292
    let text = response.text().await?;
15✔
293
    Err(OssService::new2(text, &status, url).into())
14✔
294
}
32✔
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