brawl_api/traits.rs
1//! Traits used by the library. Those are of mostly internal use and normally shouldn't need
2//! implementation by some user-made type.
3//!
4//! Note that it is recommended to always import this module for things to work fine, such as
5//! using `X::fetch` methods, where `X` is any model implementing `PropFetchable` or
6//! `PropLimFetchable` (there are some that do not implement either, and rather have their own
7//! implementation of a `fetch` function, because they have 3 or more parameters).
8
9use crate::error::{Result};
10
11#[cfg(feature = "async")]
12use async_trait::async_trait;
13use crate::http::Client;
14// use serde::de::DeserializeOwned;
15
16use crate::http::routes::Route;
17
18pub mod propfetch {
19 use super::*;
20
21 /// A trait representing a type with a property used to be fetched, and a route with which to
22 /// fetch it. This can be either a tag or the limit of fetching, for example.
23 ///
24 /// The property must be returned by the `get_fetch_prop` function.
25 /// The route must be returned by the `get_route` function.
26 /// This trait is used in parallel with `PropFetchable`.
27 ///
28 /// [`PropFetchable`]: traits/propfetch/trait.PropFetchable.html
29 /// [`GetFetchProp`]: traits/propfetch/trait.GetFetchProp.html
30 pub trait GetFetchProp: Sized {
31 type Property: ?Sized;
32
33 /// Obtain the revelant property for fetching.
34 fn get_fetch_prop(&self) -> &Self::Property;
35 // necessary for Refetchable blanket impl
36
37 /// Obtain the route for fetching by using a property.
38 fn get_route(prop: &Self::Property) -> Route;
39 }
40
41 /// A trait representing a weaker variant of [`GetFetchProp`]; only indicates the fetching route can
42 /// be obtained by the `get_route` method by specifying a property, not that such property is
43 /// obtainable. Note that all `GetFetchProp` implementers also implement `PropRouteable`, thanks
44 /// to a blanket impl.
45 pub trait PropRouteable: Sized {
46 type Property: ?Sized;
47
48 /// Obtain the route for fetching by using a property.
49 fn get_route(prop: &Self::Property) -> Route;
50 }
51
52 impl<T: GetFetchProp> PropRouteable for T {
53 type Property = <T as GetFetchProp>::Property;
54
55 fn get_route(prop: &<Self as PropRouteable>::Property) -> Route {
56 <Self as GetFetchProp>::get_route(prop)
57 }
58 }
59
60 /// A trait representing a type whose instance can be fetched from the API using some property.
61 /// This is usually the object's tag.
62 #[cfg_attr(feature = "async", async_trait)]
63 pub trait PropFetchable: Sized {
64 type Property: ?Sized;
65
66 /// (Sync) Fetch and construct a new instance of this type.
67 ///
68 /// # Errors
69 ///
70 /// This function may error:
71 /// - While requesting (will return an [`Error::Request`]);
72 /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
73 /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
74 /// - While parsing incoming JSON (will return an [`Error::Json`]).
75 ///
76 /// (All of those, of course, wrapped inside an `Err`.)
77 ///
78 /// # Examples
79 ///
80 /// Fetching a [`Player`] instance from a player tag:
81 ///
82 /// ```rust,ignore
83 /// use brawl_api::{Client, Player, traits::*};
84 ///
85 /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
86 /// let my_client = Client::new("my auth token");
87 /// let player = Player::fetch(&my_client, "#PLAYERTAGHERE")?;
88 /// // now the data for the given player is available for use
89 ///
90 /// # Ok(())
91 /// # }
92 /// ```
93 ///
94 /// [`Player`]: model/players/player/struct.Player.html
95 /// [`Error::Request`]: error/enum.Error.html#variant.Request
96 /// [`Error::Status`]: error/enum.Error.html#variant.Status
97 /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
98 /// [`Error::Json`]: error/enum.Error.html#variant.Json
99 fn fetch(client: &Client, prop: &Self::Property) -> Result<Self>;
100
101 /// (Async) Fetch and construct a new instance of this type.
102 ///
103 /// # Errors
104 ///
105 /// This function may error:
106 /// - While requesting (will return an [`Error::Request`]);
107 /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
108 /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
109 /// - While parsing incoming JSON (will return an [`Error::Json`]).
110 ///
111 /// (All of those, of course, wrapped inside an `Err`.)
112 ///
113 /// # Examples
114 ///
115 /// Fetching a [`Player`] instance from a player tag:
116 ///
117 /// ```rust,ignore
118 /// use brawl_api::{Client, Player, traits::*};
119 ///
120 /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
121 /// let my_client = Client::new("my auth token");
122 /// let player = Player::a_fetch(&my_client, "#PLAYERTAGHERE").await?;
123 /// // now the data for the given player is available for use
124 ///
125 /// # Ok(())
126 /// # }
127 /// ```
128 ///
129 /// [`Player`]: model/players/player/struct.Player.html
130 /// [`Error::Request`]: error/enum.Error.html#variant.Request
131 /// [`Error::Status`]: error/enum.Error.html#variant.Status
132 /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
133 /// [`Error::Json`]: error/enum.Error.html#variant.Json
134 #[cfg(feature = "async")]
135 async fn a_fetch(client: &Client, prop: &'async_trait Self::Property) -> Result<Self>
136 where Self: 'async_trait,
137 Self::Property: 'async_trait;
138 }
139}
140
141pub use propfetch::*;
142
143pub mod proplimfetch {
144 use super::*;
145 use num_traits::PrimInt;
146
147 /// A trait representing a type that returns a [`Route`] object given a property and a limit
148 /// of how many to fetch.
149 pub trait PropLimRouteable {
150 type Property: ?Sized;
151 type Limit: PrimInt; // must be numeric!
152
153 /// Obtain the route for fetching by using a property and specifying the limit of fetching.
154 fn get_route(prop: &Self::Property, limit: Self::Limit) -> Route;
155 }
156
157
158 /// A trait representing a type whose instance can be fetched from the API using some property
159 /// and specifying a limit of how many objects to fetch.
160 ///
161 /// **Note:** types which simply require the limit for fetching use [`PropFetchable`] instead
162 /// (with the limit being the property itself).
163 #[cfg_attr(feature = "async", async_trait)]
164 pub trait PropLimFetchable: Sized {
165 type Property: ?Sized;
166 type Limit: PrimInt; // numeric
167
168 /// (Sync) Fetch and construct a new instance of this type.
169 ///
170 /// # Errors
171 ///
172 /// This function may error:
173 /// - While requesting (will return an [`Error::Request`]);
174 /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
175 /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
176 /// - While parsing incoming JSON (will return an [`Error::Json`]).
177 ///
178 /// (All of those, of course, wrapped inside an `Err`.)
179 ///
180 /// # Examples
181 ///
182 /// Fetching a world-wide player leaderboard ([`PlayerLeaderboard`]):
183 /// ```rust,ignore
184 /// use brawl_api::{PlayerLeaderboard, Client, traits::PropLimFetchable};
185 ///
186 /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
187 /// let client = Client::new("my auth key");
188 ///
189 /// // if the fetch is successful, then the variable below will have the global top 100 players
190 /// // in the 'items' field (i.e. '*top100players').
191 /// let top100players: PlayerLeaderboard = PlayerLeaderboard::fetch(&client, "global", 100)?;
192 ///
193 /// // get player ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
194 /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
195 /// let player1 = &top100players[0];
196 ///
197 /// assert_eq!(player1.rank, 1);
198 ///
199 /// # Ok(())
200 /// # }
201 /// ```
202 ///
203 /// [`PlayerLeaderboard`]: model/rankings/players/struct.PlayerLeaderboard.html
204 /// [`Error::Request`]: error/enum.Error.html#variant.Request
205 /// [`Error::Status`]: error/enum.Error.html#variant.Status
206 /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
207 /// [`Error::Json`]: error/enum.Error.html#variant.Json
208 fn fetch(client: &Client, prop: &Self::Property, limit: Self::Limit) -> Result<Self>;
209
210 /// (Async) Fetch and construct a new instance of this type.
211 ///
212 /// # Errors
213 ///
214 /// This function may error:
215 /// - While requesting (will return an [`Error::Request`]);
216 /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
217 /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
218 /// - While parsing incoming JSON (will return an [`Error::Json`]).
219 ///
220 /// (All of those, of course, wrapped inside an `Err`.)
221 ///
222 /// # Examples
223 ///
224 /// Fetching a world-wide player leaderboard ([`PlayerLeaderboard`]):
225 /// ```rust,ignore
226 /// use brawl_api::{PlayerLeaderboard, Client, traits::PropLimFetchable};
227 ///
228 /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
229 /// let client = Client::new("my auth key");
230 ///
231 /// // if the fetch is successful, then the variable below will have the global top 100 players
232 /// // in the 'items' field (i.e. '*top100players').
233 /// let top100players: PlayerLeaderboard = PlayerLeaderboard::a_fetch(&client, "global", 100).await?;
234 ///
235 /// // get player ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
236 /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
237 /// let player1 = &top100players[0];
238 ///
239 /// assert_eq!(player1.rank, 1);
240 ///
241 /// # Ok(())
242 /// # }
243 /// ```
244 ///
245 /// [`PlayerLeaderboard`]: model/rankings/players/struct.PlayerLeaderboard.html
246 /// [`Error::Request`]: error/enum.Error.html#variant.Request
247 /// [`Error::Status`]: error/enum.Error.html#variant.Status
248 /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
249 /// [`Error::Json`]: error/enum.Error.html#variant.Json
250 #[cfg(feature = "async")]
251 async fn a_fetch(client: &Client, prop: &'async_trait Self::Property, limit: Self::Limit) -> Result<Self>
252 where Self: 'async_trait,
253 Self::Property: 'async_trait,
254 Self::Limit: 'async_trait;
255 }
256}
257
258pub use proplimfetch::*;
259
260// endregion:PropFetch
261
262/// A trait representing a type whose instance can be fetched again.
263/// Note that all types implementing [`GetFetchProp`] and [`PropFetchable`] also implement
264/// [`Refetchable`] due to a blanket implementation.
265///
266/// [`PropFetchable`]: traits/propfetch/trait.PropFetchable.html
267/// [`GetFetchProp`]: traits/propfetch/trait.GetFetchProp.html
268#[cfg_attr(feature = "async", async_trait)]
269pub trait Refetchable: Sized {
270 /// (Sync) Causes this instance to be re-fetched (i.e., updated to latest Brawl Stars data).
271 ///
272 /// # Examples
273 ///
274 /// ```rust,ignore
275 /// use brawl_api::prelude::*;
276 ///
277 /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
278 /// let my_client = Client::new("my auth key");
279 ///
280 /// let player = Player::fetch(&my_client, "#PLAYER_TAG_HERE")?;
281 ///
282 /// // after using it a bit, we want to update its data
283 ///
284 /// let player = player.refetch(&my_client)?; // `refetch` does not mutate the instance!
285 ///
286 /// // player variable is now up-to-date.
287 ///
288 /// # Ok(())
289 /// # }
290 /// ```
291 fn refetch(&self, client: &Client) -> Result<Self>;
292
293 /// (Sync) Like `refetch`, but mutates the instance, returning an immutable reference to it.
294 ///
295 /// Its usage and errors are the same (it is called, after all), but the old variable does
296 /// not need to be assigned; rather, that is done for the programmer (the variable's value
297 /// **is entirely replaced** by a new one, if the fetching is successful).
298 fn refetch_update(&mut self, client: &Client) -> Result<&Self> {
299 *self = self.refetch(client)?;
300 Ok(self as &Self)
301 }
302
303 /// (Async) Causes this instance to be re-fetched (i.e., updated to latest Brawl Stars data).
304 ///
305 /// # Examples
306 ///
307 /// ```rust,ignore
308 /// use brawl_api::prelude::*;
309 ///
310 /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
311 /// let my_client = Client::new("my auth key");
312 ///
313 /// let player = Player::a_fetch(&my_client, "#PLAYER_TAG_HERE").await?;
314 ///
315 /// // after using it a bit, we want to update its data
316 ///
317 /// let player = player.a_refetch(&my_client).await?; // this does not mutate the old instance!
318 ///
319 /// // player variable is now up-to-date.
320 ///
321 /// # Ok(())
322 /// # }
323 /// ```
324 #[cfg(feature = "async")]
325 async fn a_refetch(&self, client: &Client) -> Result<Self>;
326
327 /// (Async) Like `a_refetch`, but mutates the instance, returning an immutable reference to it.
328 ///
329 /// Its usage and errors are the same (it is called, after all), but the old variable does
330 /// not need to be assigned; rather, that is done for the programmer (the variable's value
331 /// **is entirely replaced** by a new one, if the fetching is successful).
332 #[cfg(feature = "async")]
333 async fn a_refetch_update(&'async_trait mut self, client: &Client) -> Result<&'async_trait Self>
334 where Self: Send + Sync,
335 {
336 *self = self.a_refetch(client).await?;
337 Ok(self as &Self)
338 }
339}
340
341#[cfg_attr(feature = "async", async_trait)]
342impl<T> Refetchable for T
343 where T: PropFetchable<Property=<T as GetFetchProp>::Property> + GetFetchProp + Sized + Send + Sync,
344 <T as GetFetchProp>::Property: Sync + Send {
345 fn refetch(&self, client: &Client) -> Result<Self> {
346 Self::fetch(client, self.get_fetch_prop())
347 }
348
349 #[cfg(feature = "async")]
350 async fn a_refetch(&self, client: &Client) -> Result<Self>
351 where T: 'async_trait,
352 <T as GetFetchProp>::Property: 'async_trait,
353 {
354 Self::a_fetch(client, self.get_fetch_prop()).await
355 }
356}
357
358
359/// A trait indicating that another type can be converted into this one by fetching from the API.
360/// Note that, thanks to a blanket implementation, implementing this implies implementing
361/// [`FetchInto`] for the other type.
362///
363/// [`FetchInto`]: traits/trait.FetchInto.html
364#[cfg_attr(feature = "async", async_trait)]
365pub trait FetchFrom<T>: Sized {
366 /// (Sync) Attempts to request to the API and return a new instance of the type being turned
367 /// into.
368 ///
369 /// # Errors
370 ///
371 /// See the respective struct's `fetch` implementation (or `PropFetchable`/`PropLimFetchable`
372 /// implementation).
373 ///
374 /// # Examples
375 ///
376 /// ```rust,ignore
377 /// use brawl_api::{Client, Player, Club, traits::*};
378 ///
379 /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
380 /// let my_client = Client::new("my auth token");
381 /// let club = Club::fetch(&my_client, "#CLUB_TAG_HERE")?;
382 /// let some_member = &club.members[0];
383 /// let some_player = Player::fetch_from(&my_client, some_member)?;
384 /// // now `some_member`'s full data, as a Player, is available for use.
385 ///
386 /// # Ok(())
387 /// # }
388 /// ```
389 fn fetch_from(client: &Client, value: &T) -> Result<Self>;
390
391 /// (Async) Attempts to request to the API and return a new instance of the type being turned
392 /// into.
393 ///
394 /// # Errors
395 ///
396 /// See the respective struct's `a_fetch` implementation (or `PropFetchable`/`PropLimFetchable`
397 /// implementation).
398 ///
399 /// # Examples
400 ///
401 /// ```rust,ignore
402 /// use brawl_api::{Client, Player, Club, traits::*};
403 ///
404 /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
405 /// let my_client = Client::new("my auth token");
406 /// let club = Club::a_fetch(&my_client, "#CLUB_TAG_HERE").await?;
407 /// let some_member = &club.members[0];
408 /// let some_player = Player::a_fetch_from(&my_client, some_member).await?;
409 /// // now `some_member`'s full data, as a Player, is available for use.
410 ///
411 /// # Ok(())
412 /// # }
413 /// ```
414 #[cfg(feature = "async")]
415 async fn a_fetch_from(client: &Client, value: &T) -> Result<Self>;
416}
417
418/// A trait indicating that this type can be converted into another by fetching from the API.
419/// Note that [`FetchFrom`] should be implemented, in order to apply the respective blanket
420/// implementation of this trait.
421///
422/// [`FetchFrom`]: traits/trait.FetchFrom.html
423#[cfg_attr(feature = "async", async_trait)]
424pub trait FetchInto<T>: Sized {
425 /// (Sync) Attempts to request to the API and return a new instance of the type being turned
426 /// into.
427 ///
428 /// # Errors
429 ///
430 /// See the respective into-type's `fetch` implementation (or `PropFetchable`/`PropLimFetchable`
431 /// implementation).
432 ///
433 /// # Examples
434 ///
435 /// See [`FetchFrom::<T>::fetch_from`].
436 fn fetch_into(&self, client: &Client) -> Result<T>;
437
438 #[cfg(feature = "async")]
439 /// (Async) Attempts to request to the API and return a new instance of the type being turned
440 /// into.
441 ///
442 /// # Errors
443 ///
444 /// See the respective into-type's `a_fetch` implementation (or
445 /// `PropFetchable`/`PropLimFetchable` implementation).
446 ///
447 /// # Examples
448 ///
449 /// See [`FetchFrom::<T>::a_fetch_from`].
450 async fn a_fetch_into(&self, client: &Client) -> Result<T>
451 where T: 'async_trait;
452}
453
454// FetchFrom implies FetchInto
455#[cfg_attr(feature = "async", async_trait)]
456impl<T, U> FetchInto<U> for T
457 where T: Sync + Send, U: FetchFrom<T> + Sync + Send
458{
459 fn fetch_into(&self, client: &Client) -> Result<U> {
460 U::fetch_from(client, self)
461 }
462
463 #[cfg(feature = "async")]
464 async fn a_fetch_into(&self, client: &Client) -> Result<U>
465 where U: 'async_trait
466 {
467 U::a_fetch_from(client, self).await
468 }
469}
470
471// FetchFrom (and thus FetchInto) is reflexive
472#[cfg_attr(feature = "async", async_trait)]
473impl<T: Sync + Send + Clone> FetchFrom<T> for T {
474 /// (Sync) Returns a copy of the current instance when attempting to fetch from itself.
475 /// In order to re-fetch, see [`Refetchable`].
476 ///
477 /// # Errors
478 ///
479 /// Never errors; is only a [`Result`] in order to match the trait signature.
480 ///
481 /// [`Refetchable`]: trait.Refetchable.html
482 /// [`Result`]: ../error/type.Result.html
483 fn fetch_from(_: &Client, t: &T) -> Result<T> { Ok(t.to_owned()) }
484
485 /// (Async) Returns a copy of the current instance when attempting to fetch from itself.
486 /// In order to re-fetch, see [`Refetchable`].
487 ///
488 /// # Errors
489 ///
490 /// Never errors; is only a [`Result`] in order to match the trait signature.
491 ///
492 /// [`Refetchable`]: trait.Refetchable.html
493 /// [`Result`]: ../error/type.Result.html
494 #[cfg(feature = "async")]
495 async fn a_fetch_from(_: &Client, t: &T) -> Result<Self> { Ok(t.to_owned()) }
496}