lotr_api/client.rs
1//! Client definition. This is the main entry point for the library.
2//! It is used to make requests to the API. It is created with a token, which is used to authenticate the requests.
3//! You can get a token from <https://the-one-api.dev/>.
4
5use crate::{
6 request::{Request, Requester},
7 Book, Chapter, Character, Error, Item, ItemType, Movie, Quote, Response,
8};
9
10/// The client for the one api to rule them all.
11/// It is used to make requests to the API.
12///
13/// # Examples
14/// ```rust, no_run
15/// use lotr_api::Client;
16///
17/// #[tokio::main]
18/// async fn main() {
19/// let client = Client::new("your_token".to_string());
20/// let books = client.get_books().await.unwrap();
21/// // ...
22/// }
23/// ```
24pub struct Client {
25 requester: Requester,
26}
27
28impl Client {
29 /// Creates a new client with the given token.
30 /// The token is used to authenticate the requests.
31 /// You can get a token from <https://the-one-api.dev/>.
32 pub fn new(token: String) -> Self {
33 Self {
34 requester: Requester::new(token),
35 }
36 }
37
38 async fn request_with_url<T>(&self, url: &str) -> Result<Response<T>, Error>
39 where
40 T: serde::de::DeserializeOwned,
41 {
42 let response = self.requester.get(url).await?;
43 let response: Response<T> = serde_json::from_str(&response).map_err(Error::from)?;
44 Ok(response)
45 }
46
47 async fn request<T>(&self, request: Request) -> Result<Response<T>, Error>
48 where
49 T: serde::de::DeserializeOwned,
50 {
51 let response = self.requester.get_from_request(request).await?;
52 let response: Response<T> = serde_json::from_str(&response).map_err(Error::from)?;
53 Ok(response)
54 }
55
56 /// Returns all books.
57 pub async fn get_books(&self) -> Result<Vec<Book>, Error> {
58 Ok(self.request_with_url::<Book>("book").await?.get_contents())
59 }
60
61 /// Returns all movies.
62 pub async fn get_movies(&self) -> Result<Vec<Movie>, Error> {
63 Ok(self
64 .request_with_url::<Movie>("movie")
65 .await?
66 .get_contents())
67 }
68
69 /// Returns all the quotes. Due to the API default limit of 1000,
70 /// this function has to set a hardcoded limit.
71 /// Currently there are 2384 quotes on the api, so we call it with a limit of 2400
72 /// to have a little bit of buffer in case the number of quotes increases
73 pub async fn get_quotes(&self) -> Result<Vec<Quote>, Error> {
74 Ok(self
75 .request_with_url::<Quote>("quote?limit=2400")
76 .await?
77 .get_contents())
78 }
79
80 // Returns all the characters.
81 pub async fn get_characters(&self) -> Result<Vec<Character>, Error> {
82 Ok(self
83 .request_with_url::<Character>("character")
84 .await?
85 .get_contents())
86 }
87
88 /// Returns all chapters.
89 pub async fn get_chapters(&self) -> Result<Vec<Chapter>, Error> {
90 Ok(self
91 .request_with_url::<Chapter>("chapter")
92 .await?
93 .get_contents())
94 }
95
96 /// Returns the book with the given id.
97 ///
98 /// # Errors
99 /// If there is no book with the given id, an error is returned.
100 pub async fn get_book_by_id(&self, id: &str) -> Result<Book, Error> {
101 let url = format!("book/{}", id);
102 let mut books = self.request_with_url::<Book>(&url).await?.get_contents();
103 books
104 .pop()
105 .ok_or(Error::Other("No book with id {} found".to_string()))
106 }
107
108 /// Returns the movie with the given id.
109 ///
110 /// # Errors
111 /// If there is no movie with the given id, an error is returned.
112 pub async fn get_movie_by_id(&self, id: &str) -> Result<Movie, Error> {
113 let url = format!("movie/{}", id);
114 let mut movies = self.request_with_url::<Movie>(&url).await?.get_contents();
115 movies
116 .pop()
117 .ok_or(Error::Other("No movie with id {} found".to_string()))
118 }
119
120 /// Returns the quote with the given id.
121 ///
122 /// # Errors
123 /// If there is no quote with the given id, an error is returned.
124 pub async fn get_quote_by_id(&self, id: &str) -> Result<Quote, Error> {
125 let url = format!("quote/{}", id);
126 let mut quotes = self.request_with_url::<Quote>(&url).await?.get_contents();
127 quotes
128 .pop()
129 .ok_or(Error::Other("No quote with id {} found".to_string()))
130 }
131
132 /// Returns the character with the given id.
133 ///
134 /// # Errors
135 /// If there is no character with the given id, an error is returned.
136 pub async fn get_character_by_id(&self, id: &str) -> Result<Character, Error> {
137 let url = format!("character/{}", id);
138 let mut characters = self
139 .request_with_url::<Character>(&url)
140 .await?
141 .get_contents();
142 characters
143 .pop()
144 .ok_or(Error::Other("No character with id {} found".to_string()))
145 }
146
147 /// Returns the chapter with the given id.
148 ///
149 /// # Errors
150 /// If there is no chapter with the given id, an error is returned.
151 pub async fn get_chapter_by_id(&self, id: &str) -> Result<Chapter, Error> {
152 let url = format!("chapter/{}", id);
153 let mut chapters = self.request_with_url::<Chapter>(&url).await?.get_contents();
154 chapters
155 .pop()
156 .ok_or(Error::Other("No chapter with id {} found".to_string()))
157 }
158
159 /// Returns the chapters of the given book.
160 pub async fn get_chapters_from_book(&self, book_id: &str) -> Result<Vec<Chapter>, Error> {
161 let url = format!("book/{}/chapter", book_id);
162 Ok(self.request_with_url::<Chapter>(&url).await?.get_contents())
163 }
164
165 /// Returns the quotes of the given book.
166 pub async fn get_quotes_from_movie(&self, movie_id: &str) -> Result<Vec<Quote>, Error> {
167 let url = format!("movie/{}/quote", movie_id);
168 Ok(self.request_with_url::<Quote>(&url).await?.get_contents())
169 }
170
171 /// Returns the quotes of the given book.
172 pub async fn get_quotes_from_character(&self, character_id: &str) -> Result<Vec<Quote>, Error> {
173 let url = format!("character/{}/quote", character_id);
174 Ok(self.request_with_url::<Quote>(&url).await?.get_contents())
175 }
176
177 /// returns the result of the given request.
178 /// You must specify the type of the result, if not
179 /// there is no way of deserialize the result.
180 ///
181 /// # Examples
182 ///
183 /// ```rust, no_run
184 /// use lotr_api::{Client, ItemType};
185 /// use lotr_api::Book;
186 ///
187 /// #[tokio::main]
188 /// async fn main() {
189 /// let client = Client::new("your_token".to_string());
190 /// let url= "book?page=2&limit=2";
191 /// let books = client.get_from_url::<Book>(url).await.unwrap();
192 /// // ...
193 /// }
194 /// ```
195 ///
196 pub async fn get_from_url<T>(&self, url: &str) -> Result<Vec<T>, Error>
197 where
198 T: serde::de::DeserializeOwned,
199 {
200 Ok(self.request_with_url::<T>(url).await?.get_contents())
201 }
202
203 /// Returns the items of the given custom request.
204 ///
205 /// # Examples
206 /// ```rust, no_run
207 /// use lotr_api::{
208 /// attribute::{Attribute, BookAttribute},
209 /// filter::{Filter, Operator},
210 /// request::{
211 /// sort::{Sort, SortOrder},
212 /// RequestBuilder},
213 /// Client, Item, ItemType};
214 ///
215 /// #[tokio::main]
216 /// async fn main() {
217 /// let client = Client::new("your_token".to_string());
218 /// let request = RequestBuilder::new(ItemType::Book)
219 /// .filter(Filter::Match(
220 /// Attribute::Book(BookAttribute::Name),
221 /// Operator::Eq,
222 /// vec!["The Fellowship of the Ring".to_string()])
223 /// )
224 /// .sort(Sort::new(SortOrder::Ascending, Attribute::Book(BookAttribute::Name)))
225 /// .build()
226 /// .expect("Failed to build request");
227 /// let books = client.get(request).await.unwrap();
228 /// // ...
229 /// }
230 ///
231 pub async fn get(&self, request: Request) -> Result<Vec<Item>, Error> {
232 match request.get_item_type() {
233 ItemType::Book => {
234 let response = self.request::<Book>(request).await?;
235 Ok(response.into())
236 }
237
238 ItemType::Movie => {
239 let response = self.request::<Movie>(request).await?;
240 Ok(response.into())
241 }
242
243 ItemType::Quote => {
244 let response = self.request::<Quote>(request).await?;
245 Ok(response.into())
246 }
247
248 ItemType::Character => {
249 let response = self.request::<Character>(request).await?;
250 Ok(response.into())
251 }
252
253 ItemType::Chapter => {
254 let response = self.request::<Chapter>(request).await?;
255 Ok(response.into())
256 }
257 }
258 }
259}