Skip to main content

mlb_api/requests/meta/
mod.rs

1//! An abstraction over endpoints that contain fixed data, such as [`GameType`]s, [`JobType`]s, etc.
2//!
3//! For types in which quick accessibility is important (and there are not tons of variants), they will be represented as an enum ([`GameType`]).
4//!
5//! For types which have many variants and ones that would be constantly updating, they are represented as `Vec<struct>`s ([`SituationCode`]).
6//!
7//! These types implement [`MetaKind`] and [`Requestable`] for ease of use.
8//!
9//! Some of these types will have id-only variants in most responses, in which you can request details using [`Requestable`]; [`Position`].
10//!
11//! [`Requestable`]: crate::cache::Requestable
12
13macro_rules! meta_kind_impl {
14	($endpoint:literal => $name:ty) => {
15		impl $crate::meta::MetaKind for $name {
16			type Complete = $name;
17
18			const ENDPOINT_NAME: &'static str = $endpoint;
19		}
20	};
21}
22
23macro_rules! test_impl {
24    ($name:ty) => {
25		#[cfg(test)]
26		mod tests {
27			use super::*;
28			use crate::request::RequestURL;
29
30			#[tokio::test]
31			async fn parse_meta() {
32				let _response = $crate::meta::MetaRequest::<$name>::new().get().await.unwrap();
33			}
34		}
35	};
36}
37
38macro_rules! tiered_request_entry_cache_impl {
39	($complete:ident.$id_field:ident: $id:ident) => {
40		tiered_request_entry_cache_impl!([$complete].$id_field: $id);
41	};
42	([$complete:ident $(, $($others:ident)*)?].$id_field:ident: $id:ident) => {
43		#[cfg(feature = "cache")]
44		static CACHE: $crate::RwLock<$crate::cache::CacheTable<$complete>> = $crate::rwlock_const_new($crate::cache::CacheTable::new());
45
46		impl $crate::cache::Requestable for $complete {
47			type Identifier = $id;
48			type URL = $crate::meta::MetaRequest<Self>;
49
50			fn id(&self) -> &Self::Identifier {
51				&self.$id_field
52			}
53
54			fn url_for_id(_id: &Self::Identifier) -> Self::URL {
55				$crate::meta::MetaRequest::new()
56			}
57
58			fn get_entries(response: <Self::URL as $crate::request::RequestURL>::Response) -> impl IntoIterator<Item=Self>
59			where
60				Self: Sized
61			{
62				response.entries
63			}
64
65			#[cfg(feature = "cache")]
66			fn get_cache_table() -> &'static $crate::RwLock<$crate::cache::CacheTable<Self>>
67			where
68				Self: Sized
69			{
70				&CACHE
71			}
72		}
73
74		entrypoint!($complete.$id_field => $complete);
75		$($(
76		entrypoint!($others.$id_field => $complete);
77		)*)?
78		entrypoint!($id => $complete);
79	};
80}
81
82macro_rules! static_request_entry_cache_impl {
83    ($name:ident) => {
84	    #[cfg(feature = "cache")]
85		static CACHE: $crate::RwLock<$crate::cache::CacheTable<$name>> = $crate::rwlock_const_new($crate::cache::CacheTable::new());
86
87		impl $crate::cache::Requestable for $name {
88			type Identifier = Self;
89			type URL = $crate::meta::MetaRequest<Self>;
90
91			fn id(&self) -> &Self::Identifier {
92				self
93			}
94
95			fn url_for_id(_id: &Self::Identifier) -> Self::URL {
96				$crate::meta::MetaRequest::new()
97			}
98
99			fn get_entries(response: <Self::URL as $crate::request::RequestURL>::Response) -> impl IntoIterator<Item=Self>
100			where
101				Self: Sized
102			{
103				response.entries
104			}
105
106			#[cfg(feature = "cache")]
107			fn get_cache_table() -> &'static $crate::RwLock<$crate::cache::CacheTable<Self>>
108			where
109				Self: Sized
110			{
111				&CACHE
112			}
113		}
114		
115		entrypoint!($name => $name);
116	};
117}
118
119mod baseball_stats;
120mod event_types;
121mod game_status;
122mod game_types;
123mod hit_trajectories;
124mod job_types;
125mod languages;
126mod league_leader_types;
127mod logical_events;
128mod metrics;
129mod pitch_codes;
130mod pitch_types;
131mod platforms;
132mod positions;
133mod review_reasons;
134mod roster_types;
135mod schedule_event_types;
136mod situations;
137mod sky;
138mod standings_types;
139mod stat_groups;
140mod stat_types;
141mod wind_direction;
142
143pub use baseball_stats::*;
144pub use event_types::*;
145pub use game_status::*;
146pub use game_types::*;
147pub use hit_trajectories::*;
148pub use job_types::*;
149pub use languages::*;
150pub use league_leader_types::*;
151pub use logical_events::*;
152pub use metrics::*;
153pub use pitch_codes::*;
154pub use pitch_types::*;
155pub use platforms::*;
156pub use positions::*;
157pub use review_reasons::*;
158pub use roster_types::*;
159pub use schedule_event_types::*;
160pub use situations::*;
161pub use sky::*;
162pub use standings_types::*;
163pub use stat_groups::*;
164pub use stat_types::*;
165pub use wind_direction::*;
166
167use crate::request::RequestURL;
168use derive_more::{Deref, DerefMut};
169use serde::de::{DeserializeOwned, Error, MapAccess, SeqAccess};
170use serde::{de, Deserialize, Deserializer};
171use std::fmt::{Debug, Display, Formatter};
172use std::marker::PhantomData;
173
174/// Represents a type that is metadata.
175pub trait MetaKind {
176	type Complete: Debug + DeserializeOwned + PartialEq + Clone;
177
178	const ENDPOINT_NAME: &'static str;
179}
180
181/// Generalized response for the meta endpoints.
182#[derive(Debug, Deref, DerefMut, PartialEq, Clone)]
183pub struct MetaResponse<T: MetaKind> {
184	pub entries: Vec<<T as MetaKind>::Complete>,
185}
186
187impl<'de, T: MetaKind> Deserialize<'de> for MetaResponse<T> {
188	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189	where
190		D: Deserializer<'de>,
191	{
192		struct Visitor<T: MetaKind>(PhantomData<T>);
193
194		impl<'de, T: MetaKind> de::Visitor<'de> for Visitor<T> {
195			type Value = MetaResponse<T>;
196
197			fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
198				formatter.write_str("either copyright and other entry, or just raw list")
199			}
200
201			fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
202			where
203				A: SeqAccess<'de>,
204			{
205				let mut entries = vec![];
206				while let Some(element) = seq.next_element::<<T as MetaKind>::Complete>()? {
207					entries.push(element);
208				}
209				Ok(MetaResponse { entries })
210			}
211
212			fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
213			where
214				A: MapAccess<'de>,
215			{
216				while let Some(key) = map.next_key::<String>()? {
217					if key != "copyright" {
218						let entries = map.next_value::<Vec<<T as MetaKind>::Complete>>()?;
219						return Ok(MetaResponse { entries });
220					}
221				}
222				Err(Error::custom("Could not find a field that deserializes to the entries"))
223			}
224		}
225
226		deserializer.deserialize_any(Visitor(PhantomData))
227	}
228}
229
230/// Returns [`MetaResponse<T>`].
231pub struct MetaRequest<T: MetaKind> {
232	_marker: PhantomData<T>,
233}
234
235impl<T: MetaKind> Default for MetaRequest<T> {
236	fn default() -> Self {
237		Self { _marker: PhantomData }
238	}
239}
240
241impl<T: MetaKind> MetaRequest<T> {
242	#[must_use]
243	pub const fn new() -> Self {
244		Self {
245			_marker: PhantomData
246		}
247	}
248}
249
250impl<T: MetaKind> Display for MetaRequest<T> {
251	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
252		write!(f, "http://statsapi.mlb.com/api/v1/{}", T::ENDPOINT_NAME)
253	}
254}
255
256impl<T: MetaKind> RequestURL for MetaRequest<T> {
257	type Response = MetaResponse<T>;
258}