Skip to main content

mlb_api/requests/
league.rs

1//! Leagues; AL, NL, etc.
2
3use crate::sport::SportId;
4use crate::request::{RequestURL, RequestURLBuilderExt};
5use bon::Builder;
6use derive_more::{Deref, DerefMut};
7use itertools::Itertools;
8use serde::Deserialize;
9use std::fmt::{Display, Formatter};
10use std::hash::{Hash, Hasher};
11use crate::cache::Requestable;
12use crate::season::{Season, SeasonId, SeasonState};
13
14#[cfg(feature = "cache")]
15use crate::{rwlock_const_new, RwLock, cache::CacheTable};
16
17/// Returns a [`Vec`] of [`League`]s.
18#[derive(Debug, Deserialize, PartialEq, Clone)]
19#[serde(rename_all = "camelCase")]
20pub struct LeagueResponse {
21	pub copyright: String,
22	pub leagues: Vec<League>,
23}
24
25/// A league with a name
26#[derive(Debug, Deserialize, Clone, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct NamedLeague {
29	pub name: String,
30	#[serde(flatten)]
31	pub id: LeagueId,
32}
33
34impl Hash for NamedLeague {
35	fn hash<H: Hasher>(&self, state: &mut H) {
36		self.id.hash(state);
37	}
38}
39
40/// A complete league with all it's information.
41#[allow(clippy::struct_excessive_bools, reason = "false positive")]
42#[derive(Debug, Deserialize, Deref, DerefMut, Clone)]
43#[serde(rename_all = "camelCase")]
44pub struct League {
45	pub abbreviation: String,
46	#[serde(rename = "nameShort")]
47	pub short_name: Option<String>,
48	#[serde(rename = "orgCode")]
49	pub code: String,
50	pub season_state: SeasonState,
51	#[serde(flatten, deserialize_with = "bad_league_season_schema_deserializer")]
52	#[serde(rename = "seasonDateInfo")]
53	pub season: Season,
54	#[serde(default)]
55	pub has_split_season: bool,
56	pub num_games: u8,
57	pub has_playoff_points: Option<bool>,
58	pub num_teams: u8,
59	pub num_wildcard_teams: Option<u8>,
60	#[serde(rename = "conferencesInUse")]
61	pub has_conferences: bool,
62	#[serde(rename = "divisionsInUse")]
63	pub has_divisions: bool,
64	pub sport: Option<SportId>,
65	pub active: bool,
66
67	#[deref]
68	#[deref_mut]
69	#[serde(flatten)]
70	inner: NamedLeague,
71}
72
73/// this is annoying me that it exists
74#[derive(Deserialize)]
75struct BadLeagueSeasonSchema {
76	#[serde(rename = "hasWildCard")]
77	has_wildcard: bool,
78	#[serde(rename = "seasonDateInfo")]
79	rest: Season,
80}
81
82fn bad_league_season_schema_deserializer<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Season, D::Error> {
83	let BadLeagueSeasonSchema { has_wildcard, mut rest } = BadLeagueSeasonSchema::deserialize(deserializer)?;
84	rest.has_wildcard = has_wildcard;
85	Ok(rest)
86}
87
88id!(#[doc = "A [`u32`] representing an ID of a league"] LeagueId { id: u32 });
89id_only_eq_impl!(League, id);
90id_only_eq_impl!(NamedLeague, id);
91
92impl NamedLeague {
93	#[must_use]
94	pub(crate) fn unknown_league() -> Self {
95		Self {
96			name: "null".to_owned(),
97			id: LeagueId::new(0),
98		}
99	}
100
101	#[must_use]
102	pub fn is_unknown(&self) -> bool {
103		*self.id == 0
104	}
105}
106
107/// Returns a [`LeaguesResponse`].
108#[derive(Builder)]
109#[builder(derive(Into))]
110pub struct LeaguesRequest {
111	#[builder(into)]
112	sport_id: Option<SportId>,
113	#[builder(setters(vis = "", name = league_ids_internal))]
114	league_ids: Option<Vec<LeagueId>>,
115	#[builder(into, default)]
116	season: SeasonId,
117}
118
119impl<S: leagues_request_builder::State + leagues_request_builder::IsComplete> RequestURLBuilderExt for LeaguesRequestBuilder<S> {
120	type Built = LeaguesRequest;
121}
122
123impl<S: leagues_request_builder::State> LeaguesRequestBuilder<S> {
124	#[allow(dead_code, reason = "could be used by the end user")]
125	pub fn league_ids<T: Into<LeagueId>>(self, league_ids: Vec<T>) -> LeaguesRequestBuilder<leagues_request_builder::SetLeagueIds<S>> where S::LeagueIds: leagues_request_builder::IsUnset {
126		self.league_ids_internal(league_ids.into_iter().map(T::into).collect::<Vec<_>>())
127	}
128
129	#[allow(dead_code, reason = "could be used by the end user")]
130	pub fn league_id(self, league_id: impl Into<LeagueId>) -> LeaguesRequestBuilder<leagues_request_builder::SetLeagueIds<S>> where S::LeagueIds: leagues_request_builder::IsUnset {
131		self.league_ids_internal(vec![league_id.into()])
132	}
133}
134
135impl Display for LeaguesRequest {
136	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137		write!(f, "http://statsapi.mlb.com/api/v1/leagues{}", gen_params! {
138			"sportId"?: self.sport_id,
139			"leagueIds"?: self.league_ids.as_ref().map(|ids| ids.iter().copied().join(",")),
140			"season": self.season,
141		})
142	}
143}
144
145impl RequestURL for LeaguesRequest {
146	type Response = LeagueResponse;
147}
148
149#[cfg(feature = "cache")]
150static CACHE: RwLock<CacheTable<League>> = rwlock_const_new(CacheTable::new());
151
152impl Requestable for League {
153	type Identifier = LeagueId;
154	type URL = LeaguesRequest;
155
156	fn id(&self) -> &Self::Identifier {
157		&self.id
158	}
159
160	#[cfg(feature = "aggressive_cache")]
161	fn url_for_id(_id: &Self::Identifier) -> Self::URL {
162		LeaguesRequest::builder().build()
163	}
164
165	#[cfg(not(feature = "aggressive_cache"))]
166	fn url_for_id(id: &Self::Identifier) -> Self::URL {
167		LeaguesRequest::builder().league_ids_internal(vec![*id]).build()
168	}
169
170	fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item=Self>
171	where
172		Self: Sized
173	{
174		response.leagues
175	}
176
177	#[cfg(feature = "cache")]
178	fn get_cache_table() -> &'static RwLock<CacheTable<Self>>
179	where
180		Self: Sized
181	{
182		&CACHE
183	}
184}
185
186entrypoint!(LeagueId => League);
187entrypoint!(NamedLeague.id => League);
188entrypoint!(League.id => League);