twitch_api_rs/
requests.rs1use async_trait::async_trait;
7use reqwest::Client;
8use reqwest::RequestBuilder;
9use serde::de::DeserializeOwned;
10use thiserror::Error;
11
12type None = ();
15
16impl Headers for None {
17 fn write_headers(&self, req: RequestBuilder) -> RequestBuilder {
18 req
19 }
20}
21
22impl Parameters for None {
23 fn write_parameters(&self, req: RequestBuilder) -> RequestBuilder {
24 req
25 }
26}
27
28impl Body for None {
29 fn write_body(&self, req: RequestBuilder) -> RequestBuilder {
30 req
31 }
32}
33
34use serde::Deserialize;
35
36#[derive(Debug, Deserialize)]
37pub struct FailureStatus<S>
40where
41 S: DeserializeOwned + std::fmt::Display + std::fmt::Debug + 'static,
42{
43 pub error: Option<String>,
45
46 #[serde(bound(deserialize = "S: DeserializeOwned"))]
47 pub status: S,
51
52 pub message: String,
54}
55
56impl<S> std::fmt::Display for FailureStatus<S>
57where
58 S: DeserializeOwned + std::fmt::Display + std::fmt::Debug + 'static,
59{
60 fn fmt(&self, w: &mut std::fmt::Formatter) -> std::fmt::Result {
61 if let Some(error) = &self.error {
62 write!(
63 w,
64 "Encountered error with code {}, error {}, and message {}",
65 self.status, error, self.message
66 )
67 } else {
68 write!(
69 w,
70 "Encountered error with code {}, and message {}",
71 self.status, self.message
72 )
73 }
74 }
75}
76
77impl<S> std::error::Error for FailureStatus<S> where
78 S: DeserializeOwned + std::fmt::Display + std::fmt::Debug + 'static
79{
80}
81
82impl<E: ErrorCodes> From<FailureStatus<u16>> for RequestError<E> {
83 fn from(failure: FailureStatus<u16>) -> Self {
84 match E::from_status(failure) {
85 Ok(known) => RequestError::KnownErrorStatus(known),
86 Err(unkn) => RequestError::UnkownErrorStatus(unkn),
87 }
88 }
89}
90
91#[derive(Debug, Deserialize)]
92#[serde(untagged)]
93pub enum PossibleResponse<R>
96where
97 R: Response + 'static,
98{
99 #[serde(bound(deserialize = "R: DeserializeOwned"))]
100 Response(R),
102
103 Failure(FailureStatus<u16>),
105}
106
107impl<R> PossibleResponse<R>
108where
109 R: Response + 'static,
110{
111 fn into_result(self) -> Result<R, FailureStatus<u16>> {
112 match self {
113 Self::Response(r) => Ok(r),
114 Self::Failure(f) => Err(f),
115 }
116 }
117}
118
119#[derive(Debug, Error)]
120pub enum RequestError<C: ErrorCodes + 'static> {
122 #[error("You must provide valid authorization to this endpoint")]
123 MissingAuth,
125
126 #[error("Request Malformed with message: {0}")]
127 MalformedRequest(String),
129
130 #[error("Did not have user scopes required {0:?}")]
131 ScopesError(Vec<String>),
133
134 #[error("Known Error enountered: {0}")]
135 KnownErrorStatus(FailureStatus<C>),
137
138 #[error("Unknown Error enountered: {0}")]
139 UnkownErrorStatus(FailureStatus<u16>),
141
142 #[error("Reqwest encountered an error: {0}")]
143 ReqwestError(#[from] reqwest::Error),
145
146 #[error("Unknown Error encountered {0:?}")]
147 UnknownError(#[from] Box<dyn std::error::Error>),
149}
150
151pub trait ErrorCodes: std::error::Error + Sized + DeserializeOwned + Copy {
155 fn from_status(codes: FailureStatus<u16>) -> Result<FailureStatus<Self>, FailureStatus<u16>>;
157}
158
159#[derive(Debug, Clone, Copy, Error, Deserialize)]
160pub enum CommonResponseCodes {
162 #[error("400: Malformed Request")]
163 BadRequestCode,
165
166 #[error("401: Authorization Error")]
167 AuthErrorCode,
169
170 #[error("500: Server Error")]
171 ServerErrorCode,
174}
175
176#[macro_export]
177macro_rules! response_codes {
180 ($for:ty : [$($val:expr => $item:path),+]) => {
181 impl ErrorCodes for $for {
182 fn from_status(codes: FailureStatus<u16>) -> Result<FailureStatus<Self>, FailureStatus<u16>> {
183 match codes.status {
184 $(
185 $val => Ok(FailureStatus::<Self> {
186 error: codes.error,
187 status: $item,
188 message: codes.message
189 }),
190 )*
191 _ => Err(codes),
192 }
193 }
194 }
195 }
196}
197
198response_codes!(
199 CommonResponseCodes: [
200 400 => CommonResponseCodes::BadRequestCode,
201 401 => CommonResponseCodes::AuthErrorCode,
202 500 => CommonResponseCodes::ServerErrorCode
203]);
204
205pub trait Headers {
207 fn write_headers(&self, req: RequestBuilder) -> RequestBuilder;
209}
210
211pub trait HeadersExt {
215 fn as_ref<'a>(&'a self) -> &'a [(&'a str, &'a str)];
217}
218
219impl<T: HeadersExt> Headers for T {
220 fn write_headers<'a>(&'a self, mut req: RequestBuilder) -> RequestBuilder {
221 for (a, b) in self.as_ref() {
222 req = req.header(*a, *b);
223 }
224 req
225 }
226}
227
228pub trait Parameters {
230 fn write_parameters(&self, req: RequestBuilder) -> RequestBuilder;
232}
233
234pub trait ParametersExt: serde::Serialize {}
237
238impl<T: ParametersExt> Parameters for T {
239 fn write_parameters(&self, req: RequestBuilder) -> RequestBuilder {
240 req.query(self)
241 }
242}
243
244pub trait Body {
246 fn write_body(&self, req: RequestBuilder) -> RequestBuilder;
248}
249
250pub trait BodyExt: serde::Serialize {}
253
254impl<T: BodyExt> Body for T {
255 fn write_body(&self, req: RequestBuilder) -> RequestBuilder {
256 req.json(self)
257 }
258}
259
260#[async_trait]
262#[cfg_attr(feature = "nightly", doc(spotlight))]
263pub trait Request {
264 const ENDPOINT: &'static str;
266
267 type Headers: Headers;
269
270 type Parameters: Parameters;
272
273 type Body: Body;
275
276 type Response: Response + 'static;
279
280 type ErrorCodes: ErrorCodes + 'static;
283
284 const METHOD: reqwest::Method;
286
287 fn builder() -> Self;
289
290 fn headers(&self) -> &Self::Headers;
295
296 fn parameters(&self) -> &Self::Parameters;
301
302 fn body(&self) -> &Self::Body;
307
308 fn ready(&self) -> Result<(), RequestError<Self::ErrorCodes>>;
316
317 async fn make_request<C>(
320 &self,
321 client: C,
322 ) -> Result<Self::Response, RequestError<Self::ErrorCodes>>
323 where
324 C: std::borrow::Borrow<Client> + Send,
325 {
326 self.ready()?;
328
329 let mut req = client.borrow().request(Self::METHOD, Self::ENDPOINT);
331
332 req = self.headers().write_headers(req);
334 req = self.parameters().write_parameters(req);
335 req = self.body().write_body(req);
336
337 log::info!("Making request {:#?}", req);
338
339 let resp = req.send().await?;
341
342 log::info!("Got response {:#?}", resp);
343
344 resp.json::<PossibleResponse<Self::Response>>()
345 .await?
346 .into_result()
347 .map_err(FailureStatus::into)
348 }
349}
350
351pub trait Response: DeserializeOwned + Sized {}
353
354impl<T: DeserializeOwned> Response for T {}