1use 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 #[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 #[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