Skip to main content

mlb_api/requests/stats/
leaders.rs

1use crate::league::NamedLeague;
2use crate::sport::SportId;
3use crate::types::{Copyright, IntegerOrFloatStat, PlayerPool, MLB_API_DATE_FORMAT};
4use bon::Builder;
5use chrono::NaiveDate;
6use itertools::Itertools;
7use serde::Deserialize;
8use std::fmt::{Display, Formatter};
9use std::str::FromStr;
10use crate::baseball_stats::BaseballStatId;
11use crate::game_types::GameType;
12use crate::person::NamedPerson;
13use crate::request::RequestURL;
14use crate::season::SeasonId;
15use crate::stat_groups::StatGroup;
16use crate::stat_types::StatType;
17use crate::team::NamedTeam;
18
19#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
20#[serde(rename_all = "camelCase")]
21pub struct StatLeadersResponse {
22	pub copyright: Copyright,
23	pub league_leaders: Vec<StatLeaders>,
24}
25
26#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
27#[serde(try_from = "__StatLeadersStruct")]
28pub struct StatLeaders {
29	pub category: BaseballStatId,
30	pub game_type: GameType,
31	pub leaders: Vec<StatLeader>,
32	pub stat_group: StatGroup,
33	pub total_splits: u32,
34}
35
36#[derive(Deserialize)]
37#[serde(rename_all = "camelCase")]
38#[doc(hidden)]
39struct __StatLeadersStruct {
40	leader_category: String,
41	game_type: GameType,
42	#[serde(default)]
43	leaders: Vec<StatLeader>,
44	stat_group: String,
45	total_splits: u32,
46}
47
48impl TryFrom<__StatLeadersStruct> for StatLeaders {
49	type Error = <StatGroup as FromStr>::Err;
50
51	fn try_from(value: __StatLeadersStruct) -> Result<Self, Self::Error> {
52		Ok(Self {
53			category: value.leader_category.into(),
54			game_type: value.game_type,
55			leaders: value.leaders,
56			stat_group: StatGroup::from_str(&value.stat_group)?,
57			total_splits: value.total_splits,
58		})
59	}
60}
61
62#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
63#[serde(rename_all = "camelCase")]
64pub struct StatLeader {
65	pub rank: u32,
66	pub value: IntegerOrFloatStat,
67	#[serde(default = "NamedTeam::unknown_team")]
68	pub team: NamedTeam,
69	#[serde(default = "NamedLeague::unknown_league")]
70	pub league: NamedLeague,
71	pub person: NamedPerson,
72	pub sport: SportId,
73	pub season: SeasonId,
74}
75
76#[derive(Builder)]
77#[builder(derive(Into))]
78pub struct StatLeadersRequest {
79	stats: Vec<BaseballStatId>,
80	stat_group: Option<StatGroup>,
81	#[builder(into)]
82	season: Option<SeasonId>,
83	#[builder(into, default)]
84	sport_id: SportId,
85	stat_types: Vec<StatType>,
86	start_date: Option<NaiveDate>,
87	end_date: Option<NaiveDate>,
88	pool: PlayerPool,
89
90	/// Number of days to go back for data (starting from yesterday)
91	days_back: Option<u32>,
92
93	/// Limit on how many leaders to show per stat.
94	/// Default is 5.
95	limit: Option<u16>,
96	/// Offset into results.
97	offset: Option<u16>,
98
99	/// [`None`] represents all game types.
100	game_types: Option<Vec<GameType>>,
101}
102
103impl<S: stat_leaders_request_builder::State + stat_leaders_request_builder::IsComplete> crate::request::RequestURLBuilderExt for StatLeadersRequestBuilder<S> {
104	type Built = StatLeadersRequest;
105}
106
107impl Display for StatLeadersRequest {
108	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109		write!(
110			f,
111			"http://statsapi.mlb.com/api/v1/stats/leaders{params}",
112			params = gen_params! {
113				"leaderCategories": self.stats.iter().join(","),
114				"statGroup"?: self.stat_group,
115				"season"?: self.season,
116				"sportId": self.sport_id,
117				"stats": self.stat_types.iter().join(","),
118				"startDate"?: self.start_date.map(|x| x.format(MLB_API_DATE_FORMAT)),
119				"endDate"?: self.end_date.map(|x| x.format(MLB_API_DATE_FORMAT)),
120				"playerPool": self.pool,
121				"daysBack"?: self.days_back,
122				"limit"?: self.limit,
123				"offset"?: self.offset,
124				"gameTypes"?: self.game_types.as_ref().map(|x| x.iter().join(",")),
125			}
126		)
127	}
128}
129
130impl RequestURL for StatLeadersRequest {
131	type Response = StatLeadersResponse;
132}
133
134#[cfg(test)]
135mod tests {
136	use crate::meta::MetaRequest;
137	use crate::stats::leaders::StatLeadersRequest;
138	use crate::types::PlayerPool;
139	use crate::baseball_stats::BaseballStat;
140	use crate::game_types::GameType;
141	use crate::request::{RequestURL, RequestURLBuilderExt};
142
143	#[tokio::test]
144	async fn test_stat_leaders() {
145		let all_stats = MetaRequest::<BaseballStat>::new().get().await.unwrap().entries.into_iter().map(|x| x.id.clone()).collect::<Vec<_>>();
146		let all_game_types = MetaRequest::<GameType>::new().get().await.unwrap().entries;
147
148		let _ = StatLeadersRequest::builder().stats(all_stats).pool(PlayerPool::All).limit(100).game_types(all_game_types).stat_types(vec![]).build_and_get().await.unwrap();
149	}
150}