mlb_api/requests/
venue.rs1use crate::season::SeasonId;
4use crate::Copyright;
5use crate::request::RequestURL;
6use bon::Builder;
7use derive_more::{Deref, DerefMut};
8use itertools::Itertools;
9use serde::Deserialize;
10use std::fmt::{Display, Formatter};
11use std::marker::PhantomData;
12use crate::cache::{Requestable};
13use crate::sport::SportId;
14
15#[cfg(feature = "cache")]
16use crate::{rwlock_const_new, RwLock, cache::CacheTable};
17use crate::hydrations::Hydrations;
18
19#[derive(Debug, Deserialize, PartialEq, Clone)]
20#[serde(rename_all = "camelCase", bound = "H: VenueHydrations")]
21pub struct VenuesResponse<H: VenueHydrations = ()> {
22 pub copyright: Copyright,
23 pub venues: Vec<Venue<H>>,
24}
25
26#[derive(Debug, Deserialize, Clone, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct NamedVenue {
29 pub name: String,
30 #[serde(flatten)]
31 pub id: VenueId,
32}
33
34impl NamedVenue {
35 pub(crate) fn unknown_venue() -> Self {
36 Self {
37 name: "null".to_owned(),
38 id: VenueId::new(0),
39 }
40 }
41
42 #[must_use]
43 pub fn is_unknown(&self) -> bool {
44 *self.id == 0
45 }
46}
47
48#[derive(Debug, Deserialize, Deref, DerefMut, Clone)]
49#[serde(rename_all = "camelCase", bound = "H: VenueHydrations")]
50pub struct Venue<H: VenueHydrations = ()> {
51 pub active: bool,
52 pub season: SeasonId,
53
54 #[deref]
55 #[deref_mut]
56 #[serde(flatten)]
57 pub inner: NamedVenue,
58
59 #[serde(flatten)]
60 pub extras: H,
61}
62
63id!(VenueId { id: u32 });
64id_only_eq_impl!(NamedVenue, id);
65
66impl<H: VenueHydrations> PartialEq for Venue<H> {
67 fn eq(&self, other: &Self) -> bool {
68 self.id == other.id
69 }
70}
71
72pub trait VenueHydrations: Hydrations<RequestData=()> {}
73
74impl VenueHydrations for () {}
75
76#[macro_export]
110macro_rules! venue_hydrations {
111 (@ inline_structs [$field:ident $(: $value: path)? $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
112 $crate::venue_hydrations! { @ inline_structs [$($($tt)*)?] $vis struct $name { $($field_tt)* $field $(: $value)?, } }
113 };
114 (@ inline_structs [$(,)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
115 $crate::venue_hydrations! { @ actual $vis struct $name { $($field_tt)* } }
116 };
117 (@ actual $vis:vis struct $name:ident {
118 $(location $location_comma:tt)?
119 $(timezone $timezone_comma:tt)?
120 $(field_info $field_info_comma:tt)?
121 $(external_references $external_references_comma:tt)?
122 $(tracking_system $tracking_system_comma:tt)?
123 }) => {
124 #[derive(::core::fmt::Debug, ::serde::Deserialize, ::core::cmp::PartialEq, ::core::clone::Clone)]
125 #[serde(rename_all = "camelCase")]
126 $vis struct $name {
127 $(#[serde(default)] pub location: $crate::types::Location $location_comma)?
128 $(#[serde(rename = "timeZone")] pub timezone: $crate::types::TimeZoneData $timezone_comma)?
129 $(#[serde(rename = "fieldInfo")] pub field_info: $crate::types::FieldInfo $field_info_comma)?
130 $(#[serde(default, rename = "xrefIds")] pub external_references: ::std::vec::Vec<$crate::types::ExternalReference> $external_references_comma)?
131 $(#[serde(rename = "trackingVersion")] pub tracking_system: ::core::option::Option<$crate::types::TrackingSystem> $tracking_system_comma)?
132 }
133
134 impl $crate::venue::VenueHydrations for $name {}
135
136 impl $crate::hydrations::Hydrations for $name {
137 type RequestData = ();
138
139 fn hydration_text(&(): &Self::RequestData) -> ::std::borrow::Cow<'static, str> {
140 ::std::borrow::Cow::Borrowed(::std::concat!(
141 $("location," $location_comma)?
142 $("timezone," $timezone_comma)?
143 $("fieldInfo," $field_info_comma)?
144 $("xrefId," $external_references_comma)?
145 $("trackingVersion," $tracking_system_comma)?
146 ))
147 }
148 }
149 };
150 ($vis:vis struct $name:ident { $($tt:tt)* }) => {
151 venue_hydrations! { @ inline_structs [$($tt)*] $vis struct $name {} }
152 };
153}
154
155#[cfg(feature = "cache")]
156static CACHE: RwLock<CacheTable<Venue<()>>> = rwlock_const_new(CacheTable::new());
157
158impl Requestable for Venue<()> {
159 type Identifier = VenueId;
160 type URL = VenuesRequest<()>;
161
162 fn id(&self) -> &Self::Identifier {
163 &self.id
164 }
165
166 #[cfg(feature = "aggressive_cache")]
167 fn url_for_id(_id: &Self::Identifier) -> Self::URL {
168 VenuesRequest::builder().build()
169 }
170
171 #[cfg(not(feature = "aggressive_cache"))]
172 fn url_for_id(id: &Self::Identifier) -> Self::URL {
173 VenuesRequest::builder().venue_ids(vec![*id]).build()
174 }
175
176 fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item=Self>
177 where
178 Self: Sized
179 {
180 response.venues
181 }
182
183 #[cfg(feature = "cache")]
184 fn get_cache_table() -> &'static RwLock<CacheTable<Self>>
185 where
186 Self: Sized
187 {
188 &CACHE
189 }
190}
191
192#[derive(Builder)]
193#[builder(derive(Into))]
194pub struct VenuesRequest<H: VenueHydrations> {
195 #[builder(into, default)]
196 sport_id: SportId,
197 venue_ids: Option<Vec<VenueId>>,
198 #[builder(into)]
199 season: Option<SeasonId>,
200 #[builder(skip)]
201 _marker: PhantomData<H>,
202}
203
204impl VenuesRequest<()> {
205 pub fn for_sport(sport_id: impl Into<SportId>) -> VenuesRequestBuilder<(), venues_request_builder::SetSportId> {
206 Self::builder().sport_id(sport_id)
207 }
208}
209
210impl<H: VenueHydrations, S: venues_request_builder::State + venues_request_builder::IsComplete> crate::request::RequestURLBuilderExt for VenuesRequestBuilder<H, S> {
211 type Built = VenuesRequest<H>;
212}
213
214impl<H: VenueHydrations> Display for VenuesRequest<H> {
215 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
216 let hydrations = Some(H::hydration_text(&())).filter(|s| !s.is_empty());
217 write!(f, "http://statsapi.mlb.com/api/v1/venues{}", gen_params! { "season"?: self.season, "sportId": self.sport_id, "venueIds"?: self.venue_ids.as_ref().map(|ids| ids.iter().join(",")), "hydrate"?: hydrations })
218 }
219}
220
221impl<H: VenueHydrations> RequestURL for VenuesRequest<H> {
222 type Response = VenuesResponse<H>;
223}
224
225#[cfg(test)]
226mod tests {
227 use crate::request::RequestURLBuilderExt;
228 use crate::sport::SportId;
229 use crate::TEST_YEAR;
230 use crate::venue::VenuesRequest;
231
232 #[tokio::test]
233 #[cfg_attr(not(feature = "_heavy_tests"), ignore)]
234 async fn parse_all_venues_all_seasons() {
235 for season in 1876..=TEST_YEAR {
236 let _response = VenuesRequest::<()>::builder().season(season).build_and_get().await.unwrap();
237 }
238 }
239
240 #[tokio::test]
241 async fn parse_all_venues() {
242 let _response = VenuesRequest::<()>::builder().build_and_get().await.unwrap();
243 }
244
245 #[tokio::test]
246 async fn parse_all_mlb_venues_hydrated() {
247 venue_hydrations! {
248 pub struct TestHydrations {
249 location,
250 timezone,
251 field_info,
252 external_references,
253 tracking_system,
254 }
255 }
256
257 let _response = VenuesRequest::<TestHydrations>::builder().sport_id(SportId::MLB).build_and_get().await.unwrap();
258 }
259}