mlb_api/requests/game/
linescore.rs1use std::fmt::{Display, Formatter};
2
3use bon::Builder;
4use derive_more::{Deref, DerefMut};
5use serde::Deserialize;
6use serde::de::IgnoredAny;
7
8use crate::request::RequestURL;
9use crate::{Copyright, HomeAway};
10use crate::game::{AtBatCount, GameId, Inning, InningHalf, RHE};
11use crate::person::NamedPerson;
12use crate::team::NamedTeam;
13
14#[derive(Debug, Deserialize, PartialEq, Clone)]
24#[serde(rename_all = "camelCase")]
25#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
26pub struct Linescore {
27 #[serde(default)]
28 pub copyright: Copyright,
29 pub current_inning: Inning,
30 pub inning_half: InningHalf,
31 pub scheduled_innings: usize,
32 pub innings: Vec<LinescoreInningRecord>,
33 #[serde(rename = "teams")]
34 pub rhe_totals: HomeAway<RHE>,
35 pub offense: LinescoreOffense,
36 pub defense: LinescoreDefense,
37 #[serde(flatten)]
38 pub count: AtBatCount,
39 pub note: Option<String>,
40
41 #[doc(hidden)]
42 #[serde(rename = "currentInningOrdinal", default)]
43 pub __current_inning_ordinal: IgnoredAny,
44 #[doc(hidden)]
45 #[serde(rename = "inningState", default)]
46 pub __inning_state: IgnoredAny,
47 #[doc(hidden)]
48 #[serde(rename = "isTopInning", default)]
49 pub __is_top_inning: IgnoredAny,
50}
51
52#[derive(Debug, Deserialize, PartialEq, Clone)]
54#[serde(rename_all = "camelCase")]
55#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
56pub struct LinescoreInningRecord {
57 #[serde(rename = "num")]
58 pub inning: Inning,
59 #[serde(flatten)]
60 pub inning_record: HomeAway<RHE>,
61
62 #[doc(hidden)]
63 #[serde(rename = "ordinalNum", default)]
64 pub __ordinal_num: IgnoredAny,
65}
66
67#[derive(Debug, Deserialize, PartialEq, Clone)]
69#[serde(rename_all = "camelCase")]
70#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
71pub struct LinescoreOffense {
72 pub batter: NamedPerson,
73 pub on_deck: NamedPerson,
74 #[serde(rename = "inHole")]
75 pub in_the_hole: NamedPerson,
76 pub team: NamedTeam,
77 #[serde(rename = "battingOrder")]
79 pub batting_order_index: usize,
80
81 #[doc(hidden)]
82 #[serde(rename = "pitcher", default)]
83 pub __pitcher: IgnoredAny,
84 #[doc(hidden)]
85 #[serde(rename = "catcher", default)]
86 pub __catcher: IgnoredAny,
87 #[doc(hidden)]
88 #[serde(rename = "first", default)]
89 pub __first_baseman: IgnoredAny,
90 #[doc(hidden)]
91 #[serde(rename = "second", default)]
92 pub __second_baseman: IgnoredAny,
93 #[doc(hidden)]
94 #[serde(rename = "third", default)]
95 pub __third_baseman: IgnoredAny,
96 #[doc(hidden)]
97 #[serde(rename = "shortstop", default)]
98 pub __shortstop: IgnoredAny,
99 #[doc(hidden)]
100 #[serde(rename = "left", default)]
101 pub __leftfielder: IgnoredAny,
102 #[doc(hidden)]
103 #[serde(rename = "center", default)]
104 pub __centerfielder: IgnoredAny,
105 #[doc(hidden)]
106 #[serde(rename = "right", default)]
107 pub __rightfielder: IgnoredAny,
108}
109
110#[derive(Debug, Deserialize, PartialEq, Clone, Deref, DerefMut)]
112#[cfg_attr(feature = "_debug", serde(deny_unknown_fields))]
113pub struct LinescoreDefense {
114 pub pitcher: NamedPerson,
115 pub catcher: NamedPerson,
116 #[serde(rename = "first")]
117 pub first_baseman: NamedPerson,
118 #[serde(rename = "second")]
119 pub second_baseman: NamedPerson,
120 #[serde(rename = "third")]
121 pub third_baseman: NamedPerson,
122 pub shortstop: NamedPerson,
123 #[serde(rename = "left")]
124 pub leftfielder: NamedPerson,
125 #[serde(rename = "center")]
126 pub centerfielder: NamedPerson,
127 #[serde(rename = "right")]
128 pub rightfielder: NamedPerson,
129
130 #[deref]
131 #[deref_mut]
132 #[serde(flatten)]
133 pub offense: LinescoreOffense,
134}
135
136#[derive(Builder)]
138#[builder(derive(Into))]
139pub struct LinescoreRequest {
140 #[builder(into)]
141 id: GameId
142}
143
144impl<S: linescore_request_builder::State + linescore_request_builder::IsComplete> crate::request::RequestURLBuilderExt for LinescoreRequestBuilder<S> {
145 type Built = LinescoreRequest;
146}
147
148impl Display for LinescoreRequest {
149 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150 write!(f, "http://statsapi.mlb.com/api/v1/game/{}/linescore", self.id)
151 }
152}
153
154impl RequestURL for LinescoreRequest {
155 type Response = Linescore;
156}
157
158#[cfg(test)]
159mod tests {
160 use crate::TEST_YEAR;
161 use crate::game::LinescoreRequest;
162 use crate::meta::GameType;
163 use crate::request::RequestURLBuilderExt;
164 use crate::schedule::ScheduleRequest;
165 use crate::season::{Season, SeasonsRequest};
166 use crate::sport::SportId;
167
168 #[tokio::test]
169 async fn ws_gm7_2025_linescore() {
170 let _ = LinescoreRequest::builder().id(813_024).build_and_get().await.unwrap();
171 }
172
173 #[tokio::test]
174 async fn postseason_linescore() {
175 let [season]: [Season; 1] = SeasonsRequest::builder().season(TEST_YEAR).sport_id(SportId::MLB).build_and_get().await.unwrap().seasons.try_into().unwrap();
176 let postseason = season.postseason.expect("Expected the MLB to have a postseason");
177 let games = ScheduleRequest::<()>::builder().date_range(postseason).sport_id(SportId::MLB).build_and_get().await.unwrap();
178 let games = games.dates.into_iter().flat_map(|date| date.games).filter(|game| game.game_type.is_postseason()).map(|game| game.game_id).collect::<Vec<_>>();
179 let mut has_errors = false;
180 for game in games {
181 if let Err(e) = LinescoreRequest::builder().id(game).build_and_get().await {
182 dbg!(e);
183 has_errors = true;
184 }
185 }
186 assert!(!has_errors, "Has errors.");
187 }
188
189 #[cfg_attr(not(feature = "_heavy_tests"), ignore)]
190 #[tokio::test]
191 async fn regular_season_linescore() {
192 let [season]: [Season; 1] = SeasonsRequest::builder().season(TEST_YEAR).sport_id(SportId::MLB).build_and_get().await.unwrap().seasons.try_into().unwrap();
193 let regular_season = season.regular_season;
194 let games = ScheduleRequest::<()>::builder().date_range(regular_season).sport_id(SportId::MLB).build_and_get().await.unwrap();
195 let games = games.dates.into_iter().flat_map(|date| date.games).filter(|game| game.game_type == GameType::RegularSeason).collect::<Vec<_>>();
196 let mut has_errors = false;
197 for game in games {
198 if let Err(e) = LinescoreRequest::builder().id(game.game_id).build_and_get().await {
199 dbg!(e);
200 has_errors = true;
201 }
202 }
203 assert!(!has_errors, "Has errors.");
204 }
205}