Skip to main content

mlb_api/requests/game/
pace.rs

1//! Conglomerate pace data about teams, leagues, and sports. Such as total hits, innings, game duration, etc.
2
3use std::fmt::Display;
4
5use bon::Builder;
6use chrono::TimeDelta;
7use derive_more::{Deref, DerefMut};
8use serde::{Deserialize, de::IgnoredAny};
9use itertools::Itertools;
10
11use crate::{Copyright, league::{LeagueId, NamedLeague}, request::RequestURL, season::SeasonId, sport::SportId, team::{NamedTeam, TeamId}};
12
13#[derive(Debug, Deserialize, PartialEq, Clone)]
14#[serde(rename_all = "camelCase")]
15#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
16pub struct GamePace {
17    #[serde(deserialize_with = "crate::deserialize_time_delta_from_hms", default)]
18    pub total_game_time: TimeDelta,
19    /// Can have a .5 for a half-inning played
20    #[serde(rename = "totalInningsPlayed", default)]
21    pub innings_played: f64,
22    #[serde(rename = "totalHits", default)]
23    pub hits: usize,
24    #[serde(rename = "totalRuns", default)]
25    pub runs: usize,
26    #[serde(rename = "totalPlateAppearances", default)]
27    pub plate_appearances: usize,
28    #[serde(rename = "totalPitchers",default)]
29    pub num_pitchers: usize,
30    #[serde(rename = "totalPitches", default)]
31    pub num_pitches: usize,
32    #[serde(rename = "totalGames", default)]
33    pub games: usize,
34
35    #[serde(rename = "total9InnGames", default)]
36    pub nine_inning_games: usize,
37    #[serde(rename = "total9InnGamesCompletedEarly", default)]
38    pub nine_inning_games_completed_early: usize,
39    #[serde(rename = "total9InnGamesScheduled", default)]
40    pub nine_inning_games_scheduled: usize,
41    #[serde(rename = "total9InnGamesWithoutExtraInn", default)]
42    pub nine_inning_games_without_extra_innings: usize,
43    
44    #[serde(rename = "total7InnGames", default)]
45    pub seven_inning_games: usize,
46    #[serde(rename = "total7InnGamesCompletedEarly", default)]
47    pub seven_inning_games_completed_early: usize,
48    #[serde(rename = "total7InnGamesScheduled", default)]
49    pub seven_inning_games_scheduled: usize,
50    #[serde(rename = "total7InnGamesWithoutExtraInn", default)]
51    pub seven_inning_games_without_extra_innings: usize,
52    
53    #[serde(rename = "totalExtraInnGames", default)]
54    pub extra_inning_games: usize,
55    
56    pub season: SeasonId,
57    #[serde(rename = "timePer7InnGame", deserialize_with = "crate::deserialize_time_delta_from_hms", default)]
58    pub time_per_seven_inning_game: TimeDelta,
59
60    #[doc(hidden)]
61    #[serde(rename = "prPortalCalculatedFields", default)]
62    pub __pr_portal_calculated_fields: IgnoredAny,
63
64    #[doc(hidden)]
65    #[serde(rename = "hitsPer9Inn", default)]
66    pub __hits_per_nine: IgnoredAny,
67
68    #[doc(hidden)]
69    #[serde(rename = "runsPer9Inn", default)]
70    pub __runs_per_nine: IgnoredAny,
71
72    #[doc(hidden)]
73    #[serde(rename = "pitchesPer9Inn", default)]
74    pub __pitches_per_nine: IgnoredAny,
75
76    #[doc(hidden)]
77    #[serde(rename = "plateAppearancesPer9Inn", default)]
78    pub __plate_appearances_per_nine: IgnoredAny,
79
80    #[doc(hidden)]
81    #[serde(rename = "hitsPerGame", default)]
82    pub __hits_per_game: IgnoredAny,
83
84    #[doc(hidden)]
85    #[serde(rename = "runsPerGame", default)]
86    pub __runs_per_game: IgnoredAny,
87
88    #[doc(hidden)]
89    #[serde(rename = "inningsPlayedPerGame", default)]
90    pub __innings_played_per_game: IgnoredAny,
91
92    #[doc(hidden)]
93    #[serde(rename = "pitchesPerGame", default)]
94    pub __pitches_per_game: IgnoredAny,
95
96    #[doc(hidden)]
97    #[serde(rename = "pitchersPerGame", default)]
98    pub __pitchers_per_game: IgnoredAny,
99
100    #[doc(hidden)]
101    #[serde(rename = "plateAppearancesPerGame", default)]
102    pub __plate_appearances_per_game: IgnoredAny,
103
104    #[doc(hidden)]
105    #[serde(rename = "timePerGame", default)]
106    pub __time_per_game: IgnoredAny,
107
108    #[doc(hidden)]
109    #[serde(rename = "timePerPitch", default)]
110    pub __time_per_pitch: IgnoredAny,
111
112    #[doc(hidden)]
113    #[serde(rename = "timePerHit", default)]
114    pub __time_per_hit: IgnoredAny,
115
116    #[doc(hidden)]
117    #[serde(rename = "timePerRun", default)]
118    pub __time_per_run: IgnoredAny,
119
120    #[doc(hidden)]
121    #[serde(rename = "timePerPlateAppearance", default)]
122    pub __time_per_plate_appearance: IgnoredAny,
123
124    #[doc(hidden)]
125    #[serde(rename = "timePer9Inn", default)]
126    pub __time_per_nine: IgnoredAny,
127
128    #[doc(hidden)]
129    #[serde(rename = "timePer77PlateAppearances", default)]
130    pub __time_per_77_plate_appearances: IgnoredAny,
131
132    #[doc(hidden)]
133    #[serde(rename = "totalExtraInnTime", default)]
134    pub __total_extra_inning_time: IgnoredAny,
135
136    #[doc(hidden)]
137    #[serde(rename = "timePer7InnGameWithoutExtraInn", default)]
138    pub __time_per_seven_inning_game_without_extra_inning: IgnoredAny,
139
140    #[doc(hidden)]
141    #[serde(rename = "hitsPerRun", default)]
142    pub __hits_per_run: IgnoredAny,
143
144    #[doc(hidden)]
145    #[serde(rename = "pitchesPerPitcher", default)]
146    pub __pitches_per_pitcher: IgnoredAny,
147}
148
149#[derive(Debug, Deserialize, PartialEq, Clone, Deref, DerefMut)]
150#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
151pub struct TeamGamePace {
152    #[deref]
153    #[deref_mut]
154    #[serde(flatten)]
155    pub pace: GamePace,
156
157    pub team: NamedTeam,
158    pub league: NamedLeague,
159    pub sport: SportId,
160}
161
162#[derive(Debug, Deserialize, PartialEq, Clone, Deref, DerefMut)]
163#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
164pub struct LeagueGamePace {
165    #[deref]
166    #[deref_mut]
167    #[serde(flatten)]
168    pub pace: GamePace,
169
170    pub league: NamedLeague,
171
172    // only sometimes present
173    #[doc(hidden)]
174    #[serde(rename = "sport", default)]
175    pub __sport: IgnoredAny,
176}
177
178#[derive(Debug, Deserialize, PartialEq, Clone, Deref, DerefMut)]
179#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
180pub struct SportGamePace {
181    #[deref]
182    #[deref_mut]
183    #[serde(flatten)]
184    pub pace: GamePace,
185
186    pub sport: SportId,
187}
188
189#[derive(Debug, Deserialize, PartialEq, Clone)]
190#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
191pub struct SportGamePaceResponse {
192    pub copyright: Copyright,
193    pub sports: Vec<SportGamePace>,
194
195    #[doc(hidden)]
196    #[serde(rename = "teams", default)]
197    pub __teams: IgnoredAny,
198
199    #[doc(hidden)]
200    #[serde(rename = "leagues", default)]
201    pub __leagues: IgnoredAny,
202}
203
204#[derive(Builder)]
205#[builder(derive(Into))]
206pub struct SportGamePaceRequest {
207    #[builder(into)]
208    season: SeasonId,
209    #[builder(into)]
210    sport: Option<SportId>,
211}
212
213impl Display for SportGamePaceRequest {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        write!(f, "http://statsapi.mlb.com/api/v1/gamePace{}", gen_params! {
216            "season": self.season,
217            "sportId"?: self.sport,
218        })
219    }
220}
221
222impl<S: sport_game_pace_request_builder::State + sport_game_pace_request_builder::IsComplete> crate::request::RequestURLBuilderExt for SportGamePaceRequestBuilder<S> {
223    type Built = SportGamePaceRequest;
224}
225
226impl RequestURL for SportGamePaceRequest {
227    type Response = SportGamePaceResponse;
228}
229
230#[derive(Debug, Deserialize, PartialEq, Clone)]
231#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
232pub struct TeamGamePaceResponse {
233    pub copyright: Copyright,
234    pub teams: Vec<TeamGamePace>,
235
236    #[doc(hidden)]
237    #[serde(rename = "sports", default)]
238    pub __sports: IgnoredAny,
239
240    #[doc(hidden)]
241    #[serde(rename = "leagues", default)]
242    pub __leagues: IgnoredAny,
243}
244
245#[derive(Builder)]
246#[builder(derive(Into))]
247pub struct TeamGamePaceRequest {
248    #[builder(into)]
249    season: SeasonId,
250    teams: Vec<TeamId>,
251}
252
253impl Display for TeamGamePaceRequest {
254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255        write!(f, "http://statsapi.mlb.com/api/v1/gamePace{}", gen_params! {
256            "season": self.season,
257            "teamIds": self.teams.iter().join(","),
258        })
259    }
260}
261
262impl<S: team_game_pace_request_builder::State + team_game_pace_request_builder::IsComplete> crate::request::RequestURLBuilderExt for TeamGamePaceRequestBuilder<S> {
263    type Built = TeamGamePaceRequest;
264}
265
266impl RequestURL for TeamGamePaceRequest {
267    type Response = TeamGamePaceResponse;
268}
269
270#[derive(Debug, Deserialize, PartialEq, Clone)]
271#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
272pub struct LeagueGamePaceResponse {
273    pub copyright: Copyright,
274    pub leagues: Vec<LeagueGamePace>,
275
276    #[doc(hidden)]
277    #[serde(rename = "teams", default)]
278    pub __teams: IgnoredAny,
279
280    #[doc(hidden)]
281    #[serde(rename = "sports", default)]
282    pub __sports: IgnoredAny,
283}
284
285#[derive(Builder)]
286#[builder(derive(Into))]
287pub struct LeagueGamePaceRequest {
288    #[builder(into)]
289    season: SeasonId,
290    leagues: Vec<LeagueId>,
291}
292
293impl Display for LeagueGamePaceRequest {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        write!(f, "http://statsapi.mlb.com/api/v1/gamePace{}", gen_params! {
296            "season": self.season,
297            "leagueIds": self.leagues.iter().join(","),
298        })
299    }
300}
301
302impl<S: league_game_pace_request_builder::State + league_game_pace_request_builder::IsComplete> crate::request::RequestURLBuilderExt for LeagueGamePaceRequestBuilder<S> {
303    type Built = LeagueGamePaceRequest;
304}
305
306impl RequestURL for LeagueGamePaceRequest {
307    type Response = LeagueGamePaceResponse;
308}
309
310#[cfg(test)]
311mod tests {
312    use crate::{TEST_YEAR, game::{LeagueGamePaceRequest, SportGamePaceRequest, TeamGamePaceRequest}, request::RequestURLBuilderExt, sport::SportId, team::TeamsRequest};
313
314    #[tokio::test]
315    async fn sport_game_pace() {
316        for season in 1901..=TEST_YEAR {
317            let _ = SportGamePaceRequest::builder().season(season).build_and_get().await.unwrap();
318        }
319    }
320
321    #[tokio::test]
322    async fn team_game_pace() {
323        let teams = TeamsRequest::<()>::builder().sport_id(SportId::MLB).build_and_get().await.unwrap().teams;
324        let _ = TeamGamePaceRequest::builder().season(TEST_YEAR).teams(teams.into_iter().map(|team| team.id).collect()).build_and_get().await.unwrap();
325    }
326
327    #[tokio::test]
328    async fn league_game_pace() {
329        for season in 1901..=TEST_YEAR {
330            let _ = LeagueGamePaceRequest::builder().season(season).leagues(vec![103.into(), 104.into()]).build_and_get().await.unwrap();
331        }
332    }
333}
334