brawl_api/model/players/
player.rs

1//! Models for the `/players/:tag` API endpoint.
2//! Included by the feature `"players"`; removing that feature will disable the usage of this module.
3
4use serde::{self, Serialize, Deserialize};
5
6
7#[cfg(feature = "async")]
8
9
10#[cfg(feature = "async")]
11use crate::util::a_fetch_route;
12
13#[cfg(feature = "async")]
14use async_trait::async_trait;
15
16use crate::traits::{FetchFrom, PropFetchable, GetFetchProp};
17use crate::error::{Result};
18
19#[cfg(feature = "clubs")]
20use super::super::clubs::ClubMember;
21
22use crate::http::Client;
23use crate::http::routes::Route;
24use crate::util::{auto_hashtag, fetch_route};
25use crate::serde::{deserialize_number_from_string, one_default, oxffffff_default};
26
27use super::super::common::StarPower;
28
29use super::battlelog::{BattlePlayer};
30
31#[cfg(feature = "rankings")]
32use crate::PlayerRanking;
33
34/// A struct representing a Brawl Stars player, with all of its data.
35/// Use [`Player::fetch`] to fetch one based on tag. (Make sure the [`PropFetchable`] trait
36/// is imported - in general, it is recommended to **at least** `use brawl_api::traits::*`, or,
37/// even, `use brawl_api::prelude::*` to bring the models into scope as well.)
38///
39/// [`PropFetchable`]: traits/trait.PropFetchable.html
40/// [`Player::fetch`]: ./struct.Player.html#method.fetch
41#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Player {
44
45    /// The club the Player is in (as a [`PlayerClub`] instance), or None if none.
46    ///
47    /// [`PlayerClub`]: ./struct.PlayerClub.html
48    #[serde(default)]
49    pub club: Option<PlayerClub>,
50
51    /// Whether or not the Player was qualified from the Championship challenge (2020).
52    #[serde(default = "false_default")]
53    pub is_qualified_from_championship_challenge: bool,
54
55    /// Amount of 3v3 victories the Player has earned.
56    #[serde(rename = "3vs3Victories")]
57    pub tvt_victories: usize,
58
59    /// The player's tag. **Note: this includes the initial '#'.**
60    #[serde(default)]
61    pub tag: String,
62
63    /// The player's name.
64    #[serde(default)]
65    pub name: String,
66
67    /// The player's current trophies.
68    #[serde(default)]  // zero
69    pub trophies: usize,
70
71    /// The player's highest trophies amount.
72    #[serde(default)]  // zero
73    pub highest_trophies: usize,
74
75    /// The player's experience level.
76    #[serde(default = "one_default")]
77    pub exp_level: usize,
78
79    /// The player's experience points.
80    #[serde(default)]  // zero
81    pub exp_points: usize,
82
83    /// The player's current power play points.
84    #[serde(default)]  // zero
85    pub power_play_points: usize,
86
87    /// The player's highest power play points.
88    #[serde(default)]  // zero
89    pub highest_power_play_points: usize,
90
91    /// The player's victories in solo showdown (how many times ranked #1).
92    #[serde(default)]  // zero
93    pub solo_victories: usize,
94
95    /// The player's victories in duo showdown (how many times ranked #1).
96    #[serde(default)]  // zero
97    pub duo_victories: usize,
98
99    /// The player's best Robo Rumble time, in seconds.
100    #[serde(default)]  // zero
101    pub best_robo_rumble_time: usize,
102
103    /// The player's best time as a Big Brawler, in seconds.
104    #[serde(default)]  // zero
105    pub best_time_as_big_brawler: usize,
106
107    /// The player's brawlers.
108    #[serde(default)]
109    pub brawlers: Vec<PlayerBrawlerStat>,
110
111    /// The player's name color, as an integer (Default is 0xffffff = 16777215 - this is used
112    /// when the data is not available).
113    #[serde(default = "oxffffff_default")]
114    #[serde(deserialize_with = "deserialize_number_from_string")]  // parse num
115    pub name_color: u64,
116}
117fn false_default() -> bool { false }
118
119impl Default for Player {
120    
121    /// Initializes a Player instance with default values for each field.
122    fn default() -> Player {
123        Player {
124            club: None,
125
126            is_qualified_from_championship_challenge: false,
127
128            tvt_victories: 0,
129
130            tag: String::from(""),
131
132            name: String::from(""),
133
134            trophies: 0,
135
136            highest_trophies: 0,
137
138            exp_level: 1,
139
140            exp_points: 0,
141
142            power_play_points: 0,
143
144            highest_power_play_points: 0,
145
146            solo_victories: 0,
147
148            duo_victories: 0,
149
150            best_robo_rumble_time: 0,
151
152            best_time_as_big_brawler: 0,
153
154            brawlers: Vec::<PlayerBrawlerStat>::new(),
155
156            name_color: 0xff_ff_ff,
157        }
158    }
159}
160
161impl GetFetchProp for Player {
162    type Property = str;
163
164    fn get_fetch_prop(&self) -> &str { &*self.tag }
165
166    fn get_route(tag: &str) -> Route { Route::Player(auto_hashtag(tag)) }
167}
168
169#[cfg_attr(feature = "async", async_trait)]
170impl PropFetchable for Player {
171    type Property = str;
172
173    /// (Sync) Fetches a player from its tag.
174    ///
175    /// # Errors
176    ///
177    /// This function may error:
178    /// - While requesting (will return an [`Error::Request`]);
179    /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
180    /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
181    /// - While parsing incoming JSON (will return an [`Error::Json`]).
182    ///
183    /// (All of those, of course, wrapped inside an `Err`.)
184    ///
185    /// # Examples
186    ///
187    /// ```rust,ignore
188    /// use brawl_api::{Client, Player, traits::*};
189    ///
190    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
191    /// let my_client = Client::new("my auth token");
192    /// let player = Player::fetch(&my_client, "#PLAYERTAGHERE")?;
193    /// // now the data for the given player is available for use
194    ///
195    /// # Ok(())
196    /// # }
197    /// ```
198    ///
199    /// [`Error::Request`]: error/enum.Error.html#variant.Request
200    /// [`Error::Status`]: error/enum.Error.html#variant.Status
201    /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
202    /// [`Error::Json`]: error/enum.Error.html#variant.Json
203    fn fetch(client: &Client, tag: &str) -> Result<Player> {
204        let route = Self::get_route(tag);
205        fetch_route::<Player>(client, &route)
206    }
207
208    /// (Async) Fetches a player from its tag.
209    ///
210    /// # Errors
211    ///
212    /// This function may error:
213    /// - While requesting (will return an [`Error::Request`]);
214    /// - After receiving a bad status code (API or other error - returns an [`Error::Status`]);
215    /// - After a ratelimit is indicated by the API, while also specifying when it is lifted ([`Error::Ratelimited`]);
216    /// - While parsing incoming JSON (will return an [`Error::Json`]).
217    ///
218    /// (All of those, of course, wrapped inside an `Err`.)
219    ///
220    /// # Examples
221    ///
222    /// ```rust,ignore
223    /// use brawl_api::{Client, Player, traits::*};
224    ///
225    /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
226    /// let my_client = Client::new("my auth token");
227    /// let player = Player::a_fetch(&my_client, "#PLAYERTAGHERE").await?;
228    /// // now the data for the given player is available for use
229    ///
230    /// # Ok(())
231    /// # }
232    /// ```
233    ///
234    /// [`Error::Request`]: error/enum.Error.html#variant.Request
235    /// [`Error::Status`]: error/enum.Error.html#variant.Status
236    /// [`Error::Ratelimited`]: error/enum.Error.html#variant.Ratelimited
237    /// [`Error::Json`]: error/enum.Error.html#variant.Json
238    #[cfg(feature="async")]
239    async fn a_fetch(client: &Client, tag: &'async_trait str) -> Result<Player>
240        where Self: 'async_trait,
241              Self::Property: 'async_trait,
242    {
243        let route = Player::get_route(&tag);
244        a_fetch_route::<Player>(client, &route).await
245    }
246}
247
248#[cfg_attr(feature = "async", async_trait)]
249#[cfg(feature = "clubs")]
250impl FetchFrom<ClubMember> for Player {
251    /// (Sync) Fetches a `Player` instance, given a preexisting `ClubMember` instance.
252    ///
253    /// # Errors
254    ///
255    /// See [`Player::fetch`].
256    ///
257    /// # Examples
258    ///
259    /// ```rust,ignore
260    /// use brawl_api::{Client, Player, Club, traits::*};
261    ///
262    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
263    /// let my_client = Client::new("my auth token");
264    /// let club = Club::fetch(&my_client, "#CLUB_TAG_HERE")?;
265    /// let some_member = &club.members[0];
266    /// let some_player = Player::fetch_from(&my_client, some_member)?;
267    /// // now `some_member`'s full data, as a Player, is available for use.
268    ///
269    /// # Ok(())
270    /// # }
271    /// ```
272    ///
273    /// [`Player::fetch`]: struct.Player.html#method.fetch
274    fn fetch_from(client: &Client, member: &ClubMember) -> Result<Player> {
275        Player::fetch(client, &member.tag)
276    }
277
278    /// (Async) Fetches a `Player` instance, given a preexisting `ClubMember` instance.
279    ///
280    /// # Errors
281    ///
282    /// See [`Player::fetch`].
283    ///
284    /// # Examples
285    ///
286    /// ```rust,ignore
287    /// use brawl_api::{Client, Player, Club, traits::*};
288    ///
289    /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
290    /// let my_client = Client::new("my auth token");
291    /// let club = Club::a_fetch(&my_client, "#CLUB_TAG_HERE").await?;
292    /// let some_member = &club.members[0];
293    /// let some_player = Player::a_fetch_from(&my_client, some_member).await?;
294    /// // now `some_member`'s full data, as a Player, is available for use.
295    ///
296    /// # Ok(())
297    /// # }
298    /// ```
299    ///
300    /// [`Player::fetch`]: struct.Player.html#method.fetch
301    #[cfg(feature = "async")]
302    async fn a_fetch_from(client: &Client, member: &ClubMember) -> Result<Player> {
303        Player::a_fetch(client, &member.tag).await
304    }
305}
306
307#[cfg_attr(feature = "async", async_trait)]
308impl FetchFrom<BattlePlayer> for Player {
309    /// (Async) Fetches a `Player` instance, given a preexisting `BattlePlayer` instance.
310    ///
311    /// # Errors
312    ///
313    /// See [`Player::fetch`].
314    ///
315    /// # Examples
316    ///
317    /// ```rust,ignore
318    /// use brawl_api::{
319    ///     Client, Player, BattleLog, Battle, BattleResultInfo, BattlePlayer,
320    ///     traits::*
321    /// };
322    ///
323    /// # fn main() -> Result<(), Box<dyn ::std::error::Error>> {
324    /// let my_client = Client::new("my auth token");
325    /// let battlelog = BattleLog::fetch(&my_client, "#PLAYER_TAG_HERE")?;
326    /// let most_recent_battle: Option<&Battle> = battlelog.get(0);
327    ///
328    /// if let Some(battle) = most_recent_battle {
329    ///     if let Some(ref teams) = &battle.result.teams {
330    ///         let some_b_player: &BattlePlayer = &teams[0][0];
331    ///         let some_player = Player::fetch_from(&my_client, some_b_player)?;
332    ///         // now `some_b_player`'s full data, as a Player, is available for use.
333    ///     }
334    /// }
335    ///
336    /// # Ok(())
337    /// # }
338    /// ```
339    ///
340    /// [`Player::fetch`]: struct.Player.html#method.fetch
341    fn fetch_from(client: &Client, b_player: &BattlePlayer) -> Result<Player> {
342        Player::fetch(client, &b_player.tag)
343    }
344
345    /// (Async) Fetches a `Player` instance, given a preexisting `BattlePlayer` instance.
346    ///
347    /// # Errors
348    ///
349    /// See [`Player::fetch`].
350    ///
351    /// # Examples
352    ///
353    /// ```rust,ignore
354    /// use brawl_api::{
355    ///     Client, Player, BattleLog, Battle, BattleResultInfo, BattlePlayer,
356    ///     traits::*
357    /// };
358    ///
359    /// # async fn main() -> Result<(), Box<dyn ::std::error::Error>> {
360    /// let my_client = Client::new("my auth token");
361    /// let battlelog = BattleLog::a_fetch(&my_client, "#PLAYER_TAG_HERE").await?;
362    /// let most_recent_battle: Option<&Battle> = battlelog.get(0);
363    ///
364    /// if let Some(battle) = most_recent_battle {
365    ///     if let Some(ref teams) = &battle.result.teams {
366    ///         let some_b_player: &BattlePlayer = &teams[0][0];
367    ///         let some_player = Player::a_fetch_from(&my_client, some_b_player).await?;
368    ///         // now `some_b_player`'s full data, as a Player, is available for use.
369    ///     }
370    /// }
371    ///
372    /// # Ok(())
373    /// # }
374    /// ```
375    ///
376    /// [`Player::fetch`]: struct.Player.html#method.fetch
377    #[cfg(feature = "async")]
378    async fn a_fetch_from(client: &Client, b_player: &BattlePlayer) -> Result<Player> {
379        Player::a_fetch(client, &b_player.tag).await
380    }
381}
382
383#[cfg_attr(feature = "async", async_trait)]
384#[cfg(feature = "rankings")]
385impl FetchFrom<PlayerRanking> for Player {
386
387    /// (Sync) Fetches a `Player` using data from a [`PlayerRanking`] object.
388    ///
389    /// [`PlayerRanking`]: ../../rankings/players/struct.PlayerRanking.html
390    fn fetch_from(client: &Client, p_ranking: &PlayerRanking) -> Result<Player> {
391        Player::fetch(client, &p_ranking.tag)
392    }
393
394    /// (Async) Fetches a `Player` using data from a [`PlayerRanking`] object.
395    ///
396    /// [`PlayerRanking`]: ../../rankings/players/struct.PlayerRanking.html
397    #[cfg(feature = "async")]
398    async fn a_fetch_from(client: &Client, p_ranking: &PlayerRanking) -> Result<Player> {
399        Player::a_fetch(client, &p_ranking.tag).await
400    }
401}
402
403
404/// A struct representing a club obtained from [`Player.club`].
405/// Note that it does not contain all of a club's information.
406/// For that, use [`Club::fetch_from`] (fetches the full Club).
407///
408/// [`Player.club`]: ./struct.Player.html#structfield.club
409/// [`Club::fetch_from`]: ../clubs/struct.Club.html#method.fetch_from
410#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
411pub struct PlayerClub {
412
413    /// The club's tag.
414    #[serde(default)]
415    pub tag: String,
416
417    /// The club's name
418    #[serde(default)]
419    pub name: String
420}
421
422impl Default for PlayerClub {
423
424    /// Returns an instance of `PlayerClub` with initial values.
425    ///
426    /// # Examples
427    ///
428    /// ```rust
429    /// use brawl_api::PlayerClub;
430    ///
431    /// assert_eq!(
432    ///     PlayerClub::default(),
433    ///     PlayerClub {
434    ///         tag: String::from(""),
435    ///         name: String::from(""),
436    ///     }
437    /// );
438    /// ```
439    fn default() -> PlayerClub {
440        PlayerClub {
441            tag: String::from(""),
442            name: String::from("")
443        }
444    }
445}
446
447/// A struct containing information about a player's brawler (see [`Player.brawlers`]).
448///
449/// [`Player.brawlers`]: ./struct.Player.html#structfield.brawlers
450#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct PlayerBrawlerStat {
453
454    /// A vector containing the brawler's star powers (represented by [`StarPower`]),
455    /// if any (otherwise empty vector).
456    ///
457    /// [`StarPower`]: ./struct.StarPower.html
458    #[serde(default)]
459    pub star_powers: Vec<StarPower>,
460
461    /// The brawler's id (an arbitrary number).
462    #[serde(default)]  // zero
463    pub id: usize,
464
465    /// The brawler's rank.
466    #[serde(default = "one_default")]
467    pub rank: u16,
468
469    /// The brawler's trophies.
470    #[serde(default)]
471    pub trophies: usize,
472
473    /// The brawler's highest trophies amount.
474    #[serde(default)]  // zero
475    pub highest_trophies: usize,
476
477    /// The brawler's power (1-10).
478    #[serde(default = "one_default")]
479    pub power: u8,
480
481    /// The brawler's name.
482    #[serde(default)]
483    pub name: String,
484}
485
486impl Default for PlayerBrawlerStat {
487    
488    /// Initializes a new BrawlerStat instance, with default values.
489    fn default() -> PlayerBrawlerStat {
490        PlayerBrawlerStat {
491            star_powers: vec![],
492            id: 0,
493            rank: 1,
494            trophies: 0,
495            highest_trophies: 0,
496            power: 1,
497            name: String::from(""),
498        }
499    }
500}
501
502///////////////////////////////////   tests   ///////////////////////////////////
503
504#[cfg(test)]
505mod tests {
506    use std::result::Result as StdResult;
507    use super::{Player, PlayerClub, PlayerBrawlerStat, StarPower};
508    use crate::error::Error as BrawlError;
509    use serde_json;
510
511    /// Tests for player deserialization from API-provided JSON.
512    #[test]
513    fn players_deser() -> StdResult<(), Box<dyn ::std::error::Error>> {
514        let player_json_s = r##"{
515  "tag": "#CCCCCC",
516  "name": "User",
517  "nameColor": "0xff1ba5f5",
518  "trophies": 13370,
519  "highestTrophies": 30000,
520  "powerPlayPoints": 200,
521  "highestPowerPlayPoints": 900,
522  "expLevel": 100,
523  "expPoints": 70000,
524  "isQualifiedFromChampionshipChallenge": false,
525  "3vs3Victories": 3333,
526  "soloVictories": 999,
527  "duoVictories": 333,
528  "bestRoboRumbleTime": 350,
529  "bestTimeAsBigBrawler": 250,
530  "club": {
531    "tag": "#888888",
532    "name": "Club"
533  },
534  "brawlers": [
535    {
536      "id": 16000000,
537      "name": "SHELLY",
538      "power": 9,
539      "rank": 20,
540      "trophies": 500,
541      "highestTrophies": 549,
542      "starPowers": []
543    },
544    {
545      "id": 16000001,
546      "name": "COLT",
547      "power": 10,
548      "rank": 18,
549      "trophies": 420,
550      "highestTrophies": 440,
551      "starPowers": [
552        {
553          "id": 23000138,
554          "name": "Magnum Special"
555        },
556        {
557          "id": 23000077,
558          "name": "Slick Boots"
559        }
560      ]
561    }
562  ]
563}"##;
564        let player: Player = serde_json::from_str::<Player>(player_json_s)
565            .map_err(BrawlError::Json)?;
566
567        assert_eq!(
568            player,
569            Player {
570                tag: String::from("#CCCCCC"),
571                name: String::from("User"),
572                name_color: 0xff1ba5f5,
573                trophies: 13370,
574                highest_trophies: 30000,
575                power_play_points: 200,
576                highest_power_play_points: 900,
577                exp_level: 100,
578                exp_points: 70000,
579                is_qualified_from_championship_challenge: false,
580                tvt_victories: 3333,
581                solo_victories: 999,
582                duo_victories: 333,
583                best_robo_rumble_time: 350,
584                best_time_as_big_brawler: 250,
585                club: Some(PlayerClub {
586                    tag: String::from("#888888"),
587                    name: String::from("Club")
588                }),
589                brawlers: vec![
590                    PlayerBrawlerStat {
591                        id: 16000000,
592                        name: String::from("SHELLY"),
593                        power: 9,
594                        rank: 20,
595                        trophies: 500,
596                        highest_trophies: 549,
597                        star_powers: vec![]
598                    },
599                    PlayerBrawlerStat {
600                        id: 16000001,
601                        name: String::from("COLT"),
602                        power: 10,
603                        rank: 18,
604                        trophies: 420,
605                        highest_trophies: 440,
606                        star_powers: vec![
607                            StarPower {
608                                id: 23000138,
609                                name: String::from("Magnum Special")
610                            },
611                            StarPower {
612                                id: 23000077,
613                                name: String::from("Slick Boots")
614                            }
615                        ]
616                    },
617                ]
618            }
619        );
620        Ok(())
621    }
622}
623