asknothingx2_util/api/request/
mod.rs1mod body;
2mod error;
3
4#[cfg(feature = "stream")]
5pub use body::CodecType;
6
7pub use body::RequestBody;
8pub use error::{
9 FileOperation, HeaderError, LimitType, NetworkOperation, ProcessOperation, StreamError,
10 StreamOperation,
11};
12
13use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, CONTROLS};
14
15use std::str::FromStr;
16
17use http::{header::CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, Method};
18use reqwest::{Client, Request, RequestBuilder, Response};
19use url::Url;
20
21use super::{
22 content_type::{Application, Text},
23 setup::get_global_client_or_default,
24 HeaderMut,
25};
26
27const HEADER_ENCODE_SET: &AsciiSet = &CONTROLS
30 .add(b' ')
31 .add(b'"')
32 .add(b'\\')
33 .add(b'\t')
34 .add(b'\r')
35 .add(b'\n');
36
37const HEADER_SAFE_ENCODE_SET: &AsciiSet = &HEADER_ENCODE_SET
39 .add(b'(')
40 .add(b')')
41 .add(b'<')
42 .add(b'>')
43 .add(b'@')
44 .add(b',')
45 .add(b';')
46 .add(b':')
47 .add(b'/')
48 .add(b'[')
49 .add(b']')
50 .add(b'?')
51 .add(b'=')
52 .add(b'{')
53 .add(b'}');
54
55const RFC8187_ENCODE_SET: &AsciiSet = CONTROLS;
58
59const RFC8187_SAFE_ENCODE_SET: &AsciiSet = &CONTROLS
62 .add(b' ')
63 .add(b'"')
64 .add(b'%')
65 .add(b'*')
66 .add(b'/')
67 .add(b'\\')
68 .add(b'?')
69 .add(b'<')
70 .add(b'>')
71 .add(b'|');
72
73pub trait IntoRequestParts {
74 fn into_request_parts(self) -> RequestParts;
75}
76
77#[derive(Debug)]
78pub struct RequestParts {
79 pub method: Method,
80 pub url: Url,
81 pub headers: HeaderMap,
82 pub body: Option<RequestBody>,
83 pub version: Option<http::Version>,
84 pub timeout: Option<std::time::Duration>,
85 pub request_id: Option<String>,
86}
87
88impl RequestParts {
89 pub fn new(method: Method, url: Url) -> Self {
90 Self {
91 method,
92 url,
93 headers: HeaderMap::new(),
94 body: None,
95 version: None,
96 timeout: None,
97 request_id: None,
98 }
99 }
100
101 pub fn header(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
102 if let (Ok(name), Ok(val)) = (
103 HeaderName::from_str(key.as_ref()),
104 HeaderValue::from_str(value.as_ref()),
105 ) {
106 self.headers.insert(name, val);
107 }
108 self
109 }
110
111 pub fn try_header(
112 &mut self,
113 key: impl AsRef<str>,
114 value: impl AsRef<str>,
115 ) -> Result<&mut Self, HeaderError> {
116 let key_str = key.as_ref();
117 let value_str = value.as_ref();
118
119 let name = HeaderName::from_str(key_str).map_err(|e| HeaderError::InvalidHeaderName {
120 name: key_str.to_string(),
121 reason: e.to_string(),
122 })?;
123
124 let val =
125 HeaderValue::from_str(value_str).map_err(|e| HeaderError::InvalidHeaderValue {
126 name: key_str.to_string(),
127 value: value_str.to_string(),
128 reason: e.to_string(),
129 })?;
130
131 self.headers.insert(name, val);
132 Ok(self)
133 }
134
135 pub fn headers(&mut self, headers: HeaderMap) -> &mut Self {
268 self.headers.extend(headers);
269 self
270 }
271
272 pub fn header_mut(&mut self) -> HeaderMut<'_> {
273 HeaderMut::new(&mut self.headers)
274 }
275
276 pub fn body(&mut self, body: RequestBody) -> &mut Self {
277 self.body = Some(body);
278 self
279 }
280
281 pub fn text(&mut self, text: impl Into<String>) -> &mut Self {
282 self.body = Some(RequestBody::from_string(text.into()));
283 self.header(CONTENT_TYPE, Text::Plain)
284 }
285
286 pub fn json(&mut self, value: serde_json::Value) -> &mut Self {
287 self.body = Some(RequestBody::from_json(value));
288 self.header(CONTENT_TYPE, Application::Json)
289 }
290
291 pub fn form(&mut self, form: Vec<(String, String)>) -> &mut Self {
292 self.body = Some(RequestBody::from_form(form));
293 self.header(CONTENT_TYPE, Application::FormUrlEncoded)
294 }
295
296 pub fn form_pairs<I, K, V>(&mut self, pairs: I) -> &mut Self
297 where
298 I: IntoIterator<Item = (K, V)>,
299 K: Into<String>,
300 V: Into<String>,
301 {
302 self.body = Some(RequestBody::from_form_pairs(pairs));
303 self.header(CONTENT_TYPE, Application::FormUrlEncoded)
304 }
305
306 pub fn multipart(&mut self, form: reqwest::multipart::Form) -> &mut Self {
307 self.body = Some(RequestBody::from_multipart(form));
308 self
310 }
311
312 pub fn empty(&mut self) -> &mut Self {
313 self.body = Some(RequestBody::empty());
314 self
315 }
316
317 pub fn version(&mut self, version: http::Version) -> &mut Self {
318 self.version = Some(version);
319 self
320 }
321
322 pub fn timeout(&mut self, timeout: std::time::Duration) -> &mut Self {
323 self.timeout = Some(timeout);
324 self
325 }
326
327 pub fn request_id(&mut self, request_id: impl Into<String>) -> &mut Self {
328 self.request_id = Some(request_id.into());
329 self
330 }
331
332 pub fn into_request_builder(self, client: &Client) -> (RequestBuilder, Option<String>) {
333 let mut builder = client.request(self.method, self.url);
334
335 if !self.headers.is_empty() {
336 builder = builder.headers(self.headers);
337 }
338
339 if let Some(version) = self.version {
340 builder = builder.version(version);
341 }
342
343 if let Some(timeout) = self.timeout {
344 builder = builder.timeout(timeout);
345 }
346
347 if let Some(body) = self.body {
348 builder = body.into_reqwest_body(builder);
349 }
350
351 (builder, self.request_id)
352 }
353
354 pub fn into_request(self, client: &Client) -> Result<Request, reqwest::Error> {
355 let (request_builder, _) = self.into_request_builder(client);
356 request_builder.build()
357 }
358
359 pub async fn send(self) -> Result<Response, reqwest::Error> {
360 let (request_builder, _) = self.into_request_builder(get_global_client_or_default());
361 request_builder.send().await
362 }
363
364 #[cfg(feature = "stream")]
365 pub fn from_file(&mut self, file: tokio::fs::File) -> &mut Self {
366 self.body = Some(RequestBody::from_file(file));
367 self
368 }
369
370 #[cfg(feature = "stream")]
371 pub fn from_file_buffered(&mut self, file: tokio::fs::File, buffer_size: usize) -> &mut Self {
372 self.body = Some(RequestBody::from_file_buffered(file, buffer_size));
373 self
374 }
375
376 #[cfg(feature = "stream")]
377 pub async fn from_file_path<P: AsRef<std::path::Path>>(
378 mut self,
379 path: P,
380 ) -> Result<Self, StreamError> {
381 self.body = Some(RequestBody::from_file_path(path).await?);
382 Ok(self)
383 }
384
385 #[cfg(feature = "stream")]
386 pub async fn from_file_path_buffered<P: AsRef<std::path::Path>>(
387 mut self,
388 path: P,
389 buffer_size: usize,
390 ) -> Result<Self, StreamError> {
391 self.body = Some(RequestBody::from_file_path_buffered(path, buffer_size).await?);
392 Ok(self)
393 }
394
395 #[cfg(feature = "stream")]
396 pub fn from_async_read<R>(&mut self, reader: R) -> &mut Self
397 where
398 R: tokio::io::AsyncRead + Send + Sync + 'static,
399 {
400 self.body = Some(RequestBody::from_async_read(reader));
401 self
402 }
403
404 #[cfg(feature = "stream")]
405 pub fn from_tcp_stream(&mut self, tcp: tokio::net::TcpStream) -> &mut Self {
406 self.body = Some(RequestBody::from_tcp_stream(tcp));
407 self
408 }
409
410 #[cfg(feature = "stream")]
411 pub fn from_command_output(
412 mut self,
413 command: tokio::process::Command,
414 ) -> Result<Self, StreamError> {
415 self.body = Some(RequestBody::from_command_output(command)?);
416 Ok(self)
417 }
418
419 #[cfg(feature = "stream")]
420 pub fn stream<S>(&mut self, stream: S) -> &mut Self
421 where
422 S: futures_util::Stream<Item = Result<bytes::Bytes, StreamError>> + Send + Sync + 'static,
423 {
424 self.body = Some(RequestBody::from_stream(stream));
425 self
426 }
427
428 #[cfg(feature = "stream")]
429 pub fn io_stream<S>(&mut self, stream: S) -> &mut Self
430 where
431 S: futures_util::Stream<Item = Result<bytes::Bytes, std::io::Error>>
432 + Send
433 + Sync
434 + 'static,
435 {
436 self.body = Some(RequestBody::from_io_stream(stream));
437 self
438 }
439}
440
441impl IntoRequestParts for RequestParts {
442 fn into_request_parts(self) -> RequestParts {
443 self
444 }
445}
446
447pub fn decode_header_value(encoded: &str) -> Result<String, HeaderError> {
449 percent_decode_str(encoded)
450 .decode_utf8()
451 .map(|cow| cow.into_owned())
452 .map_err(|e| HeaderError::InvalidUtf8 {
453 reason: e.to_string(),
454 })
455}
456
457pub fn encode_header(value: &str) -> String {
459 utf8_percent_encode(value, HEADER_SAFE_ENCODE_SET).to_string()
460}
461
462pub fn encode_rfc8187(value: &str) -> String {
464 if value.is_ascii() {
465 value.to_string()
466 } else {
467 let encoded = utf8_percent_encode(value, RFC8187_ENCODE_SET).to_string();
468 format!("utf-8''{encoded}")
469 }
470}