r6tracker/types/
overwolf.rs1pub mod lifetime_stats;
3
4pub use self::lifetime_stats::LifetimeStats;
5use std::collections::HashMap;
6use url::Url;
7
8#[derive(Debug)]
11pub struct InvalidOverwolfResponseError(pub String);
12
13impl std::error::Error for InvalidOverwolfResponseError {}
14
15impl std::fmt::Display for InvalidOverwolfResponseError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "the overwolf response was invalid ({})", self.0)
18 }
19}
20
21#[derive(Debug)]
23pub enum OverwolfResponse<T> {
24 Valid(T),
26
27 Invalid(String),
29}
30
31impl<'de, T> serde::Deserialize<'de> for OverwolfResponse<T>
32where
33 T: serde::Deserialize<'de>,
34{
35 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36 where
37 D: serde::Deserializer<'de>,
38 {
39 let mut map = serde_json::Map::deserialize(deserializer)?;
40
41 let success = map
42 .remove("success")
43 .ok_or_else(|| serde::de::Error::missing_field("success"))
44 .map(serde::Deserialize::deserialize)?
45 .map_err(serde::de::Error::custom)?;
46 let rest = serde_json::Value::Object(map);
47
48 if success {
49 T::deserialize(rest)
50 .map(OverwolfResponse::Valid)
51 .map_err(serde::de::Error::custom)
52 } else {
53 #[derive(serde::Deserialize)]
54 struct ErrorReason {
55 reason: String,
56 }
57
58 ErrorReason::deserialize(rest)
59 .map(|e| OverwolfResponse::Invalid(e.reason))
60 .map_err(serde::de::Error::custom)
61 }
62 }
63}
64
65impl<T> OverwolfResponse<T> {
66 pub fn into_result(self) -> Result<T, InvalidOverwolfResponseError> {
68 match self {
69 Self::Valid(data) => Ok(data),
70 Self::Invalid(reason) => Err(InvalidOverwolfResponseError(reason)),
71 }
72 }
73
74 pub fn get_valid(&self) -> Option<&T> {
76 match &self {
77 Self::Valid(data) => Some(data),
78 Self::Invalid(_) => None,
79 }
80 }
81
82 pub fn take_valid(self) -> Option<T> {
84 match self {
85 Self::Valid(data) => Some(data),
86 Self::Invalid(_) => None,
87 }
88 }
89}
90
91#[derive(Debug, serde::Deserialize, serde::Serialize)]
93pub struct OverwolfPlayer {
94 #[serde(rename = "playerId")]
96 pub player_id: String,
97
98 pub name: String,
100
101 pub avatar: Url,
103
104 pub level: u64,
106
107 #[serde(rename = "isPremium")]
109 pub is_premium: bool,
110
111 #[serde(rename = "suspectedCheater")]
113 pub suspected_cheater: bool,
114
115 #[serde(rename = "currentSeason")]
117 pub current_season: u64,
118
119 #[serde(rename = "currentSeasonBestRegion")]
121 pub current_season_best_region: Option<OverwolfSeason>,
122
123 #[serde(rename = "lifetimeStats")]
125 pub lifetime_stats: LifetimeStats,
126
127 pub seasons: Vec<OverwolfSeason>,
129
130 pub operators: Vec<OverwolfOperator>,
132
133 #[serde(rename = "seasonalOperators")]
135 pub seasonal_operators: Option<SeasonalOperators>,
136
137 #[serde(flatten)]
139 pub unknown: HashMap<String, serde_json::Value>,
140}
141
142impl OverwolfPlayer {
143 pub fn get_current_casual_season(&self) -> Option<&OverwolfSeason> {
145 self.seasons
146 .iter()
147 .find(|season| !season.is_ranked && season.season == self.current_season)
148 }
149
150 pub fn iter_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
152 self.seasons.iter().filter(|season| season.is_ranked)
153 }
154
155 pub fn iter_placed_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
157 self.iter_ranked_seasons()
158 .filter(|season| season.current_rank.rank_tier != 0)
159 }
160
161 pub fn get_max_season(&self) -> Option<&OverwolfSeason> {
163 self.iter_placed_ranked_seasons()
164 .max_by_key(|season| season.max_mmr)
165 }
166
167 pub fn get_lifetime_ranked_kd(&self) -> Option<f64> {
169 let mut length = 0;
170 let mut sum = 0.0;
171 for season in self.iter_placed_ranked_seasons() {
172 sum += season.kd;
173 length += 1;
174 }
175
176 if length == 0 {
177 None
178 } else {
179 Some(sum / (length as f64))
180 }
181 }
182
183 pub fn get_lifetime_ranked_win_pct(&self) -> Option<f64> {
185 let mut length = 0;
186 let mut sum = 0.0f64;
187 for season in self.iter_placed_ranked_seasons() {
188 sum += season.win_pct;
189 length += 1;
190 }
191
192 if length == 0 {
193 None
194 } else {
195 Some(sum / (length as f64))
196 }
197 }
198}
199
200#[derive(Debug, serde::Deserialize, serde::Serialize)]
202pub struct OverwolfSeason {
203 #[serde(rename = "rankName")]
205 pub rank_name: String,
206
207 pub img: Url,
209
210 pub season: u64,
212
213 pub region: String,
215
216 #[serde(rename = "regionLabel")]
218 pub region_label: String,
219
220 pub mmr: u64,
222
223 #[serde(rename = "winPct")]
225 pub win_pct: f64,
226
227 pub wins: u64,
229
230 pub kd: f64,
232
233 pub kills: u64,
235
236 pub matches: u64,
238
239 #[serde(rename = "mmrChange")]
241 pub mmr_change: Option<i64>,
242
243 #[serde(rename = "isRanked")]
245 pub is_ranked: bool,
246
247 #[serde(rename = "maxMmr")]
249 pub max_mmr: u64,
250
251 #[serde(rename = "currentRank")]
253 pub current_rank: OverwolfRank,
254
255 #[serde(rename = "maxRank")]
257 pub max_rank: OverwolfRank,
258
259 #[serde(rename = "prevRank")]
261 pub prev_rank: Option<OverwolfRank>,
262
263 #[serde(rename = "nextRank")]
265 pub next_rank: Option<OverwolfRank>,
266
267 #[serde(flatten)]
269 pub unknown: HashMap<String, serde_json::Value>,
270}
271
272#[derive(Debug, serde::Deserialize, serde::Serialize)]
274pub struct OverwolfRank {
275 #[serde(rename = "rankTier")]
277 pub rank_tier: u64,
278
279 pub mmr: u64,
281
282 #[serde(rename = "rankIcon")]
284 pub rank_icon: Url,
285
286 #[serde(rename = "rankName")]
288 pub rank_name: String,
289
290 #[serde(flatten)]
292 pub unknown: HashMap<String, serde_json::Value>,
293}
294
295#[derive(Debug, serde::Deserialize, serde::Serialize)]
297pub struct OverwolfOperator {
298 pub name: String,
300
301 pub img: Url,
303
304 #[serde(rename = "isAttack")]
306 pub is_attack: bool,
307
308 #[serde(rename = "isTopOperator")]
310 pub is_top_operator: bool,
311
312 #[serde(rename = "winpct")]
314 pub win_pct: f64,
315
316 pub wins: u64,
318
319 pub kd: f64,
321
322 pub kills: u64,
324
325 #[serde(rename = "timePlayedDisplay")]
327 pub time_played_display: String,
328
329 #[serde(rename = "timePlayed")]
331 pub time_played: u64,
332
333 #[serde(flatten)]
335 pub unknown: HashMap<String, serde_json::Value>,
336}
337
338#[derive(Debug, serde::Deserialize, serde::Serialize)]
340pub struct SeasonalOperators {
341 pub operators: Vec<OverwolfOperator>,
343
344 #[serde(rename = "startedTracking")]
346 pub started_tracking: String,
347
348 #[serde(flatten)]
350 pub unknown: HashMap<String, serde_json::Value>,
351}
352
353#[cfg(test)]
354mod test {
355 use super::*;
356
357 const OVERWOLF_PLAYER_1: &str = include_str!("../../test_data/overwolf_player_1.json");
358 const OVERWOLF_PLAYER_2: &str = include_str!("../../test_data/overwolf_player_2.json");
359 const INVALID_OVERWOLF_RESPONSE: &str =
360 include_str!("../../test_data/invalid_overwolf_response.json");
361 const SMACK_ASH_OVERWOLF: &str = include_str!("../../test_data/smack_ash_overwolf.json");
362
363 #[test]
364 fn parse_overwolf_player_1() {
365 let res: OverwolfResponse<OverwolfPlayer> =
366 serde_json::from_str(OVERWOLF_PLAYER_1).unwrap();
367 dbg!(res.take_valid().unwrap());
368 }
369
370 #[test]
371 fn parse_overwolf_player_2() {
372 let res: OverwolfResponse<OverwolfPlayer> =
373 serde_json::from_str(OVERWOLF_PLAYER_2).unwrap();
374 let valid = res.take_valid().unwrap();
375 dbg!(&valid);
376 dbg!(&valid.get_max_season());
377 }
378
379 #[test]
380 fn parse_smack_ash_overwolf() {
381 let res: OverwolfResponse<OverwolfPlayer> =
382 serde_json::from_str(SMACK_ASH_OVERWOLF).expect("failed to parse");
383 let valid = res.take_valid().expect("data is invalid");
384 dbg!(&valid);
385 dbg!(&valid.get_max_season());
386 }
387
388 #[test]
389 fn parse_invalid_overwolf() {
390 let res: OverwolfResponse<serde_json::Value> =
391 serde_json::from_str(INVALID_OVERWOLF_RESPONSE).unwrap();
392 dbg!(res);
393 }
394}