mlb_api/endpoints/venue/
mod.rs

1use crate::endpoints::StatsAPIUrl;
2use crate::endpoints::sports::SportId;
3use crate::{gen_params, rwlock_const_new, RwLock};
4use crate::types::Copyright;
5use derive_more::{Deref, DerefMut, Display, From};
6use serde::Deserialize;
7use serde_with::DisplayFromStr;
8use serde_with::serde_as;
9use std::fmt::{Display, Formatter};
10use std::ops::{Deref, DerefMut};
11use itertools::Itertools;
12use strum::EnumTryAs;
13use crate::cache::{EndpointEntryCache, HydratedCacheTable};
14
15#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct VenuesResponse {
18	pub copyright: Copyright,
19	pub venues: Vec<Venue>,
20}
21
22#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
23#[serde(rename_all = "camelCase")]
24pub struct IdentifiableVenue {
25	pub id: VenueId,
26}
27
28impl Default for IdentifiableVenue {
29	fn default() -> Self {
30		Self { id: VenueId(0) }
31	}
32}
33
34#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
35#[serde(rename_all = "camelCase")]
36pub struct NamedVenue {
37	pub name: String,
38
39	#[deref]
40	#[deref_mut]
41	#[serde(flatten)]
42	inner: IdentifiableVenue,
43}
44
45impl Default for NamedVenue {
46	fn default() -> Self {
47		Self {
48			name: "null".to_owned(),
49			inner: IdentifiableVenue::default(),
50		}
51	}
52}
53
54#[serde_as]
55#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
56#[serde(rename_all = "camelCase")]
57pub struct HydratedVenue {
58	pub active: bool,
59	#[serde_as(as = "DisplayFromStr")]
60	pub season: u16,
61
62	#[deref]
63	#[deref_mut]
64	#[serde(flatten)]
65	pub inner: NamedVenue,
66}
67
68#[repr(transparent)]
69#[derive(Debug, Deserialize, Deref, Display, PartialEq, Eq, Copy, Clone, Hash)]
70pub struct VenueId(u32);
71
72#[derive(Debug, Deserialize, Eq, Clone, From, EnumTryAs)]
73#[serde(untagged)]
74pub enum Venue {
75	Hydrated(HydratedVenue),
76	Named(NamedVenue),
77	Identifiable(IdentifiableVenue),
78}
79
80impl PartialEq for Venue {
81	fn eq(&self, other: &Self) -> bool {
82		self.id == other.id
83	}
84}
85
86impl Deref for Venue {
87	type Target = IdentifiableVenue;
88
89	fn deref(&self) -> &Self::Target {
90		match self {
91			Self::Hydrated(inner) => inner,
92			Self::Named(inner) => inner,
93			Self::Identifiable(inner) => inner,
94		}
95	}
96}
97
98impl DerefMut for Venue {
99	fn deref_mut(&mut self) -> &mut Self::Target {
100		match self {
101			Self::Hydrated(inner) => inner,
102			Self::Named(inner) => inner,
103			Self::Identifiable(inner) => inner,
104		}
105	}
106}
107
108#[derive(Default)]
109pub struct VenuesEndpointUrl {
110	pub sport_id: Option<SportId>,
111	pub venue_ids: Option<Vec<VenueId>>,
112	pub season: Option<u16>,
113}
114
115impl Display for VenuesEndpointUrl {
116	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117		write!(f, "http://statsapi.mlb.com/api/v1/venues{}{}", self.sport_id.map_or(String::new(), |id| format!("/{id}")), gen_params! { "season"?: self.season, "venueIds"?: self.venue_ids.as_ref().map(|ids| ids.iter().join(",")) })
118	}
119}
120
121impl StatsAPIUrl for VenuesEndpointUrl {
122	type Response = VenuesResponse;
123}
124
125static CACHE: RwLock<HydratedCacheTable<Venue>> = rwlock_const_new(HydratedCacheTable::new());
126
127impl EndpointEntryCache for Venue {
128	type HydratedVariant = HydratedVenue;
129	type Identifier = VenueId;
130	type URL = VenuesEndpointUrl;
131
132	fn into_hydrated_variant(self) -> Option<Self::HydratedVariant> {
133		self.try_as_hydrated()
134	}
135
136	fn id(&self) -> &Self::Identifier {
137		&self.id
138	}
139
140	fn url_for_id(id: &Self::Identifier) -> Self::URL {
141		VenuesEndpointUrl {
142			sport_id: None,
143			venue_ids: Some(vec![id.clone()]),
144			season: None,
145		}
146	}
147
148	fn get_entries(response: <Self::URL as StatsAPIUrl>::Response) -> impl IntoIterator<Item=Self>
149	where
150		Self: Sized
151	{
152		response.venues
153	}
154
155	fn get_hydrated_cache_table() -> &'static RwLock<HydratedCacheTable<Self>>
156	where
157		Self: Sized
158	{
159		&CACHE
160	}
161}
162
163#[cfg(test)]
164mod tests {
165	use crate::endpoints::StatsAPIUrl;
166	use crate::endpoints::venue::VenuesEndpointUrl;
167	use chrono::{Datelike, Local};
168
169	#[tokio::test]
170	#[cfg_attr(not(feature = "_heavy_tests"), ignore)]
171	async fn parse_all_venues_all_seasons() {
172		for season in 1876..=Local::now().year() as _ {
173			let _response = VenuesEndpointUrl { sport_id: None, season: Some(season), venue_ids: None }.get().await.unwrap();
174		}
175	}
176
177	async fn parse_all_venues() {
178		let _response = VenuesEndpointUrl { sport_id: None, season: None, venue_ids: None }.get().await.unwrap();
179	}
180}