Skip to main content

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    #[allow(clippy::missing_errors_doc)]
67    #[instrument(skip(token))]
68    pub fn build(
69        self,
70        client: &Client,
71        token: &str,
72        proxy: Option<&str>,
73    ) -> Result<ReqwestRequestBuilder> {
74        let mut path = self.route.path().to_string();
75
76        if let Some(proxy) = proxy {
77            // trim_end_matches to prevent double slashes after the domain
78            path = path.replace("https://discord.com", proxy.trim_end_matches('/'));
79        }
80
81        if let Some(params) = self.params {
82            path += "?";
83            for (param, value) in params {
84                write!(path, "&{param}={value}").expect("writing to a string should never fail");
85            }
86        }
87
88        let mut builder = client
89            .request(self.method.reqwest_method(), Url::parse(&path).map_err(HttpError::Url)?);
90
91        let mut headers = self.headers.unwrap_or_default();
92        headers.insert(USER_AGENT, HeaderValue::from_static(constants::USER_AGENT));
93        headers
94            .insert(AUTHORIZATION, HeaderValue::from_str(token).map_err(HttpError::InvalidHeader)?);
95
96        if let Some(multipart) = self.multipart {
97            // Setting multipart adds the content-length header.
98            builder = builder.multipart(multipart.build_form()?);
99        } else if let Some(bytes) = self.body {
100            headers.insert(CONTENT_LENGTH, bytes.len().into());
101            headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
102            builder = builder.body(bytes);
103        } else {
104            headers.insert(CONTENT_LENGTH, 0.into()); // Can we skip this?
105        }
106
107        Ok(builder.headers(headers))
108    }
109
110    #[must_use]
111    pub fn body_ref(&self) -> Option<&[u8]> {
112        self.body.as_deref()
113    }
114
115    #[must_use]
116    pub fn body_mut(&mut self) -> Option<&mut [u8]> {
117        self.body.as_deref_mut()
118    }
119
120    #[must_use]
121    pub fn headers_ref(&self) -> &Option<Headers> {
122        &self.headers
123    }
124
125    #[must_use]
126    pub fn headers_mut(&mut self) -> &mut Option<Headers> {
127        &mut self.headers
128    }
129
130    #[must_use]
131    pub fn method_ref(&self) -> &LightMethod {
132        &self.method
133    }
134
135    #[must_use]
136    pub fn route_ref(&self) -> &Route<'_> {
137        &self.route
138    }
139
140    #[must_use]
141    pub fn params_ref(&self) -> Option<&[(&'static str, String)]> {
142        self.params.as_deref()
143    }
144
145    #[must_use]
146    pub fn params_mut(&mut self) -> Option<&mut [(&'static str, String)]> {
147        self.params.as_deref_mut()
148    }
149}