otdb/
request.rs

1use serde::de::DeserializeOwned;
2use reqwest::{Client, RequestBuilder};
3use std::fmt::{Debug, Formatter, Result as FmtResult};
4use std::ops::{Deref, DerefMut};
5use std::marker::PhantomData;
6use crate::error::{HttpError, Result};
7use crate::options::*;
8
9/// A request used to make API calls.
10///
11/// This struct contains unowned fields and cannot be sent between threads, to do so consider
12/// using an [owned request](OwnedRequest), it can be obtained by
13/// using [into_owned](Request::into_owned)
14pub struct Request<'a, T> {
15    client: &'a Client,
16    token: &'a Option<String>,
17    endpoint: String,
18    options: Options,
19    marker: PhantomData<T>
20}
21
22impl<'a, T: DeserializeOwned> Request<'a, T> {
23    pub(crate) fn new(client: &'a Client, token: &'a Option<String>, endpoint: impl ToString) -> Self {
24        let mut this = Self {
25            client,
26            token,
27            endpoint: endpoint.to_string(),
28            options: Default::default(),
29            marker: PhantomData
30        };
31
32        this.question_number(10);
33        this
34    }
35
36    /// Converts the request into an [owned request](OwnedRequest)
37    ///
38    /// # Example
39    ///
40    /// ```rust
41    /// use otdb::Client;
42    ///
43    /// #[tokio::main]
44    /// async fn main() {
45    ///     let client = Client::new();
46    ///     let owned_request = client.trivia().into_owned();
47    ///
48    ///     match owned_request.send().await {
49    ///         Ok(response) => {
50    ///             // ...
51    ///         },
52    ///         Err(error) => {
53    ///             // ...
54    ///         }
55    ///     }
56    /// }
57    /// ```
58    pub fn into_owned(self) -> OwnedRequest<T> {
59        OwnedRequest {
60            client: self.client.clone(),
61            token: self.token.clone(),
62            endpoint: self.endpoint,
63            options: self.options,
64            marker: PhantomData
65        }
66    }
67
68    pub(crate) fn prepare(&mut self, mut request: RequestBuilder) -> RequestBuilder {
69        if let Some(t) = self.token {
70            request = request.query(&[("token", t)]);
71        }
72        self.options.prepare(request)
73    }
74
75    /// Sends the request, returning the proper response or error.
76    ///
77    /// # Example
78    ///
79    /// ```rust
80    /// use otdb::Client;
81    ///
82    /// #[tokio::main]
83    /// async fn main() {
84    ///     let client = Client::new();
85    ///     let request = client.trivia();
86    ///
87    ///     match request.send().await {
88    ///         Ok(response) => {
89    ///             // ...
90    ///         },
91    ///         Err(error) => {
92    ///             // ...
93    ///         }
94    ///     }
95    /// }
96    /// ```
97    pub async fn send(mut self) -> Result<T> {
98        Self::make_request(self.prepare(self.client.get(&self.endpoint))).await
99    }
100
101    async fn make_request(req: RequestBuilder) -> Result<T>
102    where
103    {
104        let response = req.send().await?;
105
106        match response.status().as_u16() {
107            200 => Ok(response.json().await?),
108            c if c >= 500 => Err(HttpError::InternalServerError(response.text().await?)),
109            _ => Err(HttpError::UnsuccessfulRequest(response.status(), response.text().await?)),
110        }
111    }
112}
113
114impl<T> Deref for Request<'_, T> {
115    type Target = Options;
116
117    fn deref(&self) -> &Self::Target {
118        &self.options
119    }
120}
121
122impl<T> DerefMut for Request<'_, T> {
123    fn deref_mut(&mut self) -> &mut Self::Target {
124        &mut self.options
125    }
126}
127
128impl<T: DeserializeOwned> Debug for Request<'_, T> {
129    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
130        f.debug_struct("Request")
131            .field("token", &self.token)
132            .field("endpoint", &self.endpoint)
133            .field("options", &self.options)
134            .finish()
135    }
136}
137
138/// A request used to make API calls.
139///
140/// Unlike the normal [request](Request), this struct does not contain any unowned field and can be
141/// sent between threads.
142pub struct OwnedRequest<T> {
143    client: Client,
144    token: Option<String>,
145    endpoint: String,
146    options: Options,
147    marker: PhantomData<T>
148}
149
150unsafe impl<T: DeserializeOwned> Send for OwnedRequest<T> {}
151
152impl<T: DeserializeOwned> OwnedRequest<T> {
153    pub(crate) fn prepare(&mut self, mut request: RequestBuilder) -> RequestBuilder {
154        if let Some(t) = &self.token {
155            request = request.query(&[("token", t)]);
156        }
157        self.options.prepare(request)
158    }
159
160    /// Sends the request, returning the proper response or error.
161    ///
162    /// # Example
163    ///
164    /// ```rust
165    /// use otdb::{Client, Difficulty};
166    ///
167    /// #[tokio::main]
168    /// async fn main() {
169    ///     let client = Client::new();
170    ///     let mut owned_request = client.trivia().into_owned();
171    ///     owned_request.difficulty(Difficulty::Easy);
172    ///
173    ///     match owned_request.send().await {
174    ///         Ok(response) => {
175    ///             // ...
176    ///         }
177    ///         Err(error) => {
178    ///             // ...
179    ///         }
180    ///     }
181    /// }
182    /// ```
183    pub async fn send(mut self) -> Result<T> {
184        Request::make_request(self.prepare(self.client.get(&self.endpoint))).await
185    }
186}
187
188impl<T: DeserializeOwned> Deref for OwnedRequest<T> {
189    type Target = Options;
190
191    fn deref(&self) -> &Self::Target {
192        &self.options
193    }
194}
195
196impl<T: DeserializeOwned> DerefMut for OwnedRequest<T> {
197    fn deref_mut(&mut self) -> &mut Self::Target {
198        &mut self.options
199    }
200}
201
202impl<T: DeserializeOwned> Debug for OwnedRequest<T> {
203    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
204        f.debug_struct("OwnedRequest")
205            .field("token", &self.token)
206            .field("endpoint", &self.endpoint)
207            .field("options", &self.options)
208            .finish()
209    }
210}