1use crate::{MetaRequest, RwLock};
2use crate::request::{RequestURL, RequestURLBuilderExt};
3use fxhash::FxBuildHasher;
4use serde::de::DeserializeOwned;
5use std::collections::HashMap;
6use std::fmt::{Debug, Display};
7use std::hash::Hash;
8use std::sync::Arc;
9use thiserror::Error;
10use crate::person::Person;
11use crate::person::players::PlayersRequest;
12
13pub trait Requestable: 'static + Send + Sync + DeserializeOwned + Debug + Clone + Eq {
14 type Identifier: Clone + Eq + Hash + Display + Sync + Debug;
15 type URL: RequestURL;
16
17 fn id(&self) -> &Self::Identifier;
18
19 fn url_for_id(id: &Self::Identifier) -> Self::URL;
20
21 fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item = Self> where Self: Sized;
22
23 #[cfg(feature = "cache")]
24 fn get_cache_table() -> &'static RwLock<CacheTable<Self>> where Self: Sized;
25}
26
27pub trait RequestableEntrypoint {
28 type Complete: Requestable;
29
30 fn id(&self) -> &<<Self as RequestableEntrypoint>::Complete as Requestable>::Identifier;
31
32 #[cfg(feature = "reqwest")]
33 #[cfg(feature = "cache")]
34 fn as_complete_or_request(&self) -> impl Future<Output = Result<Arc<<Self as RequestableEntrypoint>::Complete>, Error<Self>>>
35 where
36 Self: Sized,
37 { async {
38 let cache_lock = <<Self as RequestableEntrypoint>::Complete as Requestable>::get_cache_table();
39 let id = self.id();
40 let cache = cache_lock.read().await;
41 if let Some(complete_entry) = cache.get(id).cloned() {
42 return Ok(complete_entry);
43 }
44 drop(cache);
45
46 let mut cache = cache_lock.write().await;
47 cache.request_and_add(id).await?;
48 cache.get(id).cloned().ok_or_else(|| Error::NoMatchingVariant(id.clone()))
49 } }
50
51 #[cfg(feature = "ureq")]
52 #[cfg(feature = "cache")]
53 fn as_complete_or_request(&self) -> Result<Arc<<Self as RequestableEntrypoint>::Complete>, Error<Self>>
54 where
55 Self: Sized
56 {
57 let cache_lock = <<Self as RequestableEntrypoint>::Complete as Requestable>::get_cache_table();
58 let id = self.id();
59 let cache = cache_lock.read();
60 if let Some(complete_entry) = cache.get(id).cloned() {
61 return Ok(complete_entry);
62 }
63
64 let mut cache = cache_lock.write();
65 cache.request_and_add(id)?;
66 cache.get(id).cloned().ok_or_else(|| Error::NoMatchingVariant(id.clone()))
67 }
68
69 #[cfg(feature = "reqwest")]
70 #[cfg(not(feature = "cache"))]
71 fn as_complete_or_request(&self) -> impl Future<Output = Result<<Self as RequestableEntrypoint>::Complete, Error<Self>>>
72 where
73 Self: Sized,
74 { async {
75 let id = self.id();
76 let url = <Self::Complete as Requestable>::url_for_id(id).to_string();
77 let response: <<Self::Complete as Requestable>::URL as RequestURL>::Response = crate::request::get::<<<<Self as RequestableEntrypoint>::Complete as Requestable>::URL as RequestURL>::Response>(url).await?;
78 let entries = <Self::Complete as Requestable>::get_entries(response);
79 entries.into_iter().next().ok_or_else(|| Error::<Self>::NoMatchingVariant(id.clone()))
80 } }
81
82 #[cfg(feature = "ureq")]
83 #[cfg(not(feature = "cache"))]
84 fn as_complete_or_request(&self) -> Result<Arc<<Self as RequestableEntrypoint>::Complete>, Error<Self>> {
85 let id = self.id();
86 let url = <Self::Complete as Requestable>::url_for_id(id).to_string();
87 let response = crate::request::get::<<<Self::Complete as Requestable>::URL as RequestURL>::Response>(&url)?;
88 let entries = <Self::Complete as Requestable>::get_entries(response);
89 entries.into_iter().next().ok_or_else(|| Error::<Self>::NoMatchingVariant(id.clone()))
90 }
91}
92
93#[cfg(feature = "cache")]
94pub struct CacheTable<T: Requestable> {
95 cached_values: HashMap<T::Identifier, Arc<T>, FxBuildHasher>,
96}
97
98#[derive(Debug, Error)]
99pub enum Error<T: RequestableEntrypoint> {
100 #[error(transparent)]
101 Url(#[from] crate::request::Error),
102 #[error("No matching entry was found for id {0}")]
103 NoMatchingVariant(<T::Complete as Requestable>::Identifier),
104}
105
106#[cfg(feature = "cache")]
107impl<T: Requestable> CacheTable<T> {
108 #[allow(clippy::new_without_default, reason = "needs to be const")]
109 #[must_use]
110 pub const fn new() -> Self {
111 Self {
112 cached_values: HashMap::with_hasher(FxBuildHasher::new()),
113 }
114 }
115
116 #[must_use]
117 pub fn get(&self, id: &T::Identifier) -> Option<&Arc<T>> {
118 self.cached_values.get(id)
119 }
120
121 pub fn insert(&mut self, value: T) {
122 self.cached_values.insert(value.id().clone(), Arc::new(value));
123 }
124
125 pub fn clear(&mut self) {
126 self.cached_values.clear();
127 }
128
129 pub fn add_entries(&mut self, entries: impl IntoIterator<Item = T>) {
130 for entry in entries {
131 self.insert(entry);
132 }
133 }
134
135 #[cfg(feature = "reqwest")]
138 pub async fn request_and_add(&mut self, id: &T::Identifier) -> Result<(), crate::request::Error> {
139 let url = <T as Requestable>::url_for_id(id).to_string();
140 let response = crate::request::get::<<<T as Requestable>::URL as RequestURL>::Response>(url).await?;
141 self.add_entries(<T as Requestable>::get_entries(response));
142 Ok(())
143 }
144
145 #[cfg(feature = "ureq")]
146 pub fn request_and_add(&mut self, id: &T::Identifier) -> Result<(), crate::request::Error> {
147 let url = <T as Requestable>::url_for_id(id).to_string();
148 let response = crate::request::get::<<<T as Requestable>::URL as RequestURL>::Response>(url)?;
149 self.add_entries(<T as Requestable>::get_entries(response));
150 Ok(())
151 }
152}
153
154#[cfg(feature = "cache")]
157#[cfg(feature = "reqwest")]
158pub async fn precache() -> Result<(), crate::request::Error> {
159 let people_response = PlayersRequest::builder().build_and_get();
160
161 let award_response = crate::awards::AwardRequest::builder().build_and_get();
162 let division_response = crate::divisions::DivisionsRequest::builder().build_and_get();
163 let conference_response = crate::conferences::ConferencesRequest::builder().build_and_get();
164 let venue_response = crate::venue::VenuesRequest::builder().build_and_get();
165 let league_response = crate::league::LeaguesRequest::builder().build_and_get();
166 let sport_response = crate::sport::SportsRequest::builder().build_and_get();
167 <crate::awards::Award as Requestable>::get_cache_table().write().await.add_entries(award_response.await?.awards);
168 <crate::divisions::Division as Requestable>::get_cache_table().write().await.add_entries(division_response.await?.divisions);
169 <crate::conferences::Conference as Requestable>::get_cache_table().write().await.add_entries(conference_response.await?.conferences);
170 <crate::venue::Venue as Requestable>::get_cache_table().write().await.add_entries(venue_response.await?.venues);
171 <crate::league::League as Requestable>::get_cache_table().write().await.add_entries(league_response.await?.leagues);
172 <crate::sport::Sport as Requestable>::get_cache_table().write().await.add_entries(sport_response.await?.sports);
173
174 <crate::baseball_stats::BaseballStat as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::baseball_stats::BaseballStat>::new().get().await?.entries);
175 <crate::job_types::JobType as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::job_types::JobType>::new().get().await?.entries);
176 <crate::event_types::EventType as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::event_types::EventType>::new().get().await?.entries);
177 <crate::game_status::GameStatus as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::game_status::GameStatus>::new().get().await?.entries);
178 <crate::metrics::Metric as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::metrics::Metric>::new().get().await?.entries);
179 <crate::pitch_codes::PitchCode as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::pitch_codes::PitchCode>::new().get().await?.entries);
180 <crate::pitch_types::PitchType as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::pitch_types::PitchType>::new().get().await?.entries);
181 <crate::platforms::Platform as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::platforms::Platform>::new().get().await?.entries);
182 <crate::positions::Position as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::positions::Position>::new().get().await?.entries);
183 <crate::review_reasons::ReviewReason as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::review_reasons::ReviewReason>::new().get().await?.entries);
184 <crate::schedule_event_types::ScheduleEventType as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::schedule_event_types::ScheduleEventType>::new().get().await?.entries);
185 <crate::situations::SituationCode as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::situations::SituationCode>::new().get().await?.entries);
186 <crate::sky::SkyDescription as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::sky::SkyDescription>::new().get().await?.entries);
187 <crate::standings_types::StandingsType as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::standings_types::StandingsType>::new().get().await?.entries);
188 <crate::wind_direction::WindDirection as Requestable>::get_cache_table().write().await.add_entries(MetaRequest::<crate::wind_direction::WindDirection>::new().get().await?.entries);
189
190 <crate::person::Person as Requestable>::get_cache_table().write().await.add_entries(people_response.await?.people.into_iter().map(Box::new).map(Person::Ballplayer));
191
192 Ok(())
193}
194
195#[cfg(feature = "cache")]
198#[cfg(feature = "ureq")]
199pub fn precache() -> Result<(), crate::request::Error> {
200 <crate::awards::Award as Requestable>::get_cache_table().write().add_entries(crate::awards::AwardRequest::builder().build_and_get()?.awards);
201 <crate::divisions::Division as Requestable>::get_cache_table().write().add_entries(crate::divisions::DivisionsRequest::builder().build_and_get()?.divisions);
202 <crate::conferences::Conference as Requestable>::get_cache_table().write().add_entries(crate::conferences::ConferencesRequest::builder().build_and_get()?.conferences);
203 <crate::venue::Venue as Requestable>::get_cache_table().write().add_entries(crate::venue::VenuesRequest::builder().build_and_get()?.venues);
204 <crate::league::League as Requestable>::get_cache_table().write().add_entries(crate::league::LeaguesRequest::builder().build_and_get()?.leagues);
205 <crate::sport::Sport as Requestable>::get_cache_table().write().add_entries(crate::sport::SportsRequest::builder().build_and_get()?.sports);
206
207 <crate::baseball_stats::BaseballStat as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::baseball_stats::BaseballStat>::new().get()?.entries);
208 <crate::job_types::JobType as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::job_types::JobType>::new().get()?.entries);
209 <crate::event_types::EventType as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::event_types::EventType>::new().get()?.entries);
210 <crate::game_status::GameStatus as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::game_status::GameStatus>::new().get()?.entries);
211 <crate::metrics::Metric as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::metrics::Metric>::new().get()?.entries);
212 <crate::pitch_codes::PitchCode as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::pitch_codes::PitchCode>::new().get()?.entries);
213 <crate::pitch_types::PitchType as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::pitch_types::PitchType>::new().get()?.entries);
214 <crate::platforms::Platform as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::platforms::Platform>::new().get()?.entries);
215 <crate::positions::Position as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::positions::Position>::new().get()?.entries);
216 <crate::review_reasons::ReviewReason as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::review_reasons::ReviewReason>::new().get()?.entries);
217 <crate::schedule_event_types::ScheduleEventType as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::schedule_event_types::ScheduleEventType>::new().get()?.entries);
218 <crate::situations::SituationCode as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::situations::SituationCode>::new().get()?.entries);
219 <crate::sky::SkyDescription as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::sky::SkyDescription>::new().get()?.entries);
220 <crate::standings_types::StandingsType as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::standings_types::StandingsType>::new().get()?.entries);
221 <crate::wind_direction::WindDirection as Requestable>::get_cache_table().write().add_entries(MetaRequest::<crate::wind_direction::WindDirection>::new().get()?.entries);
222
223 <crate::person::Person as Requestable>::get_cache_table().write().add_entries(PlayersRequest::builder().build_and_get()?.people);
224
225 Ok(())
226}