mlb_api/endpoints/teams/team/uniforms/
mod.rs

1use std::fmt::{Display, Formatter};
2use std::ops::{Deref, DerefMut};
3use derive_more::{Deref, DerefMut, Display, From};
4use itertools::Itertools;
5use serde::Deserialize;
6use strum::EnumTryAs;
7use crate::endpoints::StatsAPIUrl;
8use crate::endpoints::teams::team::TeamId;
9use crate::{gen_params, rwlock_const_new, RwLock};
10use crate::cache::{EndpointEntryCache, HydratedCacheTable};
11use crate::types::Copyright;
12
13#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
14pub struct UniformsResponse {
15    pub copyright: Copyright,
16    #[serde(rename = "uniforms")] pub teams: Vec<TeamUniformAssets>,
17}
18
19#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
20#[serde(rename_all = "camelCase")]
21pub struct TeamUniformAssets {
22    pub team_id: TeamId,
23    pub uniform_assets: Vec<UniformAsset>,
24}
25
26#[derive(Debug, Deserialize, Eq, Clone, From, EnumTryAs)]
27#[serde(untagged)]
28pub enum UniformAsset {
29    Hydrated(HydratedUniformAsset),
30    Identifiable(IdentifiableUniformAsset),
31}
32
33#[repr(transparent)]
34#[derive(Debug, Deserialize, Deref, Display, PartialEq, Eq, Copy, Clone, Hash)]
35pub struct UniformAssetId(u32);
36
37impl UniformAssetId {
38    #[must_use]
39    pub const fn new(id: u32) -> Self {
40        Self(id)
41    }
42}
43
44#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
45pub struct IdentifiableUniformAsset {
46    #[serde(rename = "uniformAssetId")] pub id: UniformAssetId,
47    #[serde(rename = "uniformAssetCode")] pub code: String,
48}
49
50#[derive(Debug, Deserialize, Deref, DerefMut, PartialEq, Eq, Clone)]
51pub struct HydratedUniformAsset {
52    #[serde(rename = "uniformAssetText")] pub name: String,
53    #[serde(rename = "uniformAssetType")] pub category: UniformAssetCategory,
54
55    #[deref]
56    #[deref_mut]
57    #[serde(flatten)]
58    inner: IdentifiableUniformAsset,
59}
60
61#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
62pub struct UniformAssetCategory {
63    #[serde(rename = "uniformAssetTypeText")] pub name: String,
64    #[serde(rename = "uniformAssetTypeCode")] pub code: String,
65    #[serde(rename = "uniformAssetTypeDesc")] pub description: String,
66    #[serde(rename = "uniformAssetTypeId")] pub id: UniformAssetCategoryId,
67}
68
69#[repr(transparent)]
70#[derive(Debug, Deserialize, Deref, Display, PartialEq, Eq, Copy, Clone, Hash)]
71pub struct UniformAssetCategoryId(u32);
72
73impl UniformAssetCategoryId {
74    #[must_use]
75    pub const fn new(id: u32) -> Self {
76        Self(id)
77    }
78}
79
80impl PartialEq for UniformAsset {
81    fn eq(&self, other: &Self) -> bool {
82        self.id == other.id
83    }
84}
85
86impl Deref for UniformAsset {
87    type Target = IdentifiableUniformAsset;
88
89    fn deref(&self) -> &Self::Target {
90        match self {
91            Self::Hydrated(inner) => inner,
92            Self::Identifiable(inner) => inner,
93        }
94    }
95}
96
97impl DerefMut for UniformAsset {
98    fn deref_mut(&mut self) -> &mut Self::Target {
99        match self {
100            Self::Hydrated(inner) => inner,
101            Self::Identifiable(inner) => inner,
102        }
103    }
104}
105
106pub struct UniformsEndpointUrl {
107    pub teams: Vec<TeamId>,
108    pub season: Option<u16>,
109}
110
111impl Display for UniformsEndpointUrl {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        write!(f, "http://statsapi.mlb.com/api/v1/uniforms/team{}", gen_params! { "teamIds": self.teams.iter().copied().join(","), "season"?: self.season })
114    }
115}
116
117impl StatsAPIUrl for UniformsEndpointUrl {
118    type Response = UniformsResponse;
119}
120
121static CACHE: RwLock<HydratedCacheTable<UniformAsset>> = rwlock_const_new(HydratedCacheTable::new());
122
123impl EndpointEntryCache for UniformAsset {
124    type HydratedVariant = HydratedUniformAsset;
125    type Identifier = String;
126    type URL = UniformsEndpointUrl;
127
128    fn into_hydrated_variant(self) -> Option<Self::HydratedVariant> {
129        self.try_as_hydrated()
130    }
131
132    fn id(&self) -> &Self::Identifier {
133        &self.code
134    }
135
136    fn url_for_id(id: &Self::Identifier) -> Self::URL {
137        UniformsEndpointUrl {
138            teams: vec![TeamId::new(id.split_once('_').and_then(|(num, _)| num.parse().ok()).unwrap_or(0))],
139            season: None,
140        }
141    }
142
143    fn get_entries(response: <Self::URL as StatsAPIUrl>::Response) -> impl IntoIterator<Item=Self>
144    where
145        Self: Sized
146    {
147        response.teams.into_iter().flat_map(|team| team.uniform_assets)
148    }
149
150    fn get_hydrated_cache_table() -> &'static RwLock<HydratedCacheTable<Self>>
151    where
152        Self: Sized
153    {
154        &CACHE
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::endpoints::sports::SportId;
161    use crate::endpoints::StatsAPIUrl;
162    use crate::endpoints::teams::team::uniforms::UniformsEndpointUrl;
163    use crate::endpoints::teams::TeamsEndpointUrl;
164
165    #[tokio::test]
166    async fn parse_all_mlb_teams_this_season() {
167        let mlb_teams = TeamsEndpointUrl { sport_id: Some(SportId::MLB), season: None }.get().await.unwrap();
168        let team_ids = mlb_teams.teams.into_iter().map(|team| team.id).collect::<Vec<_>>();
169        for _ in (UniformsEndpointUrl { teams: team_ids, season: None }.get().await.unwrap().teams.into_iter().flat_map(|x| x.uniform_assets).map(|x| x.try_as_hydrated().unwrap())) {}
170    }
171}