serenity/http/
request.rs

1use std::fmt::Write;
2
3use reqwest::header::{
4    HeaderMap as Headers,
5    HeaderValue,
6    AUTHORIZATION,
7    CONTENT_LENGTH,
8    CONTENT_TYPE,
9    USER_AGENT,
10};
11use reqwest::{Client, RequestBuilder as ReqwestRequestBuilder, Url};
12use tracing::instrument;
13
14use super::multipart::Multipart;
15use super::routing::Route;
16use super::{HttpError, LightMethod};
17use crate::constants;
18use crate::internal::prelude::*;
19
20#[deprecated = "use Request directly now"]
21pub type RequestBuilder<'a> = Request<'a>;
22
23#[derive(Clone, Debug)]
24#[must_use]
25pub struct Request<'a> {
26    pub(super) body: Option<Vec<u8>>,
27    pub(super) multipart: Option<Multipart>,
28    pub(super) headers: Option<Headers>,
29    pub(super) method: LightMethod,
30    pub(super) route: Route<'a>,
31    pub(super) params: Option<Vec<(&'static str, String)>>,
32}
33
34impl<'a> Request<'a> {
35    pub const fn new(route: Route<'a>, method: LightMethod) -> Self {
36        Self {
37            body: None,
38            multipart: None,
39            headers: None,
40            method,
41            route,
42            params: None,
43        }
44    }
45
46    pub fn body(mut self, body: Option<Vec<u8>>) -> Self {
47        self.body = body;
48        self
49    }
50
51    pub fn multipart(mut self, multipart: Option<Multipart>) -> Self {
52        self.multipart = multipart;
53        self
54    }
55
56    pub fn headers(mut self, headers: Option<Headers>) -> Self {
57        self.headers = headers;
58        self
59    }
60
61    pub fn params(mut self, params: Option<Vec<(&'static str, String)>>) -> Self {
62        self.params = params;
63        self
64    }
65
66    #[instrument(skip(token))]
67    pub fn build(
68        self,
69        client: &Client,
70        token: &str,
71        proxy: Option<&str>,
72    ) -> Result<ReqwestRequestBuilder> {
73        let mut path = self.route.path().to_string();
74
75        if let Some(proxy) = proxy {
76            // trim_end_matches to prevent double slashes after the domain
77            path = path.replace("https://discord.com", proxy.trim_end_matches('/'));
78        }
79
80        if let Some(params) = self.params {
81            path += "?";
82            for (param, value) in params {
83                write!(path, "&{param}={value}").unwrap();
84            }
85        }
86
87        let mut builder = client
88            .request(self.method.reqwest_method(), Url::parse(&path).map_err(HttpError::Url)?);
89
90        let mut headers = self.headers.unwrap_or_default();
91        headers.insert(USER_AGENT, HeaderValue::from_static(constants::USER_AGENT));
92        headers
93            .insert(AUTHORIZATION, HeaderValue::from_str(token).map_err(HttpError::InvalidHeader)?);
94
95        if let Some(multipart) = self.multipart {
96            // Setting multipart adds the content-length header.
97            builder = builder.multipart(multipart.build_form()?);
98        } else if let Some(bytes) = self.body {
99            headers.insert(CONTENT_LENGTH, bytes.len().into());
100            headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
101            builder = builder.body(bytes);
102        } else {
103            headers.insert(CONTENT_LENGTH, 0.into()); // Can we skip this?
104        }
105
106        Ok(builder.headers(headers))
107    }
108
109    #[must_use]
110    pub fn body_ref(&self) -> Option<&[u8]> {
111        self.body.as_deref()
112    }
113
114    #[must_use]
115    pub fn body_mut(&mut self) -> Option<&mut [u8]> {
116        self.body.as_deref_mut()
117    }
118
119    #[must_use]
120    pub fn headers_ref(&self) -> &Option<Headers> {
121        &self.headers
122    }
123
124    #[must_use]
125    pub fn headers_mut(&mut self) -> &mut Option<Headers> {
126        &mut self.headers
127    }
128
129    #[must_use]
130    pub fn method_ref(&self) -> &LightMethod {
131        &self.method
132    }
133
134    #[must_use]
135    pub fn route_ref(&self) -> &Route<'_> {
136        &self.route
137    }
138
139    #[must_use]
140    pub fn params_ref(&self) -> Option<&[(&'static str, String)]> {
141        self.params.as_deref()
142    }
143
144    #[must_use]
145    pub fn params_mut(&mut self) -> Option<&mut [(&'static str, String)]> {
146        self.params.as_deref_mut()
147    }
148}