Skip to main content

mlb_api/requests/
venue.rs

1//! Venues; Yankee Stadium, Rogers Centre, Dodger Stadium, etc.
2
3use 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/// Creates hydrations for a venue
77///
78/// ## Examples
79/// ```
80/// use mlb_api::venue_hydrations;
81/// 
82/// venue_hydrations! {
83///     pub struct TestHydrations {
84///         location,
85///         timezone,
86///         field_info,
87///         external_references,
88///         tracking_system,
89///     }
90/// }
91///
92/// let [venue]: [Venue<TestHydrations>; 1] = VenuesRequest::<TestHydrations>::builder().venue_ids([14.into()]).build_and_get().await.unwrap().venues.try_into().unwrap();
93/// ```
94///
95/// ## Venue Hydrations
96/// | Name                  | Type                       |
97/// |-----------------------|----------------------------|
98/// | `location`            | [`Location`]               |
99/// | `timezone`            | [`TimeZoneData`]           |
100/// | `field_info`          | [`FieldInfo`]              |
101/// | `external_references` | [`Vec<ExternalReference>`] |
102/// | `tracking_system`     | [`TrackingSystem`]         |
103///
104/// [`Location`]: crate::types::Location
105/// [`TimeZoneData`]: crate::types::TimeZoneData
106/// [`FieldInfo`]: crate::types::FieldInfo
107/// [`Vec<ExternalReference>`]: crate::types::ExternalReference
108/// [`TrackingSystem`]: crate::types::TrackingSystem
109#[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}