twilight_http/request/base.rs
1use super::{Form, Method};
2use crate::{
3 error::Error,
4 routing::{Path, Route},
5};
6use http::header::{HeaderMap, HeaderName, HeaderValue};
7use serde::Serialize;
8
9/// Builder to create a customized request.
10///
11/// # Examples
12///
13/// Create a request to create a message with a content of "test" in a
14/// channel with an ID of 1:
15///
16/// ```
17/// use twilight_http::{request::Request, routing::Route};
18///
19/// let body = br#"{
20/// "content": "test"
21/// }"#
22/// .to_vec();
23///
24/// let request = Request::builder(&Route::CreateMessage { channel_id: 1 })
25/// .body(body)
26/// .build();
27/// ```
28#[derive(Debug)]
29#[must_use = "request has not been fully built"]
30pub struct RequestBuilder(Result<Request, Error>);
31
32impl RequestBuilder {
33 /// Create a new request builder.
34 pub fn new(route: &Route<'_>) -> Self {
35 Self(Ok(Request::from_route(route)))
36 }
37
38 /// Create a request with raw information about the method, ratelimiting
39 /// path, and URL path and query.
40 ///
41 /// The path and query should not include the leading slash as that is
42 /// prefixed by the client. In the URL
43 /// `https://discord.com/api/vX/channels/123/pins` the "path and query"
44 /// is considered to be `channels/123/pins`.
45 ///
46 /// # Examples
47 ///
48 /// Create a request from a method and the URL path and query
49 /// `channels/123/pins`:
50 ///
51 /// ```
52 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
53 /// use std::str::FromStr;
54 /// use twilight_http::{
55 /// request::{Method, RequestBuilder},
56 /// routing::Path,
57 /// };
58 ///
59 /// let method = Method::Post;
60 /// let path_and_query = "channels/123/pins".to_owned();
61 /// let ratelimit_path = Path::from_str(&path_and_query)?;
62 ///
63 /// let _request = RequestBuilder::raw(method, ratelimit_path, path_and_query).build();
64 /// # Ok(()) }
65 /// ```
66 pub const fn raw(method: Method, ratelimit_path: Path, path_and_query: String) -> Self {
67 Self(Ok(Request {
68 body: None,
69 form: None,
70 headers: None,
71 method,
72 path: path_and_query,
73 ratelimit_path,
74 use_authorization_token: true,
75 }))
76 }
77
78 /// Consume the builder, returning the built request.
79 ///
80 /// # Errors
81 ///
82 /// Returns an [`ErrorType::Json`] error type JSON input could not be
83 /// serialized.
84 ///
85 /// [`ErrorType::Json`]: crate::error::ErrorType::Json
86 #[allow(clippy::missing_const_for_fn)]
87 #[must_use = "request information is not useful on its own and must be acted on"]
88 pub fn build(self) -> Result<Request, Error> {
89 self.0
90 }
91
92 /// Set the contents of the body.
93 pub fn body(mut self, body: Vec<u8>) -> Self {
94 if let Ok(request) = self.0.as_mut() {
95 request.body = Some(body);
96 }
97
98 self
99 }
100
101 /// Set the multipart form.
102 #[allow(clippy::missing_const_for_fn)]
103 pub fn form(mut self, form: Form) -> Self {
104 if let Ok(request) = self.0.as_mut() {
105 request.form = Some(form);
106 }
107
108 self
109 }
110
111 /// Set the headers to add.
112 pub fn headers(mut self, iter: impl Iterator<Item = (HeaderName, HeaderValue)>) -> Self {
113 if let Ok(request) = self.0.as_mut() {
114 request.headers.replace(iter.collect());
115 }
116
117 self
118 }
119
120 /// Set the body, to be serialized as JSON.
121 pub fn json(mut self, to: &impl Serialize) -> Self {
122 self.0 = self.0.and_then(|mut request| {
123 let bytes = crate::json::to_vec(to).map_err(Error::json)?;
124 request.body = Some(bytes);
125
126 Ok(request)
127 });
128
129 self
130 }
131
132 /// Whether to use the client's authorization token in the request, if one
133 /// is set.
134 ///
135 /// This is primarily useful for executing webhooks.
136 pub fn use_authorization_token(mut self, use_authorization_token: bool) -> Self {
137 if let Ok(request) = self.0.as_mut() {
138 request.use_authorization_token = use_authorization_token;
139 }
140
141 self
142 }
143}
144
145#[derive(Clone, Debug)]
146pub struct Request {
147 pub(crate) body: Option<Vec<u8>>,
148 pub(crate) form: Option<Form>,
149 pub(crate) headers: Option<HeaderMap<HeaderValue>>,
150 pub(crate) method: Method,
151 pub(crate) path: String,
152 pub(crate) ratelimit_path: Path,
153 pub(crate) use_authorization_token: bool,
154}
155
156impl Request {
157 /// Create a new request builder.
158 ///
159 /// # Examples
160 ///
161 /// Create a request to create a message with a content of "test" in a
162 /// channel with an ID of 1:
163 ///
164 /// ```
165 /// use twilight_http::{request::Request, routing::Route};
166 ///
167 /// let body = br#"{
168 /// "content": "test"
169 /// }"#
170 /// .to_vec();
171 ///
172 /// let request = Request::builder(&Route::CreateMessage { channel_id: 1 })
173 /// .body(body)
174 /// .build();
175 /// ```
176 pub fn builder(route: &Route<'_>) -> RequestBuilder {
177 RequestBuilder::new(route)
178 }
179
180 /// Create a request from only its route information.
181 ///
182 /// If you need to set additional configurations like the body then use
183 /// [`builder`].
184 ///
185 /// # Examples
186 ///
187 /// Create a request to get a message with an ID of 2 in a channel with an
188 /// ID of 1:
189 ///
190 /// ```
191 /// use twilight_http::{request::Request, routing::Route};
192 ///
193 /// let request = Request::from_route(&Route::GetMessage {
194 /// channel_id: 1,
195 /// message_id: 2,
196 /// });
197 /// ```
198 ///
199 /// [`builder`]: Self::builder
200 pub fn from_route(route: &Route<'_>) -> Self {
201 Self {
202 body: None,
203 form: None,
204 headers: None,
205 method: route.method(),
206 path: route.to_string(),
207 ratelimit_path: route.to_path(),
208 use_authorization_token: true,
209 }
210 }
211
212 /// Body of the request, if any.
213 pub fn body(&self) -> Option<&[u8]> {
214 self.body.as_deref()
215 }
216
217 /// Multipart form of the request, if any.
218 pub const fn form(&self) -> Option<&Form> {
219 self.form.as_ref()
220 }
221
222 /// Headers to set in the request, if any.
223 pub const fn headers(&self) -> Option<&HeaderMap<HeaderValue>> {
224 self.headers.as_ref()
225 }
226
227 /// Method when sending the request.
228 pub const fn method(&self) -> Method {
229 self.method
230 }
231
232 /// String path of the full URL.
233 pub fn path(&self) -> &str {
234 &self.path
235 }
236
237 /// Path used for ratelimiting.
238 pub const fn ratelimit_path(&self) -> &Path {
239 &self.ratelimit_path
240 }
241
242 /// Whether to use the client's authorization token in the request.
243 pub const fn use_authorization_token(&self) -> bool {
244 self.use_authorization_token
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::RequestBuilder;
251 use static_assertions::assert_impl_all;
252 use std::fmt::Debug;
253
254 assert_impl_all!(RequestBuilder: Debug, Send, Sync);
255}