Skip to main content

mlb_api/
cache.rs

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    /// # Errors
136    /// See variants of [`crate::request::Error`]
137    #[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/// # Errors
155/// See variants of [`crate::request::Error`]
156#[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/// # Errors
196/// See variants of [`crate::request::Error`]
197#[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}