mlb_api/requests/
league.rs1use 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#[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#[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#[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#[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#[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);