mlb_api/endpoints/sports/
mod.rs

1pub mod players;
2
3use crate::endpoints::StatsAPIUrl;
4use crate::{gen_params, rwlock_const_new, RwLock};
5use crate::types::Copyright;
6use derive_more::{Deref, DerefMut, Display, From};
7use serde::Deserialize;
8use std::fmt::{Display, Formatter};
9use std::ops::{Deref, DerefMut};
10use strum::EnumTryAs;
11use crate::cache::{EndpointEntryCache, HydratedCacheTable};
12
13#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
14#[serde(rename_all = "camelCase")]
15pub struct SportsResponse {
16	pub copyright: Copyright,
17	pub sports: Vec<Sport>,
18}
19
20#[repr(transparent)]
21#[derive(Debug, Deserialize, Deref, Display, PartialEq, Eq, Copy, Clone, Hash)]
22pub struct SportId(pub(super) u32);
23
24impl SportId {
25	#[must_use]
26	pub const fn new(id: u32) -> Self {
27		Self(id)
28	}
29
30	/// This is here because we can rest assured that it won't ever go away.
31	pub const MLB: Self = Self::new(1);
32}
33
34impl Default for SportId {
35	fn default() -> Self {
36		Self(1)
37	}
38}
39
40pub struct SportsEndpointUrl {
41	pub id: Option<SportId>,
42}
43
44impl Display for SportsEndpointUrl {
45	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46		write!(f, "http://statsapi.mlb.com/api/v1/sports{}", gen_params! { "sportId"?: self.id })
47	}
48}
49
50impl StatsAPIUrl for SportsEndpointUrl {
51	type Response = SportsResponse;
52}
53
54#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
55#[serde(rename_all = "camelCase")]
56pub struct IdentifiableSport {
57	pub id: SportId,
58}
59
60#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
61#[serde(rename_all = "camelCase")]
62pub struct NamedSport {
63	pub name: String,
64
65	#[deref]
66	#[deref_mut]
67	#[serde(flatten)]
68	pub(super) inner: IdentifiableSport,
69}
70
71#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
72#[serde(rename_all = "camelCase")]
73pub struct HydratedSport {
74	pub code: String,
75	pub abbreviation: String,
76	#[serde(rename = "activeStatus")]
77	pub active: bool,
78
79	#[deref]
80	#[deref_mut]
81	#[serde(flatten)]
82	pub(super) inner: NamedSport,
83}
84
85#[derive(Debug, Deserialize, Eq, Clone, From, EnumTryAs)]
86#[serde(untagged)]
87pub enum Sport {
88	Hydrated(HydratedSport),
89	Named(NamedSport),
90	Identifiable(IdentifiableSport),
91}
92
93impl PartialEq for Sport {
94	fn eq(&self, other: &Self) -> bool {
95		self.id == other.id
96	}
97}
98
99impl Deref for Sport {
100	type Target = IdentifiableSport;
101
102	fn deref(&self) -> &Self::Target {
103		match self {
104			Self::Hydrated(inner) => inner,
105			Self::Named(inner) => inner,
106			Self::Identifiable(inner) => inner,
107		}
108	}
109}
110
111impl DerefMut for Sport {
112	fn deref_mut(&mut self) -> &mut Self::Target {
113		match self {
114			Self::Hydrated(inner) => inner,
115			Self::Named(inner) => inner,
116			Self::Identifiable(inner) => inner,
117		}
118	}
119}
120
121static CACHE: RwLock<HydratedCacheTable<Sport>> = rwlock_const_new(HydratedCacheTable::new());
122
123impl EndpointEntryCache for Sport {
124	type HydratedVariant = HydratedSport;
125	type Identifier = SportId;
126	type URL = SportsEndpointUrl;
127
128	fn into_hydrated_variant(self) -> Option<Self::HydratedVariant> {
129		self.try_as_hydrated()
130	}
131
132	fn id(&self) -> &Self::Identifier {
133		&self.id
134	}
135
136	fn url_for_id(id: &Self::Identifier) -> Self::URL {
137		SportsEndpointUrl {
138			id: Some(id.clone()),
139		}
140	}
141
142	fn get_entries(response: <Self::URL as StatsAPIUrl>::Response) -> impl IntoIterator<Item=Self>
143	where
144		Self: Sized
145	{
146		response.sports
147	}
148
149	fn get_hydrated_cache_table() -> &'static RwLock<HydratedCacheTable<Self>>
150	where
151		Self: Sized
152	{
153		&CACHE
154	}
155}
156
157#[cfg(test)]
158mod tests {
159	use super::*;
160	use crate::endpoints::StatsAPIUrl;
161
162	#[tokio::test]
163	async fn parse_all_sports() {
164		let _result = SportsEndpointUrl { id: None }.get().await.unwrap();
165	}
166}