Skip to main content

brawl_api/model/rankings/
clubs.rs

1//! Contains models for the `/rankings/:country_code/clubs?limit=x` Brawl Stars API endpoint.
2//! Included by the feature `"rankings"`; removing that feature will disable the usage of this module.
3
4use serde::{self, Serialize, Deserialize};
5use crate::traits::{PropLimRouteable, PropLimFetchable};
6use crate::serde::one_default;
7use std::ops::{Deref, DerefMut};
8use crate::util::fetch_route;
9use crate::error::Result;
10
11#[cfg(feature = "async")]
12use async_trait::async_trait;
13
14#[cfg(feature = "async")]
15use crate::util::a_fetch_route;
16use crate::http::Client;
17use crate::http::routes::Route;
18
19/// Represents a leaderboard of [`ClubRanking`]s - the top x clubs in a regional or global
20/// leaderboard.
21///
22/// **NOTE:** The API only allows fetching up to the top 200 clubs.
23///
24/// [`ClubRanking`]: ./struct.ClubRanking.html
25#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
26pub struct ClubLeaderboard {
27    /// The clubs in the ranking.
28    #[serde(default)]
29    pub items: Vec<ClubRanking>,
30}
31
32impl Deref for ClubLeaderboard {
33    type Target = Vec<ClubRanking>;
34
35    /// Obtain the clubs in the ranking - dereferencing returns the [`items`] field.
36    ///
37    /// # Examples
38    ///
39    /// ```rust,ignore
40    /// use brawl_api::{Client, ClubLeaderboard, traits::*};
41    ///
42    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
43    /// let client = Client::new("my auth token");
44    /// let top50clubs = ClubLeaderboard::fetch(
45    ///     &client,   // <- the client containing the auth key
46    ///     "global",  // <- the region of the leaderboard to fetch ("global" - world-wide)
47    ///     50         // <- limit of rankings to fetch (i.e. top 50)
48    /// )?;
49    ///
50    /// assert_eq!(top50clubs.items, *top50clubs);
51    ///
52    /// #     Ok(())
53    /// # }
54    ///
55    /// ```
56    ///
57    /// [`items`]: #structfield.items
58    fn deref(&self) -> &Vec<ClubRanking> {
59        &self.items
60    }
61}
62
63impl DerefMut for ClubLeaderboard {
64    /// Obtain the clubs in the ranking - dereferencing returns the [`items`] field.
65    ///
66    /// # Examples
67    ///
68    /// ```rust,ignore
69    /// use brawl_api::{Client, ClubLeaderboard, traits::*};
70    ///
71    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
72    /// let client = Client::new("my auth token");
73    /// let top50clubs = ClubLeaderboard::fetch(
74    ///     &client,   // <- the client containing the auth key
75    ///     "global",  // <- the region of the leaderboard to fetch ("global" - world-wide)
76    ///     50         // <- limit of rankings to fetch (i.e. top 50)
77    /// )?;
78    ///
79    /// assert_eq!(top50clubs.items, *top50clubs);
80    ///
81    /// #     Ok(())
82    /// # }
83    ///
84    /// ```
85    ///
86    /// [`items`]: #structfield.items
87    fn deref_mut(&mut self) -> &mut Vec<ClubRanking> {
88        &mut self.items
89    }
90}
91
92#[cfg_attr(feature = "async", async_trait)]
93impl PropLimFetchable for ClubLeaderboard {
94    type Property = str;
95    type Limit = u8;
96
97    /// (Sync) Fetches the top `limit <= 200` clubs in the regional (two-letter) `country_code`
98    /// leaderboard (or global leaderboard, if `country_code == "global"`).
99    ///
100    /// # Errors
101    ///
102    /// This function may error:
103    /// - While requesting (will return an [`Error::Request`]);
104    /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
105    /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
106    /// - While parsing incoming JSON (will return an [`Error::Json`]).
107    ///
108    /// (All of those, of course, wrapped inside an `Err`.)
109    ///
110    /// # Examples
111    ///
112    /// World-wide club leaderboard:
113    /// ```rust,ignore
114    /// use brawl_api::{ClubLeaderboard, Client, traits::PropLimFetchable};
115    ///
116    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
117    /// let client = Client::new("my auth key");
118    ///
119    /// // if the fetch is successful, then the variable below will have the global top 100 clubs
120    /// // in the 'items' field (i.e. '*top100clubs').
121    /// let top100clubs: ClubLeaderboard = ClubLeaderboard::fetch(&client, "global", 100)?;
122    ///
123    /// // get club ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
124    /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
125    /// let club1 = &top100clubs[0];
126    ///
127    /// assert_eq!(club1.rank, 1);
128    ///
129    /// #     Ok(())
130    /// # }
131    /// ```
132    ///
133    /// Regional (in this case, zimbabwean) club leaderboard:
134    /// ```rust,ignore
135    /// use brawl_api::{ClubLeaderboard, Client, traits::PropLimFetchable};
136    ///
137    /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
138    /// let client = Client::new("my auth key");
139    ///
140    /// // if the fetch is successful, then the variable below will have the top 100 zimbabwean clubs
141    /// // in the 'items' field (i.e. '*top100zwclubs').
142    /// let top100zwclubs: ClubLeaderboard = ClubLeaderboard::fetch(&client, "ZW", 100)?;
143    ///
144    /// // get club ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
145    /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
146    /// let club1 = &top100zwclubs[0];
147    ///
148    /// assert_eq!(club1.rank, 1);
149    ///
150    /// #     Ok(())
151    /// # }
152    /// ```
153    ///
154    /// [`Error::Request`]: error/enum.Error.html#variant.Request
155    /// [`Error::Status`]: error/enum.Error.html#variant.Status
156    /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
157    /// [`Error::Json`]: error/enum.Error.html#variant.Json
158    fn fetch(client: &Client, country_code: &str, limit: u8) -> Result<ClubLeaderboard> {
159        let route = ClubLeaderboard::get_route(country_code, limit);
160        fetch_route::<ClubLeaderboard>(client, &route)
161    }
162
163    /// (Async) Fetches the top `limit <= 200` clubs in the regional (two-letter) `country_code`
164    /// leaderboard (or global leaderboard, if `country_code == "global"`).
165    ///
166    /// # Errors
167    ///
168    /// This function may error:
169    /// - While requesting (will return an [`Error::Request`]);
170    /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
171    /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
172    /// - While parsing incoming JSON (will return an [`Error::Json`]).
173    ///
174    /// (All of those, of course, wrapped inside an `Err`.)
175    ///
176    /// # Examples
177    ///
178    /// World-wide club leaderboard:
179    /// ```rust,ignore
180    /// use brawl_api::{ClubLeaderboard, Client, traits::PropLimFetchable};
181    ///
182    /// let client = Client::new("my auth key");
183    ///
184    /// // if the fetch is successful, then the variable below will have the global top 100 clubs
185    /// // in the 'items' field (i.e. '*top100clubs').
186    /// let top100clubs: ClubLeaderboard = ClubLeaderboard::a_fetch(&client, "global", 100).await?;
187    ///
188    /// // get club ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
189    /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
190    /// let club1 = &top100clubs[0];
191    ///
192    /// # Ok::<(), Box<dyn ::std::error::Error>>(())
193    /// ```
194    ///
195    /// Regional (in this case, zimbabwean) club leaderboard:
196    /// ```rust,ignore
197    /// use brawl_api::{ClubLeaderboard, Client, traits::PropLimFetchable};
198    ///
199    /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
200    /// let client = Client::new("my auth key");
201    ///
202    /// // if the fetch is successful, then the variable below will have the top 100 zimbabwean clubs
203    /// // in the 'items' field (i.e. '*top100zwclubs').
204    /// let top100zwclubs: ClubLeaderboard = ClubLeaderboard::a_fetch(&client, "ZW", 100).await?;
205    ///
206    /// // get club ranked #1. The items are usually sorted (i.e. rank 1 on index [0], rank 2
207    /// // on index [1] etc.), but, to make the program absolutely safe, might want to .sort()
208    /// let club1 = &top100zwclubs[0];
209    ///
210    /// assert_eq!(club1.rank, 1);
211    ///
212    /// #     Ok(())
213    /// # }
214    /// ```
215    ///
216    /// [`Error::Request`]: error/enum.Error.html#variant.Request
217    /// [`Error::Status`]: error/enum.Error.html#variant.Status
218    /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
219    /// [`Error::Json`]: error/enum.Error.html#variant.Json
220    #[cfg(feature="async")]
221    async fn a_fetch(
222        client: &Client, country_code: &'async_trait str, limit: u8
223    ) -> Result<ClubLeaderboard>
224        where Self: 'async_trait,
225              Self::Property: 'async_trait,
226    {
227        let route = ClubLeaderboard::get_route(&country_code, limit);
228        a_fetch_route::<ClubLeaderboard>(client, &route).await
229    }
230}
231
232impl PropLimRouteable for ClubLeaderboard {
233    type Property = str;
234    type Limit = u8;
235
236    /// Get the route for fetching the top `limit` clubs in the regional `country_code`
237    /// leaderboard (or global, if `country_code == "global"`).
238    fn get_route(country_code: &str, limit: u8) -> Route {
239        Route::ClubRankings {
240            country_code: country_code.to_owned(),
241            limit
242        }
243    }
244}
245
246/// Represents a club's ranking, based on a regional or global leaderboard.
247/// To obtain the club's full data (a [`Club`] instance), see [`Club::fetch_from`].
248///
249/// [`Club`]: ../clubs/club/struct.Club.html
250/// [`Club::fetch_from`]: ../clubs/club/struct.Club.html#method.fetch_from
251#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct ClubRanking {
254    /// The club's tag.
255    #[serde(default)]
256    pub tag: String,
257
258    /// The club's name.
259    #[serde(default)]
260    pub name: String,
261
262    /// The club's current trophies.
263    #[serde(default)]
264    pub trophies: usize,
265
266    /// The club's current rank in the leaderboard.
267    #[serde(default = "one_default")]
268    pub rank: u8,
269
270    /// The amount of members in this club.
271    #[serde(default)]
272    pub member_count: usize,
273}
274
275impl Default for ClubRanking {
276    /// Returns an instance of `ClubRanking` with initial values.
277    ///
278    /// # Examples
279    ///
280    /// ```rust
281    /// use brawl_api::ClubRanking;
282    ///
283    /// assert_eq!(
284    ///     ClubRanking::default(),
285    ///     ClubRanking {
286    ///         tag: String::from(""),
287    ///         name: String::from(""),
288    ///         trophies: 0,
289    ///         rank: 1,
290    ///         member_count: 0,
291    ///     }
292    /// );
293    /// ```
294    fn default() -> ClubRanking {
295        ClubRanking {
296            tag: String::from(""),
297            name: String::from(""),
298            trophies: 0,
299            rank: 1,
300            member_count: 0,
301        }
302    }
303}
304
305impl PartialOrd for ClubRanking {
306    /// Compares and determines which `ClubRanking` has a higher rank (i.e., smaller rank number).
307    ///
308    /// # Examples
309    ///
310    /// ```rust
311    /// use brawl_api::{ClubLeaderboard, traits::*};
312    ///
313    /// # use brawl_api::ClubRanking;
314    ///
315    /// let leaderboard: ClubLeaderboard;
316    ///
317    /// # leaderboard = ClubLeaderboard {
318    /// #     items: vec![
319    /// #         ClubRanking { rank: 1, ..ClubRanking::default() },  // #1 position
320    /// #         ClubRanking { rank: 2, ..ClubRanking::default() },  // #2 position
321    /// #     ]
322    /// # };
323    ///
324    /// // after fetching the leaderboard (see examples in ClubLeaderboard::fetch)...
325    ///
326    /// let (club_1, club_2) = (&leaderboard[0], &leaderboard[1]);
327    /// // generally, the first club is the one with 'rank' field equal to 1 and the second,
328    /// // 'rank' field equal to 2, so assume this is true (the API generally sends them sorted,
329    /// // but, to guarantee strictness, one can 'leaderboard.sort()'.
330    ///
331    /// assert!(club_1 > club_2)  // smaller rank number = higher position
332    /// ```
333    ///
334    /// [`ClubLeaderboard`]: struct.ClubLeaderboard.html
335    fn partial_cmp(&self, other: &ClubRanking) -> Option<::std::cmp::Ordering> {
336        Some(self.cmp(other))
337    }
338}
339
340impl Ord for ClubRanking {
341    /// Compares and determines which `ClubRanking` has a higher rank (i.e., smaller rank number).
342    ///
343    /// # Examples
344    ///
345    /// ```rust
346    /// use brawl_api::{ClubLeaderboard, traits::*};
347    ///
348    /// # use brawl_api::ClubRanking;
349    ///
350    /// let leaderboard: ClubLeaderboard;
351    ///
352    /// # leaderboard = ClubLeaderboard {
353    /// #     items: vec![
354    /// #         ClubRanking { rank: 1, ..ClubRanking::default() },  // #1 position
355    /// #         ClubRanking { rank: 2, ..ClubRanking::default() },  // #2 position
356    /// #     ]
357    /// # };
358    ///
359    /// // after fetching the leaderboard (see examples in ClubLeaderboard::fetch)...
360    ///
361    /// let (club_1, club_2) = (&leaderboard[0], &leaderboard[1]);
362    /// // generally, the first club is the one with 'rank' field equal to 1 and the second,
363    /// // 'rank' field equal to 2, so assume this is true (the API generally sends them sorted,
364    /// // but, to guarantee strictness, one can 'leaderboard.sort()'.
365    ///
366    /// assert!(club_1 > club_2)  // smaller rank number = higher position
367    /// ```
368    ///
369    /// [`ClubLeaderboard`]: struct.ClubLeaderboard.html
370    fn cmp(&self, other: &ClubRanking) -> ::std::cmp::Ordering {
371        self.rank.cmp(&other.rank).reverse()
372    }
373}
374
375///////////////////////////////////   tests   ///////////////////////////////////
376
377#[cfg(test)]
378mod tests {
379    use serde_json;
380    use super::{ClubLeaderboard, ClubRanking};
381    use crate::error::Error;
382
383    /// Tests for ClubLeaderboard deserialization from API-provided JSON.
384    #[test]
385    fn rankings_clubs_deser() -> Result<(), Box<dyn ::std::error::Error>> {
386
387        let rc_json_s = r##"{
388  "items": [
389    {
390      "tag": "#AAAAAAAAA",
391      "name": "Club",
392      "trophies": 30000,
393      "rank": 1,
394      "memberCount": 50
395    },
396    {
397      "tag": "#EEEEEEE",
398      "name": "Also Club",
399      "trophies": 25000,
400      "rank": 2,
401      "memberCount": 30
402    },
403    {
404      "tag": "#QQQQQQQ",
405      "name": "Clubby Club",
406      "trophies": 23000,
407      "rank": 3,
408      "memberCount": 25
409    },
410    {
411      "tag": "#55555553Q",
412      "name": "Not a valid club",
413      "trophies": 20000,
414      "rank": 4,
415      "memberCount": 10
416    }
417  ]
418}"##;
419
420        let c_leaders = serde_json::from_str::<ClubLeaderboard>(rc_json_s)
421            .map_err(Error::Json)?;
422
423        assert_eq!(
424            c_leaders,
425            ClubLeaderboard {
426                items: vec![
427                    ClubRanking {
428                        tag: String::from("#AAAAAAAAA"),
429                        name: String::from("Club"),
430                        member_count: 50,
431                        trophies: 30000,
432                        rank: 1,
433                    },
434                    ClubRanking {
435                        tag: String::from("#EEEEEEE"),
436                        name: String::from("Also Club"),
437                        member_count: 30,
438                        trophies: 25000,
439                        rank: 2,
440                    },
441                    ClubRanking {
442                        tag: String::from("#QQQQQQQ"),
443                        name: String::from("Clubby Club"),
444                        member_count: 25,
445                        trophies: 23000,
446                        rank: 3,
447                    },
448                    ClubRanking {
449                        tag: String::from("#55555553Q"),
450                        name: String::from("Not a valid club"),
451                        member_count: 10,
452                        trophies: 20000,
453                        rank: 4,
454                    }
455                ]
456            }
457        );
458
459        Ok(())
460    }
461}