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