mlb_api/endpoints/league/
mod.rs1pub 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#[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}