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}