mlb_api/endpoints/stats/leaders/
mod.rs1use crate::endpoints::league::League;
2use crate::endpoints::person::Person;
3use crate::endpoints::sports::{Sport, SportId};
4use crate::endpoints::teams::team::Team;
5use crate::endpoints::{BaseballStat, BaseballStatId, GameType, IdentifiableBaseballStat, StatGroup, StatType, StatsAPIUrl};
6use crate::gen_params;
7use crate::types::{Copyright, IntegerOrFloatStat, PlayerPool};
8use chrono::NaiveDate;
9use itertools::Itertools;
10use serde::Deserialize;
11use serde_with::serde_as;
12use serde_with::DisplayFromStr;
13use std::fmt::{Display, Formatter};
14use std::str::FromStr;
15
16#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
17#[serde(rename_all = "camelCase")]
18pub struct StatLeadersResponse {
19 pub copyright: Copyright,
20 pub league_leaders: Vec<StatLeaders>,
21}
22
23#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
24#[serde(try_from = "__StatLeadersStruct")]
25pub struct StatLeaders {
26 pub category: BaseballStat,
27 pub game_type: GameType,
28 pub leaders: Vec<StatLeader>,
29 pub stat_group: StatGroup,
30 pub total_splits: u32,
31}
32
33#[derive(Deserialize)]
34#[serde(rename_all = "camelCase")]
35struct __StatLeadersStruct {
36 leader_category: String,
37 game_type: GameType,
38 #[serde(default)]
39 leaders: Vec<StatLeader>,
40 stat_group: String,
41 total_splits: u32,
42}
43
44impl TryFrom<__StatLeadersStruct> for StatLeaders {
45 type Error = <StatGroup as FromStr>::Err;
46
47 fn try_from(value: __StatLeadersStruct) -> Result<Self, Self::Error> {
48 Ok(StatLeaders {
49 category: BaseballStat::Identifiable(IdentifiableBaseballStat {
50 id: BaseballStatId::new(value.leader_category),
51 }),
52 game_type: value.game_type,
53 leaders: value.leaders,
54 stat_group: StatGroup::from_str(&value.stat_group)?,
55 total_splits: value.total_splits,
56 })
57 }
58}
59
60#[serde_as]
61#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
62#[serde(rename_all = "camelCase")]
63pub struct StatLeader {
64 pub rank: u32,
65 pub value: IntegerOrFloatStat,
66 #[serde(default = "Team::unknown_team")]
67 pub team: Team,
68 pub num_teams: u32,
69 #[serde(default = "League::unknown_league")]
70 pub league: League,
71 pub person: Person,
72 pub sport: Sport,
73 #[serde_as(as = "DisplayFromStr")]
74 pub season: u16,
75}
76
77pub struct StatLeadersEndpointUrl {
78 pub stats: Vec<BaseballStat>,
79 pub stat_groups: Vec<StatGroup>,
80 pub season: Option<u16>,
81 pub sport_id: SportId,
82 pub stat_types: Vec<StatType>,
83 pub start_date: Option<NaiveDate>,
84 pub end_date: Option<NaiveDate>,
85 pub pool: PlayerPool,
86
87 pub days_back: Option<u32>,
89
90 pub limit: Option<u16>,
93 pub offset: Option<u16>,
95
96 pub game_types: Option<Vec<GameType>>,
98}
99
100impl Display for StatLeadersEndpointUrl {
101 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102 write!(f, "http://statsapi.mlb.com/api/v1/stats/leaders{params}", params = gen_params! {
103 "leaderCategories": self.stats.iter().map(|stat| &stat.id).join(","),
104 "statGroup": self.stat_groups.iter().join(","),
105 "season"?: self.season,
106 "sportId": self.sport_id,
107 "stats": self.stat_types.iter().join(","),
108 "startDate"?: self.start_date,
109 "endDate"?: self.end_date,
110 "playerPool": self.pool,
111 "daysBack"?: self.days_back,
112 "limit"?: self.limit,
113 "offset"?: self.offset,
114 "gameTypes"?: self.game_types.as_ref().map(|x| x.iter().join(",")),
115 })
116 }
117}
118
119impl StatsAPIUrl for StatLeadersEndpointUrl {
120 type Response = StatLeadersResponse;
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::endpoints::meta::MetaEndpointUrl;
126 use crate::endpoints::sports::SportId;
127 use crate::endpoints::stats::leaders::StatLeadersEndpointUrl;
128 use crate::endpoints::{BaseballStat, GameType, StatsAPIUrl};
129 use crate::types::PlayerPool;
130
131 #[tokio::test]
132 async fn test_stat_leaders() {
133 let all_stats = MetaEndpointUrl::<BaseballStat>::new().get().await.unwrap().entries;
134 let all_game_types = MetaEndpointUrl::<GameType>::new().get().await.unwrap().entries;
135
136 let request = StatLeadersEndpointUrl {
137 stats: all_stats,
138 stat_groups: vec![],
139 season: None,
140 sport_id: SportId::MLB,
141 stat_types: vec![],
142 start_date: None,
143 end_date: None,
144 pool: PlayerPool::All,
145 days_back: None,
146 limit: Some(100),
147 offset: None,
148 game_types: Some(all_game_types),
149 };
150 let response_str = reqwest::get(request.to_string()).await.unwrap().text().await.unwrap();
151 let mut de = serde_json::Deserializer::from_str(&response_str);
152 let result: Result<<StatLeadersEndpointUrl as StatsAPIUrl>::Response, serde_path_to_error::Error<_>> = serde_path_to_error::deserialize(&mut de);
153 match result {
154 Ok(_) => {}
155 Err(e) if format!("{:?}", e.inner()).contains("missing field `copyright`") => {}
156 Err(e) => panic!("Err: {:?}", e),
157 }
158 }
159}