1use futures_util::TryFutureExt;
2use reqwest::header::{HeaderValue, CONTENT_TYPE};
3use reqwest::multipart::Form;
4use reqwest::StatusCode;
5use serde::de::DeserializeOwned;
6use serde::ser::Serialize;
7use tracing::{debug, level_enabled, trace};
8use url::Url;
9
10use crate::auth::Token;
11use crate::error::{self, Result};
12use crate::routing::{Parts, Route};
13use crate::types::ErrorResponse;
14use crate::Modio;
15
16#[allow(dead_code)]
17mod headers {
18 const X_MODIO_ERROR_REF: &str = "x-modio-error-ref";
19 const X_MODIO_REQUEST_ID: &str = "x-modio-request-id";
20
21 use http::header::{HeaderMap, RETRY_AFTER};
22
23 pub fn retry_after(headers: &HeaderMap) -> Option<u64> {
24 headers
25 .get(RETRY_AFTER)
26 .and_then(|v| v.to_str().ok())
27 .and_then(|v| v.parse().ok())
28 }
29}
30
31pub struct RequestBuilder {
32 modio: Modio,
33 request: Result<reqwest::RequestBuilder>,
34}
35
36impl RequestBuilder {
37 pub fn new(modio: Modio, route: Route) -> Self {
38 let Parts {
39 method,
40 path,
41 token_required,
42 } = route.into_parts();
43
44 if let (true, None) = (token_required, &modio.inner.credentials.token) {
45 return Self {
46 modio,
47 request: Err(error::token_required()),
48 };
49 }
50
51 let url = format!("{}{}", modio.inner.host, path);
52 let params = [("api_key", &modio.inner.credentials.api_key)];
53 let request = Url::parse_with_params(&url, ¶ms)
54 .map(|url| {
55 let mut req = modio.inner.client.request(method, url);
56
57 if let (true, Some(Token { value, .. })) =
58 (token_required, &modio.inner.credentials.token)
59 {
60 req = req.bearer_auth(value);
61 }
62 req
63 })
64 .map_err(error::builder);
65
66 Self { modio, request }
67 }
68
69 pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self {
70 Self {
71 request: self.request.map(|r| r.query(query)),
72 ..self
73 }
74 }
75
76 pub fn form<T: Serialize + ?Sized>(self, form: &T) -> Self {
77 Self {
78 request: self.request.map(|r| r.form(form)),
79 ..self
80 }
81 }
82
83 pub fn multipart(self, form: Form) -> Self {
84 Self {
85 request: self.request.map(|r| r.multipart(form)),
86 ..self
87 }
88 }
89
90 pub async fn send<Out>(self) -> Result<Out>
91 where
92 Out: DeserializeOwned + Send,
93 {
94 let mut req = self.request?.build().map_err(error::builder)?;
95 if !req.headers().contains_key(CONTENT_TYPE) {
96 req.headers_mut().insert(
97 CONTENT_TYPE,
98 HeaderValue::from_static("application/x-www-form-urlencoded"),
99 );
100 }
101
102 debug!("request: {} {}", req.method(), req.url());
103 let response = self
104 .modio
105 .inner
106 .client
107 .execute(req)
108 .map_err(error::request)
109 .await?;
110
111 let status = response.status();
112
113 let retry_after = if status.is_success() {
114 None
115 } else {
116 headers::retry_after(response.headers())
117 };
118
119 trace!("response headers: {:?}", response.headers());
120
121 let body = response.bytes().map_err(error::request).await?;
122
123 if level_enabled!(tracing::Level::TRACE) {
124 match std::str::from_utf8(&body) {
125 Ok(s) => trace!("status: {}, response: {}", status, s),
126 Err(_) => trace!("status: {}, response: {:?}", status, body),
127 }
128 }
129
130 if status == StatusCode::NO_CONTENT {
131 serde_json::from_str("null").map_err(error::decode)
132 } else if status.is_success() {
133 serde_json::from_slice(&body).map_err(error::decode)
134 } else if let Some(retry_after) = retry_after {
135 debug!("ratelimit reached: retry after {retry_after} seconds");
136 Err(error::ratelimit(retry_after))
137 } else {
138 serde_json::from_slice::<ErrorResponse>(&body)
139 .map(|mer| Err(error::error_for_status(status, mer.error)))
140 .map_err(error::decode)?
141 }
142 }
143}