pub mod lifetime_stats;
pub use self::lifetime_stats::LifetimeStats;
use std::collections::HashMap;
use url::Url;
#[derive(Debug)]
pub struct InvalidOverwolfResponseError(pub String);
impl std::error::Error for InvalidOverwolfResponseError {}
impl std::fmt::Display for InvalidOverwolfResponseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "the overwolf response was invalid ({})", self.0)
}
}
#[derive(Debug)]
pub enum OverwolfResponse<T> {
Valid(T),
Invalid(String),
}
impl<'de, T> serde::Deserialize<'de> for OverwolfResponse<T>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut map = serde_json::Map::deserialize(deserializer)?;
let success = map
.remove("success")
.ok_or_else(|| serde::de::Error::missing_field("success"))
.map(serde::Deserialize::deserialize)?
.map_err(serde::de::Error::custom)?;
let rest = serde_json::Value::Object(map);
if success {
T::deserialize(rest)
.map(OverwolfResponse::Valid)
.map_err(serde::de::Error::custom)
} else {
#[derive(serde::Deserialize)]
struct ErrorReason {
reason: String,
}
ErrorReason::deserialize(rest)
.map(|e| OverwolfResponse::Invalid(e.reason))
.map_err(serde::de::Error::custom)
}
}
}
impl<T> OverwolfResponse<T> {
pub fn into_result(self) -> Result<T, InvalidOverwolfResponseError> {
match self {
Self::Valid(data) => Ok(data),
Self::Invalid(reason) => Err(InvalidOverwolfResponseError(reason)),
}
}
pub fn get_valid(&self) -> Option<&T> {
match &self {
Self::Valid(data) => Some(data),
Self::Invalid(_) => None,
}
}
pub fn take_valid(self) -> Option<T> {
match self {
Self::Valid(data) => Some(data),
Self::Invalid(_) => None,
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfPlayer {
#[serde(rename = "playerId")]
pub player_id: String,
pub name: String,
pub avatar: Url,
pub level: u64,
#[serde(rename = "isPremium")]
pub is_premium: bool,
#[serde(rename = "suspectedCheater")]
pub suspected_cheater: bool,
#[serde(rename = "currentSeason")]
pub current_season: u64,
#[serde(rename = "currentSeasonBestRegion")]
pub current_season_best_region: Option<OverwolfSeason>,
#[serde(rename = "lifetimeStats")]
pub lifetime_stats: LifetimeStats,
pub seasons: Vec<OverwolfSeason>,
pub operators: Vec<OverwolfOperator>,
#[serde(rename = "seasonalOperators")]
pub seasonal_operators: Option<SeasonalOperators>,
#[serde(flatten)]
pub unknown: HashMap<String, serde_json::Value>,
}
impl OverwolfPlayer {
pub fn get_current_casual_season(&self) -> Option<&OverwolfSeason> {
self.seasons
.iter()
.find(|season| !season.is_ranked && season.season == self.current_season)
}
pub fn iter_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
self.seasons.iter().filter(|season| season.is_ranked)
}
pub fn iter_placed_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
self.iter_ranked_seasons()
.filter(|season| season.current_rank.rank_tier != 0)
}
pub fn get_max_season(&self) -> Option<&OverwolfSeason> {
self.iter_placed_ranked_seasons()
.max_by_key(|season| season.max_mmr)
}
pub fn get_lifetime_ranked_kd(&self) -> Option<f64> {
let mut length = 0;
let mut sum = 0.0;
for season in self.iter_placed_ranked_seasons() {
sum += season.kd;
length += 1;
}
if length == 0 {
None
} else {
Some(sum / (length as f64))
}
}
pub fn get_lifetime_ranked_win_pct(&self) -> Option<f64> {
let mut length = 0;
let mut sum = 0.0f64;
for season in self.iter_placed_ranked_seasons() {
sum += season.win_pct;
length += 1;
}
if length == 0 {
None
} else {
Some(sum / (length as f64))
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfSeason {
#[serde(rename = "rankName")]
pub rank_name: String,
pub img: Url,
pub season: u64,
pub region: String,
#[serde(rename = "regionLabel")]
pub region_label: String,
pub mmr: u64,
#[serde(rename = "winPct")]
pub win_pct: f64,
pub wins: u64,
pub kd: f64,
pub kills: u64,
pub matches: u64,
#[serde(rename = "mmrChange")]
pub mmr_change: Option<i64>,
#[serde(rename = "isRanked")]
pub is_ranked: bool,
#[serde(rename = "maxMmr")]
pub max_mmr: u64,
#[serde(rename = "currentRank")]
pub current_rank: OverwolfRank,
#[serde(rename = "maxRank")]
pub max_rank: OverwolfRank,
#[serde(rename = "prevRank")]
pub prev_rank: Option<OverwolfRank>,
#[serde(rename = "nextRank")]
pub next_rank: Option<OverwolfRank>,
#[serde(flatten)]
pub unknown: HashMap<String, serde_json::Value>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfRank {
#[serde(rename = "rankTier")]
pub rank_tier: u64,
pub mmr: u64,
#[serde(rename = "rankIcon")]
pub rank_icon: Url,
#[serde(rename = "rankName")]
pub rank_name: String,
#[serde(flatten)]
pub unknown: HashMap<String, serde_json::Value>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfOperator {
pub name: String,
pub img: Url,
#[serde(rename = "isAttack")]
pub is_attack: bool,
#[serde(rename = "isTopOperator")]
pub is_top_operator: bool,
#[serde(rename = "winpct")]
pub win_pct: f64,
pub wins: u64,
pub kd: f64,
pub kills: u64,
#[serde(rename = "timePlayedDisplay")]
pub time_played_display: String,
#[serde(rename = "timePlayed")]
pub time_played: u64,
#[serde(flatten)]
pub unknown: HashMap<String, serde_json::Value>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SeasonalOperators {
pub operators: Vec<OverwolfOperator>,
#[serde(rename = "startedTracking")]
pub started_tracking: String,
#[serde(flatten)]
pub unknown: HashMap<String, serde_json::Value>,
}
#[cfg(test)]
mod test {
use super::*;
const OVERWOLF_PLAYER_1: &str = include_str!("../../test_data/overwolf_player_1.json");
const OVERWOLF_PLAYER_2: &str = include_str!("../../test_data/overwolf_player_2.json");
const INVALID_OVERWOLF_RESPONSE: &str =
include_str!("../../test_data/invalid_overwolf_response.json");
const SMACK_ASH_OVERWOLF: &str = include_str!("../../test_data/smack_ash_overwolf.json");
#[test]
fn parse_overwolf_player_1() {
let res: OverwolfResponse<OverwolfPlayer> =
serde_json::from_str(OVERWOLF_PLAYER_1).unwrap();
dbg!(res.take_valid().unwrap());
}
#[test]
fn parse_overwolf_player_2() {
let res: OverwolfResponse<OverwolfPlayer> =
serde_json::from_str(OVERWOLF_PLAYER_2).unwrap();
let valid = res.take_valid().unwrap();
dbg!(&valid);
dbg!(&valid.get_max_season());
}
#[test]
fn parse_smack_ash_overwolf() {
let res: OverwolfResponse<OverwolfPlayer> =
serde_json::from_str(SMACK_ASH_OVERWOLF).expect("failed to parse");
let valid = res.take_valid().expect("data is invalid");
dbg!(&valid);
dbg!(&valid.get_max_season());
}
#[test]
fn parse_invalid_overwolf() {
let res: OverwolfResponse<serde_json::Value> =
serde_json::from_str(INVALID_OVERWOLF_RESPONSE).unwrap();
dbg!(res);
}
}