Skip to main content

mlb_api/requests/
league.rs

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