Skip to main content

mlb_api/requests/
sport.rs

1//! Different "sports"; MLB, AAA, AA, A+, A, Rookieball, etc.
2
3use crate::Copyright;
4use crate::request::RequestURL;
5use bon::Builder;
6use serde::Deserialize;
7use std::fmt::{Display, Formatter};
8use std::marker::PhantomData;
9use crate::cache::{Requestable};
10#[cfg(feature = "cache")]
11use crate::{rwlock_const_new, RwLock, cache::CacheTable};
12use crate::hydrations::Hydrations;
13
14/// A [`Vec`] of [`Sport`]s.
15#[derive(Debug, Deserialize, PartialEq, Clone)]
16#[serde(rename_all = "camelCase", bound = "H: SportsHydrations")]
17pub struct SportsResponse<H: SportsHydrations> {
18	pub copyright: Copyright,
19	pub sports: Vec<Sport<H>>,
20}
21
22id!(#[doc = "A [`u32`] representing the ID of the sport; `SportId::MLB = 1`"] SportId { id: u32 });
23
24impl SportId {
25	pub const MLB: Self = Self::new(1);
26}
27
28impl Default for SportId {
29	fn default() -> Self {
30		Self::MLB
31	}
32}
33
34/// Returns a [`SportsResponse`]
35#[derive(Builder)]
36#[builder(derive(Into))]
37pub struct SportsRequest<H: SportsHydrations> {
38	#[builder(into)]
39	id: Option<SportId>,
40	#[builder(skip)]
41	_marker: PhantomData<H>,
42}
43
44impl<H: SportsHydrations, S: sports_request_builder::State + sports_request_builder::IsComplete> crate::request::RequestURLBuilderExt for SportsRequestBuilder<H, S> {
45	type Built = SportsRequest<H>;
46}
47
48impl<H: SportsHydrations> Display for SportsRequest<H> {
49	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50		let hydrations = Some(H::hydration_text(&())).filter(|s| !s.is_empty());
51		write!(f, "http://statsapi.mlb.com/api/v1/sports{}", gen_params! { "sportId"?: self.id, "hydrate"?: hydrations })
52	}
53}
54
55impl<H: SportsHydrations> RequestURL for SportsRequest<H> {
56	type Response = SportsResponse<H>;
57}
58
59/// A detailed `struct` representing information about a sport (or Organized Baseball Level)
60///
61/// ## Examples
62/// ```no_run
63/// Sport {
64///     code: "mlb",
65///     name: "Major League Baseball",
66///     abbreviation: "MLB",
67///     active: true,
68///     id: 1,
69/// }
70/// ```
71#[derive(Debug, Deserialize, Clone)]
72#[serde(rename_all = "camelCase", bound = "H: SportsHydrations")]
73pub struct Sport<H: SportsHydrations = ()> {
74	pub code: String,
75	pub name: String,
76	pub abbreviation: String,
77	#[serde(rename = "activeStatus")]
78	pub active: bool,
79	#[serde(flatten)]
80	pub id: SportId,
81	#[serde(flatten)]
82	pub extras: H,
83}
84
85impl<H: SportsHydrations> PartialEq for Sport<H> {
86	fn eq(&self, other: &Self) -> bool {
87		self.id == other.id
88	}
89}
90
91pub trait SportsHydrations: Hydrations<RequestData=()> {}
92
93impl SportsHydrations for () {}
94
95/// Creates hydrations for a sport
96///
97/// ## Examples
98/// ```no_run
99/// sports_hydrations! {
100///     pub struct TestHydrations {
101///         season,
102///     }
103/// }
104///
105/// let response = SportsRequest::<TestHydrations>::builder().build_and_get().await.unwrap();
106/// for sport in response.sports {
107///     dbg!(&sport.extras.season_date_info);
108/// }
109/// ```
110///
111/// ## Sport Hydrations
112/// | Name                         | Type           |
113/// |------------------------------|----------------|
114/// | `season`                     | [`Season`]     |
115///
116/// [`Season`]: crate::season::Season
117#[macro_export]
118macro_rules! sports_hydrations {
119	(@ inline_structs [$field:ident $(: $value:ty)? $(, $($t:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
120		$crate::sports_hydrations! { @ inline_structs [$($($t)*)?] $vis struct $name { $($field_tt)* $field $(: $value)?, } }
121	};
122	(@ inline_structs [$(,)?] $vis:vis struct $name:ident { $($t:tt)* }) => {
123		$crate::sports_hydrations! { @ actual $vis struct $name { $($t)* } }
124	};
125	(@ actual $vis:vis struct $name:ident {
126		$(season $season_comma:tt)?
127	}) => {
128		#[derive(::core::fmt::Debug, ::serde::Deserialize, ::core::cmp::PartialEq, ::core::clone::Clone)]
129		#[serde(rename_all = "camelCase")]
130		$vis struct $name {
131			$(pub season_date_info: $crate::season::Season $season_comma)?
132		}
133
134		impl $crate::sport::SportsHydrations 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					$("season," $season_comma)?
142				))
143			}
144		}
145	};
146    ($vis:vis struct $name:ident { $($t:tt)* }) => {
147	    $crate::sports_hydrations! { @ inline_structs [$($t)*] $vis struct $name {} }
148    };
149}
150
151#[cfg(feature = "cache")]
152static CACHE: RwLock<CacheTable<Sport>> = rwlock_const_new(CacheTable::new());
153
154impl Requestable for Sport {
155	type Identifier = SportId;
156	type URL = SportsRequest<()>;
157
158	fn id(&self) -> &Self::Identifier {
159		&self.id
160	}
161
162	#[cfg(feature = "aggressive_cache")]
163	fn url_for_id(_id: &Self::Identifier) -> Self::URL {
164		SportsRequest::builder().build()
165	}
166
167	#[cfg(not(feature = "aggressive_cache"))]
168	fn url_for_id(id: &Self::Identifier) -> Self::URL {
169		SportsRequest::builder().id(*id).build()
170	}
171
172	fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item=Self>
173	where
174		Self: Sized
175	{
176		response.sports
177	}
178
179	#[cfg(feature = "cache")]
180	fn get_cache_table() -> &'static RwLock<CacheTable<Self>>
181	where
182		Self: Sized
183	{
184		&CACHE
185	}
186}
187
188entrypoint!(SportId => Sport);
189entrypoint!(Sport.id => Sport);
190
191#[cfg(test)]
192mod tests {
193	use super::*;
194	use crate::request::RequestURLBuilderExt;
195
196	#[tokio::test]
197	async fn parse_all_sports() {
198		let _result = SportsRequest::<()>::builder().build_and_get().await.unwrap();
199	}
200
201	#[tokio::test]
202	async fn parse_all_sports_with_hydrations() {
203		sports_hydrations! {
204			pub struct TestHydrations {
205				season
206			}
207		}
208
209		let _result = SportsRequest::<TestHydrations>::builder().build_and_get().await.unwrap();
210	}
211}