mlb_api/endpoints/league/
mod.rs

1pub mod all_star_ballot;
2pub mod all_star_final_vote;
3pub mod all_star_write_ins;
4
5use std::fmt::{Display, Formatter};
6use crate::endpoints::seasons::season::{Season, SeasonState};
7use crate::endpoints::sports::{NamedSport, SportId};
8use derive_more::{Deref, DerefMut, Display, From};
9use serde::Deserialize;
10use std::ops::{Deref, DerefMut};
11use itertools::Itertools;
12use strum::EnumTryAs;
13use crate::endpoints::StatsAPIUrl;
14use crate::{gen_params, rwlock_const_new, RwLock};
15use crate::cache::{EndpointEntryCache, HydratedCacheTable};
16
17#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
18#[serde(rename_all = "camelCase")]
19pub struct LeagueResponse {
20	pub copyright: String,
21	pub leagues: Vec<League>,
22}
23
24#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
25#[serde(rename_all = "camelCase")]
26pub struct IdentifiableLeague {
27	pub id: LeagueId,
28}
29
30impl Default for IdentifiableLeague {
31	fn default() -> Self {
32		Self { id: LeagueId(0) }
33	}
34}
35
36#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
37#[serde(rename_all = "camelCase")]
38pub struct NamedLeague {
39	pub name: String,
40
41	#[deref]
42	#[deref_mut]
43	#[serde(flatten)]
44	inner: IdentifiableLeague,
45}
46
47impl Default for NamedLeague {
48	fn default() -> Self {
49		Self {
50			name: "null".to_owned(),
51			inner: IdentifiableLeague::default(),
52		}
53	}
54}
55
56#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
57#[serde(rename_all = "camelCase")]
58pub struct HydratedLeague {
59	pub abbreviation: String,
60	#[serde(rename = "nameShort")]
61	pub short_name: Option<String>,
62	#[serde(rename = "orgCode")]
63	pub code: String,
64	pub season_state: SeasonState,
65	#[serde(flatten, deserialize_with = "bad_league_season_schema_deserializer")]
66	#[serde(rename = "seasonDateInfo")]
67	pub season: Season,
68	#[serde(default)]
69	pub has_split_season: bool,
70	pub num_games: u8,
71	pub has_playoff_points: Option<bool>,
72	pub num_teams: u8,
73	pub num_wildcard_teams: Option<u8>,
74	#[serde(rename = "conferencesInUse")]
75	pub has_conferences: bool,
76	#[serde(rename = "divisionsInUse")]
77	pub has_divisions: bool,
78	pub sport: Option<NamedSport>,
79	pub active: bool,
80
81	#[deref]
82	#[deref_mut]
83	#[serde(flatten)]
84	inner: NamedLeague,
85}
86
87// this is annoying me that it exists
88#[derive(Deserialize)]
89struct BadLeagueSeasonSchema {
90	#[serde(rename = "hasWildCard")]
91	has_wildcard: bool,
92	#[serde(rename = "seasonDateInfo")]
93	rest: Season,
94}
95
96fn bad_league_season_schema_deserializer<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Season, D::Error> {
97	let BadLeagueSeasonSchema { has_wildcard, mut rest } = BadLeagueSeasonSchema::deserialize(deserializer)?;
98	rest.has_wildcard = has_wildcard;
99	Ok(rest)
100}
101
102#[repr(transparent)]
103#[derive(Debug, Deserialize, Deref, Display, PartialEq, Eq, Copy, Clone, Hash)]
104pub struct LeagueId(u32);
105
106impl LeagueId {
107	#[must_use]
108	pub const fn new(id: u32) -> Self {
109		Self(id)
110	}
111}
112
113#[derive(Debug, Deserialize, Eq, Clone, From, EnumTryAs)]
114#[serde(untagged)]
115pub enum League {
116	Hydrated(HydratedLeague),
117	Named(NamedLeague),
118	Identifiable(IdentifiableLeague),
119}
120
121impl League {
122	#[must_use]
123	pub(crate) const fn unknown_league() -> Self {
124		Self::Identifiable(IdentifiableLeague { id: LeagueId::new(0) })
125	}
126}
127
128impl PartialEq for League {
129	fn eq(&self, other: &Self) -> bool {
130		self.id == other.id
131	}
132}
133
134impl Deref for League {
135	type Target = IdentifiableLeague;
136
137	fn deref(&self) -> &Self::Target {
138		match self {
139			Self::Hydrated(inner) => inner,
140			Self::Named(inner) => inner,
141			Self::Identifiable(inner) => inner,
142		}
143	}
144}
145
146impl DerefMut for League {
147	fn deref_mut(&mut self) -> &mut Self::Target {
148		match self {
149			Self::Hydrated(inner) => inner,
150			Self::Named(inner) => inner,
151			Self::Identifiable(inner) => inner,
152		}
153	}
154}
155
156pub struct LeagueEndpointUrl {
157	pub sport_id: Option<SportId>,
158	pub league_ids: Option<Vec<LeagueId>>,
159}
160
161impl Display for LeagueEndpointUrl {
162	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
163		write!(f, "http://statsapi.mlb.com/api/v1/leagues{}", gen_params! {
164			"sportId"?: self.sport_id,
165			"leagueIds"?: self.league_ids.as_ref().map(|ids| ids.iter().copied().join(",")),
166		})
167	}
168}
169
170impl StatsAPIUrl for LeagueEndpointUrl {
171	type Response = LeagueResponse;
172}
173
174static CACHE: RwLock<HydratedCacheTable<League>> = rwlock_const_new(HydratedCacheTable::new());
175
176impl EndpointEntryCache for League {
177	type HydratedVariant = HydratedLeague;
178	type Identifier = LeagueId;
179	type URL = LeagueEndpointUrl;
180
181	fn into_hydrated_variant(self) -> Option<Self::HydratedVariant> {
182		self.try_as_hydrated()
183	}
184
185	fn id(&self) -> &Self::Identifier {
186		&self.id
187	}
188
189	fn url_for_id(id: &Self::Identifier) -> Self::URL {
190		LeagueEndpointUrl {
191			sport_id: None,
192			league_ids: Some(vec![id.clone()]),
193		}
194	}
195
196	fn get_entries(response: <Self::URL as StatsAPIUrl>::Response) -> impl IntoIterator<Item=Self>
197	where
198		Self: Sized
199	{
200		response.leagues
201	}
202
203	fn get_hydrated_cache_table() -> &'static RwLock<HydratedCacheTable<Self>>
204	where
205		Self: Sized
206	{
207		&CACHE
208	}
209}