Skip to main content

mlb_api/requests/stats/
leaders.rs

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