mlb_api/endpoints/venue/
mod.rs1use 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}