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