convex-rust 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +137 -2
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +200 -0
- package/dist/cli/build.d.ts +146 -0
- package/dist/cli/build.js +902 -0
- package/dist/cli/demangle.d.ts +7 -0
- package/dist/cli/demangle.js +328 -0
- package/dist/cli/diagnostics.d.ts +34 -0
- package/dist/cli/diagnostics.js +296 -0
- package/dist/cli/generate.d.ts +29 -0
- package/dist/cli/generate.js +82 -0
- package/dist/cli/manifest.d.ts +24 -0
- package/dist/cli/manifest.js +75 -0
- package/dist/cli/rustApi.d.ts +78 -0
- package/dist/cli/rustApi.js +609 -0
- package/dist/cli/scaffold.d.ts +22 -0
- package/dist/cli/scaffold.js +114 -0
- package/dist/cli/shim.d.ts +30 -0
- package/dist/cli/shim.js +140 -0
- package/dist/cli/size.d.ts +17 -0
- package/dist/cli/size.js +73 -0
- package/dist/cli/split.d.ts +20 -0
- package/dist/cli/split.js +140 -0
- package/dist/cli/symbols.d.ts +30 -0
- package/dist/cli/symbols.js +133 -0
- package/dist/cli/toolchain.d.ts +53 -0
- package/dist/cli/toolchain.js +167 -0
- package/dist/cli/validators.d.ts +6 -0
- package/dist/cli/validators.js +66 -0
- package/dist/cli/wasisdk.d.ts +82 -0
- package/dist/cli/wasisdk.js +220 -0
- package/dist/cli/wasmHints.d.ts +29 -0
- package/dist/cli/wasmHints.js +172 -0
- package/dist/runtime/base64.d.ts +4 -0
- package/dist/runtime/base64.js +77 -0
- package/dist/runtime/crypto.d.ts +7 -0
- package/dist/runtime/crypto.js +421 -0
- package/dist/runtime/fastjson.d.ts +3 -0
- package/dist/runtime/fastjson.js +170 -0
- package/dist/runtime/host.d.ts +73 -0
- package/dist/runtime/host.js +1230 -0
- package/dist/runtime/http.d.ts +40 -0
- package/dist/runtime/http.js +157 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/intl.d.ts +5 -0
- package/dist/runtime/intl.js +69 -0
- package/dist/runtime/tmpfs.d.ts +129 -0
- package/dist/runtime/tmpfs.js +624 -0
- package/dist/runtime/wasi.d.ts +33 -0
- package/dist/runtime/wasi.js +216 -0
- package/package.json +66 -4
- package/rust/convex_fn/Cargo.toml +32 -0
- package/rust/convex_fn/LICENSE +201 -0
- package/rust/convex_fn/build.rs +3 -0
- package/rust/convex_fn/src/args.rs +416 -0
- package/rust/convex_fn/src/crypto.rs +237 -0
- package/rust/convex_fn/src/ctx.rs +1044 -0
- package/rust/convex_fn/src/dns.rs +356 -0
- package/rust/convex_fn/src/error.rs +179 -0
- package/rust/convex_fn/src/executor.rs +577 -0
- package/rust/convex_fn/src/fanout.rs +106 -0
- package/rust/convex_fn/src/fs.rs +19 -0
- package/rust/convex_fn/src/function_ref.rs +219 -0
- package/rust/convex_fn/src/http.rs +441 -0
- package/rust/convex_fn/src/intl.rs +75 -0
- package/rust/convex_fn/src/lib.rs +242 -0
- package/rust/convex_fn/src/logging.rs +222 -0
- package/rust/convex_fn/src/native.rs +360 -0
- package/rust/convex_fn/src/net.rs +122 -0
- package/rust/convex_fn/src/sys.rs +178 -0
- package/rust/convex_fn/src/values.rs +121 -0
- package/rust/convex_fn/src/wire.rs +579 -0
- package/rust/convex_fn_macros/Cargo.toml +13 -0
- package/rust/convex_fn_macros/LICENSE +201 -0
- package/rust/convex_fn_macros/src/derive.rs +632 -0
- package/rust/convex_fn_macros/src/lib.rs +338 -0
- package/rust/convex_reqwest/Cargo.toml +69 -0
- package/rust/convex_reqwest/LICENSE +201 -0
- package/rust/convex_reqwest/src/blocking.rs +287 -0
- package/rust/convex_reqwest/src/body.rs +152 -0
- package/rust/convex_reqwest/src/client.rs +359 -0
- package/rust/convex_reqwest/src/config_types.rs +150 -0
- package/rust/convex_reqwest/src/error.rs +241 -0
- package/rust/convex_reqwest/src/into_url.rs +57 -0
- package/rust/convex_reqwest/src/lib.rs +42 -0
- package/rust/convex_reqwest/src/multipart.rs +140 -0
- package/rust/convex_reqwest/src/request.rs +358 -0
- package/rust/convex_reqwest/src/response.rs +310 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
use std::{fmt, future::Future, time::Duration};
|
|
2
|
+
|
|
3
|
+
use base64::Engine;
|
|
4
|
+
use http::{
|
|
5
|
+
header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE},
|
|
6
|
+
Method, Version,
|
|
7
|
+
};
|
|
8
|
+
use serde::Serialize;
|
|
9
|
+
use url::Url;
|
|
10
|
+
|
|
11
|
+
use crate::{
|
|
12
|
+
body::Body,
|
|
13
|
+
client::Client,
|
|
14
|
+
error::{Error, Result},
|
|
15
|
+
multipart,
|
|
16
|
+
response::Response,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/// A request which can be executed with `Client::execute()`.
|
|
20
|
+
pub struct Request {
|
|
21
|
+
method: Method,
|
|
22
|
+
url: Url,
|
|
23
|
+
headers: HeaderMap,
|
|
24
|
+
body: Option<Body>,
|
|
25
|
+
timeout: Option<Duration>,
|
|
26
|
+
version: Version,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl Request {
|
|
30
|
+
/// Constructs a new request.
|
|
31
|
+
pub fn new(method: Method, url: Url) -> Self {
|
|
32
|
+
Request {
|
|
33
|
+
method,
|
|
34
|
+
url,
|
|
35
|
+
headers: HeaderMap::new(),
|
|
36
|
+
body: None,
|
|
37
|
+
timeout: None,
|
|
38
|
+
version: Version::default(),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn method(&self) -> &Method {
|
|
43
|
+
&self.method
|
|
44
|
+
}
|
|
45
|
+
pub fn method_mut(&mut self) -> &mut Method {
|
|
46
|
+
&mut self.method
|
|
47
|
+
}
|
|
48
|
+
pub fn url(&self) -> &Url {
|
|
49
|
+
&self.url
|
|
50
|
+
}
|
|
51
|
+
pub fn url_mut(&mut self) -> &mut Url {
|
|
52
|
+
&mut self.url
|
|
53
|
+
}
|
|
54
|
+
pub fn headers(&self) -> &HeaderMap {
|
|
55
|
+
&self.headers
|
|
56
|
+
}
|
|
57
|
+
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
58
|
+
&mut self.headers
|
|
59
|
+
}
|
|
60
|
+
pub fn body(&self) -> Option<&Body> {
|
|
61
|
+
self.body.as_ref()
|
|
62
|
+
}
|
|
63
|
+
pub fn body_mut(&mut self) -> &mut Option<Body> {
|
|
64
|
+
&mut self.body
|
|
65
|
+
}
|
|
66
|
+
pub fn timeout(&self) -> Option<&Duration> {
|
|
67
|
+
self.timeout.as_ref()
|
|
68
|
+
}
|
|
69
|
+
pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
|
|
70
|
+
&mut self.timeout
|
|
71
|
+
}
|
|
72
|
+
pub fn version(&self) -> Version {
|
|
73
|
+
self.version
|
|
74
|
+
}
|
|
75
|
+
pub fn version_mut(&mut self) -> &mut Version {
|
|
76
|
+
&mut self.version
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Attempts to clone the request; `None` if the body is a stream.
|
|
80
|
+
pub fn try_clone(&self) -> Option<Request> {
|
|
81
|
+
let body = match &self.body {
|
|
82
|
+
Some(b) => Some(b.try_clone()?),
|
|
83
|
+
None => None,
|
|
84
|
+
};
|
|
85
|
+
Some(Request {
|
|
86
|
+
method: self.method.clone(),
|
|
87
|
+
url: self.url.clone(),
|
|
88
|
+
headers: self.headers.clone(),
|
|
89
|
+
body,
|
|
90
|
+
timeout: self.timeout,
|
|
91
|
+
version: self.version,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub(crate) fn into_parts(self) -> (Method, Url, HeaderMap, Option<Body>, Option<Duration>) {
|
|
96
|
+
(self.method, self.url, self.headers, self.body, self.timeout)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl fmt::Debug for Request {
|
|
101
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
102
|
+
f.debug_struct("Request")
|
|
103
|
+
.field("method", &self.method)
|
|
104
|
+
.field("url", &self.url.as_str())
|
|
105
|
+
.field("headers", &self.headers)
|
|
106
|
+
.finish()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
impl<T: Into<Body>> TryFrom<http::Request<T>> for Request {
|
|
111
|
+
type Error = Error;
|
|
112
|
+
|
|
113
|
+
fn try_from(req: http::Request<T>) -> Result<Self> {
|
|
114
|
+
let (parts, body) = req.into_parts();
|
|
115
|
+
let url = Url::parse(&parts.uri.to_string()).map_err(Error::builder)?;
|
|
116
|
+
let mut r = Request::new(parts.method, url);
|
|
117
|
+
r.headers = parts.headers;
|
|
118
|
+
r.version = parts.version;
|
|
119
|
+
r.body = Some(body.into());
|
|
120
|
+
Ok(r)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl From<Request> for http::Request<Body> {
|
|
125
|
+
fn from(req: Request) -> Self {
|
|
126
|
+
let mut r = http::Request::new(req.body.unwrap_or_default());
|
|
127
|
+
*r.method_mut() = req.method;
|
|
128
|
+
*r.uri_mut() = req.url.as_str().parse().expect("url is a valid uri");
|
|
129
|
+
*r.headers_mut() = req.headers;
|
|
130
|
+
*r.version_mut() = req.version;
|
|
131
|
+
r
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// A builder to construct the properties of a `Request`.
|
|
136
|
+
#[must_use = "RequestBuilder does nothing until you 'send' it"]
|
|
137
|
+
pub struct RequestBuilder {
|
|
138
|
+
client: Client,
|
|
139
|
+
request: Result<Request>,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
impl RequestBuilder {
|
|
143
|
+
pub(crate) fn new(client: Client, request: Result<Request>) -> RequestBuilder {
|
|
144
|
+
RequestBuilder { client, request }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Assemble a builder starting from an existing `Client` and a `Request`.
|
|
148
|
+
pub fn from_parts(client: Client, request: Request) -> RequestBuilder {
|
|
149
|
+
RequestBuilder {
|
|
150
|
+
client,
|
|
151
|
+
request: Ok(request),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Add a `Header` to this Request.
|
|
156
|
+
pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
|
|
157
|
+
where
|
|
158
|
+
HeaderName: TryFrom<K>,
|
|
159
|
+
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
|
160
|
+
HeaderValue: TryFrom<V>,
|
|
161
|
+
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
|
162
|
+
{
|
|
163
|
+
self.header_sensitive(key, value, false)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
|
|
167
|
+
where
|
|
168
|
+
HeaderName: TryFrom<K>,
|
|
169
|
+
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
|
170
|
+
HeaderValue: TryFrom<V>,
|
|
171
|
+
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
|
172
|
+
{
|
|
173
|
+
let mut error = None;
|
|
174
|
+
if let Ok(req) = &mut self.request {
|
|
175
|
+
match <HeaderName as TryFrom<K>>::try_from(key) {
|
|
176
|
+
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
|
|
177
|
+
Ok(mut value) => {
|
|
178
|
+
value.set_sensitive(sensitive);
|
|
179
|
+
req.headers_mut().append(key, value);
|
|
180
|
+
}
|
|
181
|
+
Err(e) => error = Some(Error::builder(e.into())),
|
|
182
|
+
},
|
|
183
|
+
Err(e) => error = Some(Error::builder(e.into())),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if let Some(e) = error {
|
|
187
|
+
self.request = Err(e);
|
|
188
|
+
}
|
|
189
|
+
self
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Add a set of Headers to the existing ones on this Request.
|
|
193
|
+
pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder {
|
|
194
|
+
if let Ok(req) = &mut self.request {
|
|
195
|
+
for (k, v) in headers.iter() {
|
|
196
|
+
req.headers_mut().insert(k.clone(), v.clone());
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
self
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Enable HTTP basic authentication.
|
|
203
|
+
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
|
|
204
|
+
where
|
|
205
|
+
U: fmt::Display,
|
|
206
|
+
P: fmt::Display,
|
|
207
|
+
{
|
|
208
|
+
let creds = match password {
|
|
209
|
+
Some(p) => format!("{username}:{p}"),
|
|
210
|
+
None => format!("{username}:"),
|
|
211
|
+
};
|
|
212
|
+
let value = format!(
|
|
213
|
+
"Basic {}",
|
|
214
|
+
base64::engine::general_purpose::STANDARD.encode(creds)
|
|
215
|
+
);
|
|
216
|
+
self.header_sensitive(AUTHORIZATION, value, true)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Enable HTTP bearer authentication.
|
|
220
|
+
pub fn bearer_auth<T: fmt::Display>(self, token: T) -> RequestBuilder {
|
|
221
|
+
self.header_sensitive(AUTHORIZATION, format!("Bearer {token}"), true)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Set the request body.
|
|
225
|
+
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
|
|
226
|
+
if let Ok(req) = &mut self.request {
|
|
227
|
+
*req.body_mut() = Some(body.into());
|
|
228
|
+
}
|
|
229
|
+
self
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Enables a request timeout (covers the response body too).
|
|
233
|
+
pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
|
|
234
|
+
if let Ok(req) = &mut self.request {
|
|
235
|
+
*req.timeout_mut() = Some(timeout);
|
|
236
|
+
}
|
|
237
|
+
self
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Sends a multipart/form-data body.
|
|
241
|
+
pub fn multipart(self, form: multipart::Form) -> RequestBuilder {
|
|
242
|
+
let ct = format!("multipart/form-data; boundary={}", form.boundary());
|
|
243
|
+
let mut b = self.header(CONTENT_TYPE, ct);
|
|
244
|
+
if let Ok(req) = &mut b.request {
|
|
245
|
+
*req.body_mut() = Some(Body::from(form.into_bytes()));
|
|
246
|
+
}
|
|
247
|
+
b
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// Modify the query string of the URL.
|
|
251
|
+
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
|
|
252
|
+
let mut error = None;
|
|
253
|
+
if let Ok(req) = &mut self.request {
|
|
254
|
+
let url = req.url_mut();
|
|
255
|
+
let mut pairs = url.query_pairs_mut();
|
|
256
|
+
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
|
|
257
|
+
if let Err(err) = query.serialize(serializer) {
|
|
258
|
+
error = Some(Error::builder(err));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if let Ok(req) = &mut self.request {
|
|
262
|
+
if let Some("") = req.url().query() {
|
|
263
|
+
req.url_mut().set_query(None);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if let Some(e) = error {
|
|
267
|
+
self.request = Err(e);
|
|
268
|
+
}
|
|
269
|
+
self
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// Set HTTP version (accepted, ignored: the host decides).
|
|
273
|
+
pub fn version(mut self, version: Version) -> RequestBuilder {
|
|
274
|
+
if let Ok(req) = &mut self.request {
|
|
275
|
+
*req.version_mut() = version;
|
|
276
|
+
}
|
|
277
|
+
self
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// Send a form body (`application/x-www-form-urlencoded`).
|
|
281
|
+
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
|
|
282
|
+
let mut error = None;
|
|
283
|
+
if let Ok(req) = &mut self.request {
|
|
284
|
+
match serde_urlencoded::to_string(form) {
|
|
285
|
+
Ok(body) => {
|
|
286
|
+
req.headers_mut().insert(
|
|
287
|
+
CONTENT_TYPE,
|
|
288
|
+
HeaderValue::from_static("application/x-www-form-urlencoded"),
|
|
289
|
+
);
|
|
290
|
+
*req.body_mut() = Some(body.into());
|
|
291
|
+
}
|
|
292
|
+
Err(err) => error = Some(Error::builder(err)),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if let Some(e) = error {
|
|
296
|
+
self.request = Err(e);
|
|
297
|
+
}
|
|
298
|
+
self
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Send a JSON body.
|
|
302
|
+
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
|
|
303
|
+
let mut error = None;
|
|
304
|
+
if let Ok(req) = &mut self.request {
|
|
305
|
+
match serde_json::to_vec(json) {
|
|
306
|
+
Ok(body) => {
|
|
307
|
+
if !req.headers().contains_key(CONTENT_TYPE) {
|
|
308
|
+
req.headers_mut()
|
|
309
|
+
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
310
|
+
}
|
|
311
|
+
*req.body_mut() = Some(body.into());
|
|
312
|
+
}
|
|
313
|
+
Err(err) => error = Some(Error::builder(err)),
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if let Some(e) = error {
|
|
317
|
+
self.request = Err(e);
|
|
318
|
+
}
|
|
319
|
+
self
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// Build a `Request`, which can be inspected, modified and executed.
|
|
323
|
+
pub fn build(self) -> Result<Request> {
|
|
324
|
+
self.request
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// Build a `Request`, returning the `Client` as well.
|
|
328
|
+
pub fn build_split(self) -> (Client, Result<Request>) {
|
|
329
|
+
(self.client, self.request)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/// Constructs the Request and sends it to the target URL.
|
|
333
|
+
pub fn send(self) -> impl Future<Output = Result<Response>> {
|
|
334
|
+
let client = self.client;
|
|
335
|
+
let request = self.request;
|
|
336
|
+
async move { client.execute(request?).await }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// Attempt to clone the RequestBuilder; `None` for a streaming body.
|
|
340
|
+
pub fn try_clone(&self) -> Option<RequestBuilder> {
|
|
341
|
+
let req = self.request.as_ref().ok()?.try_clone()?;
|
|
342
|
+
Some(RequestBuilder {
|
|
343
|
+
client: self.client.clone(),
|
|
344
|
+
request: Ok(req),
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
impl fmt::Debug for RequestBuilder {
|
|
350
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
351
|
+
let mut d = f.debug_struct("RequestBuilder");
|
|
352
|
+
match &self.request {
|
|
353
|
+
Ok(r) => d.field("request", r),
|
|
354
|
+
Err(e) => d.field("error", e),
|
|
355
|
+
};
|
|
356
|
+
d.finish()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
fmt,
|
|
3
|
+
future::Future,
|
|
4
|
+
pin::Pin,
|
|
5
|
+
task::{Context, Poll},
|
|
6
|
+
time::{Duration, Instant},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
use bytes::Bytes;
|
|
10
|
+
use convex_fn::{select, Ctx, Either, FetchStream};
|
|
11
|
+
use futures_core::Stream;
|
|
12
|
+
use http::{header::CONTENT_LENGTH, HeaderMap, StatusCode, Version};
|
|
13
|
+
use serde::de::DeserializeOwned;
|
|
14
|
+
|
|
15
|
+
use url::Url;
|
|
16
|
+
|
|
17
|
+
use crate::{
|
|
18
|
+
body::Body,
|
|
19
|
+
error::{Error, Kind, Result},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/// A Response to a submitted `Request`.
|
|
23
|
+
pub struct Response {
|
|
24
|
+
status: StatusCode,
|
|
25
|
+
headers: HeaderMap,
|
|
26
|
+
url: Box<Url>,
|
|
27
|
+
version: Version,
|
|
28
|
+
extensions: http::Extensions,
|
|
29
|
+
body: RespBody,
|
|
30
|
+
deadline: Option<Instant>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enum RespBody {
|
|
34
|
+
Stream(FetchStream),
|
|
35
|
+
Bytes(Option<Bytes>),
|
|
36
|
+
/// Taken by `bytes_stream`.
|
|
37
|
+
Gone,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Response {
|
|
41
|
+
pub(crate) fn from_fetch(
|
|
42
|
+
stream: FetchStream,
|
|
43
|
+
status: StatusCode,
|
|
44
|
+
headers: HeaderMap,
|
|
45
|
+
url: Url,
|
|
46
|
+
timeout: Option<Duration>,
|
|
47
|
+
) -> Response {
|
|
48
|
+
Response {
|
|
49
|
+
status,
|
|
50
|
+
headers,
|
|
51
|
+
url: Box::new(url),
|
|
52
|
+
version: Version::HTTP_11,
|
|
53
|
+
extensions: http::Extensions::new(),
|
|
54
|
+
body: RespBody::Stream(stream),
|
|
55
|
+
deadline: timeout.map(|t| Instant::now() + t),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Get the `StatusCode` of this `Response`.
|
|
60
|
+
pub fn status(&self) -> StatusCode {
|
|
61
|
+
self.status
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Get the HTTP `Version` of this `Response`.
|
|
65
|
+
pub fn version(&self) -> Version {
|
|
66
|
+
self.version
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Get the `Headers` of this `Response`.
|
|
70
|
+
pub fn headers(&self) -> &HeaderMap {
|
|
71
|
+
&self.headers
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Get a mutable reference to the `Headers` of this `Response`.
|
|
75
|
+
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
|
76
|
+
&mut self.headers
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Get the content length of the response, if the header says.
|
|
80
|
+
pub fn content_length(&self) -> Option<u64> {
|
|
81
|
+
self.headers
|
|
82
|
+
.get(CONTENT_LENGTH)?
|
|
83
|
+
.to_str()
|
|
84
|
+
.ok()?
|
|
85
|
+
.parse()
|
|
86
|
+
.ok()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Get the final `Url` of this `Response`.
|
|
90
|
+
pub fn url(&self) -> &Url {
|
|
91
|
+
&self.url
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Get the remote address (never known: the host owns the socket).
|
|
95
|
+
pub fn remote_addr(&self) -> Option<std::net::SocketAddr> {
|
|
96
|
+
None
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Returns a reference to the associated extensions.
|
|
100
|
+
pub fn extensions(&self) -> &http::Extensions {
|
|
101
|
+
&self.extensions
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Returns a mutable reference to the associated extensions.
|
|
105
|
+
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
|
|
106
|
+
&mut self.extensions
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Get the full response text (lossy UTF-8).
|
|
110
|
+
pub async fn text(self) -> Result<String> {
|
|
111
|
+
self.text_with_charset("utf-8").await
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Get the full response text; only UTF-8 (lossy) is decoded, other
|
|
115
|
+
/// charsets are read as UTF-8 too.
|
|
116
|
+
pub async fn text_with_charset(self, _default_encoding: &str) -> Result<String> {
|
|
117
|
+
let bytes = self.bytes().await?;
|
|
118
|
+
Ok(String::from_utf8_lossy(&bytes).into_owned())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Try to deserialize the response body as JSON.
|
|
122
|
+
pub async fn json<T: DeserializeOwned>(self) -> Result<T> {
|
|
123
|
+
let url = (*self.url).clone();
|
|
124
|
+
let full = self.bytes().await?;
|
|
125
|
+
serde_json::from_slice(&full).map_err(|e| Error::decode(e).with_url(url))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Get the full response body as `Bytes`.
|
|
129
|
+
pub async fn bytes(mut self) -> Result<Bytes> {
|
|
130
|
+
let mut out: Vec<u8> = Vec::new();
|
|
131
|
+
let mut first: Option<Bytes> = None;
|
|
132
|
+
while let Some(c) = self.chunk().await? {
|
|
133
|
+
if first.is_none() && out.is_empty() {
|
|
134
|
+
first = Some(c);
|
|
135
|
+
} else {
|
|
136
|
+
if let Some(f) = first.take() {
|
|
137
|
+
out.extend_from_slice(&f);
|
|
138
|
+
}
|
|
139
|
+
out.extend_from_slice(&c);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
Ok(match first {
|
|
143
|
+
Some(f) => f,
|
|
144
|
+
None => Bytes::from(out),
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Stream a chunk of the response body; `None` when the body ended.
|
|
149
|
+
pub async fn chunk(&mut self) -> Result<Option<Bytes>> {
|
|
150
|
+
let deadline = self.deadline;
|
|
151
|
+
let url = (*self.url).clone();
|
|
152
|
+
match &mut self.body {
|
|
153
|
+
RespBody::Gone => Ok(None),
|
|
154
|
+
RespBody::Bytes(b) => Ok(b.take().filter(|b| !b.is_empty())),
|
|
155
|
+
RespBody::Stream(s) => {
|
|
156
|
+
let r = match deadline {
|
|
157
|
+
Some(d) => {
|
|
158
|
+
let left = d.saturating_duration_since(Instant::now());
|
|
159
|
+
match select(s.chunk(), Ctx::default().sleep(crate::client::millis(left))).await {
|
|
160
|
+
Either::Left(r) => r,
|
|
161
|
+
Either::Right(_) => return Err(Error::timeout(Kind::Body).with_url(url)),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
None => s.chunk().await,
|
|
165
|
+
};
|
|
166
|
+
match r {
|
|
167
|
+
Ok(c) => Ok(c.map(Bytes::from)),
|
|
168
|
+
Err(e) => Err(Error::body(e.to_string()).with_url(url)),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Convert the response into a `Stream` of `Bytes` from the body.
|
|
175
|
+
pub fn bytes_stream(mut self) -> impl Stream<Item = Result<Bytes>> {
|
|
176
|
+
let body = std::mem::replace(&mut self.body, RespBody::Gone);
|
|
177
|
+
BytesStream {
|
|
178
|
+
resp: Some(Box::new(Response { body, ..self })),
|
|
179
|
+
fut: None,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Turn a response into an error if the server returned an error.
|
|
184
|
+
pub fn error_for_status(self) -> Result<Self> {
|
|
185
|
+
let status = self.status;
|
|
186
|
+
if status.is_client_error() || status.is_server_error() {
|
|
187
|
+
Err(Error::status_code(*self.url, status))
|
|
188
|
+
} else {
|
|
189
|
+
Ok(self)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Turn a reference to a response into an error if the server returned an error.
|
|
194
|
+
pub fn error_for_status_ref(&self) -> Result<&Self> {
|
|
195
|
+
let status = self.status;
|
|
196
|
+
if status.is_client_error() || status.is_server_error() {
|
|
197
|
+
Err(Error::status_code((*self.url).clone(), status))
|
|
198
|
+
} else {
|
|
199
|
+
Ok(self)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
type ChunkFut = Pin<Box<dyn Future<Output = (Box<Response>, Result<Option<Bytes>>)> + Send>>;
|
|
205
|
+
|
|
206
|
+
struct BytesStream {
|
|
207
|
+
resp: Option<Box<Response>>,
|
|
208
|
+
fut: Option<ChunkFut>,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
impl Stream for BytesStream {
|
|
212
|
+
type Item = Result<Bytes>;
|
|
213
|
+
|
|
214
|
+
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
215
|
+
if self.fut.is_none() {
|
|
216
|
+
let Some(mut resp) = self.resp.take() else {
|
|
217
|
+
return Poll::Ready(None);
|
|
218
|
+
};
|
|
219
|
+
self.fut = Some(Box::pin(async move {
|
|
220
|
+
let r = resp.chunk().await;
|
|
221
|
+
(resp, r)
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
let fut = self.fut.as_mut().expect("set above");
|
|
225
|
+
match fut.as_mut().poll(cx) {
|
|
226
|
+
Poll::Pending => Poll::Pending,
|
|
227
|
+
Poll::Ready((resp, r)) => {
|
|
228
|
+
self.fut = None;
|
|
229
|
+
match r {
|
|
230
|
+
Ok(Some(c)) => {
|
|
231
|
+
self.resp = Some(resp);
|
|
232
|
+
Poll::Ready(Some(Ok(c)))
|
|
233
|
+
}
|
|
234
|
+
Ok(None) => Poll::Ready(None),
|
|
235
|
+
Err(e) => Poll::Ready(Some(Err(e))),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
impl fmt::Debug for Response {
|
|
243
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
244
|
+
f.debug_struct("Response")
|
|
245
|
+
.field("url", &self.url.as_str())
|
|
246
|
+
.field("status", &self.status)
|
|
247
|
+
.field("headers", &self.headers)
|
|
248
|
+
.finish()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
impl<T: Into<Body>> From<http::Response<T>> for Response {
|
|
253
|
+
fn from(r: http::Response<T>) -> Response {
|
|
254
|
+
let (mut parts, body) = r.into_parts();
|
|
255
|
+
let body: Body = body.into();
|
|
256
|
+
let bytes = match body.as_bytes() {
|
|
257
|
+
Some(b) => Bytes::copy_from_slice(b),
|
|
258
|
+
None => Bytes::new(),
|
|
259
|
+
};
|
|
260
|
+
let url = parts
|
|
261
|
+
.extensions
|
|
262
|
+
.remove::<Url>()
|
|
263
|
+
.unwrap_or_else(|| Url::parse("http://no.url.provided.local").expect("static url"));
|
|
264
|
+
Response {
|
|
265
|
+
status: parts.status,
|
|
266
|
+
headers: parts.headers,
|
|
267
|
+
url: Box::new(url),
|
|
268
|
+
version: parts.version,
|
|
269
|
+
extensions: parts.extensions,
|
|
270
|
+
body: RespBody::Bytes(Some(bytes)),
|
|
271
|
+
deadline: None,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
impl From<Response> for http::Response<Body> {
|
|
277
|
+
fn from(r: Response) -> http::Response<Body> {
|
|
278
|
+
let (status, headers, url, version, extensions) = (
|
|
279
|
+
r.status,
|
|
280
|
+
r.headers.clone(),
|
|
281
|
+
(*r.url).clone(),
|
|
282
|
+
r.version,
|
|
283
|
+
r.extensions.clone(),
|
|
284
|
+
);
|
|
285
|
+
let body = match &r.body {
|
|
286
|
+
RespBody::Bytes(Some(b)) => Body::from(b.clone()),
|
|
287
|
+
_ => Body::wrap_stream(r.bytes_stream()),
|
|
288
|
+
};
|
|
289
|
+
let mut out = http::Response::new(body);
|
|
290
|
+
*out.status_mut() = status;
|
|
291
|
+
*out.headers_mut() = headers;
|
|
292
|
+
*out.version_mut() = version;
|
|
293
|
+
*out.extensions_mut() = extensions;
|
|
294
|
+
out.extensions_mut().insert(url);
|
|
295
|
+
out
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Extension trait for `http::response::Builder` objects: records the final
|
|
300
|
+
/// URL so `Response::from(http::Response)` keeps it.
|
|
301
|
+
pub trait ResponseBuilderExt {
|
|
302
|
+
/// Adds a `Url` to the `http::Response` being built.
|
|
303
|
+
fn url(self, url: Url) -> Self;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
impl ResponseBuilderExt for http::response::Builder {
|
|
307
|
+
fn url(self, url: Url) -> Self {
|
|
308
|
+
self.extension(url)
|
|
309
|
+
}
|
|
310
|
+
}
|