mlb_api/endpoints/teams/team/uniforms/
mod.rs1use 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}