Skip to content

Commit 03b6bf8

Browse files
committed
Cache team API calls for two minutes in memory
1 parent 1bb3966 commit 03b6bf8

File tree

1 file changed

+62
-10
lines changed

1 file changed

+62
-10
lines changed

src/team_data.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
use anyhow::Context as _;
21
use reqwest::Client;
32
use rust_team_data::v1::{People, Teams, ZulipMapping, BASE_URL};
43
use serde::de::DeserializeOwned;
4+
use std::sync::Arc;
5+
use std::time::{Duration, Instant};
6+
use tokio::sync::RwLock;
57

68
#[derive(Clone)]
79
pub struct TeamApiClient {
810
base_url: String,
911
client: Client,
12+
teams: CachedTeamItem<Teams>,
13+
people: CachedTeamItem<People>,
14+
zulip_mapping: CachedTeamItem<ZulipMapping>,
1015
}
1116

1217
impl TeamApiClient {
@@ -19,6 +24,9 @@ impl TeamApiClient {
1924
Self {
2025
base_url,
2126
client: Client::new(),
27+
teams: CachedTeamItem::new("/teams.json"),
28+
people: CachedTeamItem::new("/people.json"),
29+
zulip_mapping: CachedTeamItem::new("/zulip-map.json"),
2230
}
2331
}
2432

@@ -84,24 +92,68 @@ impl TeamApiClient {
8492
}
8593

8694
pub async fn zulip_map(&self) -> anyhow::Result<ZulipMapping> {
87-
download(&self.client, &self.base_url, "/zulip-map.json")
88-
.await
89-
.context("team-api: zulip-map.json")
95+
self.zulip_mapping.get(&self.client, &self.base_url).await
9096
}
9197

9298
pub async fn teams(&self) -> anyhow::Result<Teams> {
93-
download(&self.client, &self.base_url, "/teams.json")
94-
.await
95-
.context("team-api: teams.json")
99+
self.teams.get(&self.client, &self.base_url).await
96100
}
97101

98102
pub async fn people(&self) -> anyhow::Result<People> {
99-
download(&self.client, &self.base_url, "/people.json")
100-
.await
101-
.context("team-api: people.json")
103+
self.people.get(&self.client, &self.base_url).await
102104
}
103105
}
104106

107+
/// How long should downloaded team data items be cached in memory.
108+
const CACHE_DURATION: Duration = Duration::from_secs(2 * 60);
109+
110+
#[derive(Clone)]
111+
struct CachedTeamItem<T> {
112+
value: Arc<RwLock<CachedValue<T>>>,
113+
url_path: String,
114+
}
115+
116+
impl<T: DeserializeOwned + Clone> CachedTeamItem<T> {
117+
fn new(url_path: &str) -> Self {
118+
Self {
119+
value: Arc::new(RwLock::new(CachedValue::Empty)),
120+
url_path: url_path.to_string(),
121+
}
122+
}
123+
124+
async fn get(&self, client: &Client, base_url: &str) -> anyhow::Result<T> {
125+
let now = Instant::now();
126+
{
127+
let value = self.value.read().await;
128+
if let CachedValue::Present {
129+
value,
130+
last_download,
131+
} = &*value
132+
{
133+
if *last_download + CACHE_DURATION > now {
134+
return Ok(value.clone());
135+
}
136+
}
137+
}
138+
match download::<T>(client, base_url, &self.url_path).await {
139+
Ok(v) => {
140+
let mut value = self.value.write().await;
141+
*value = CachedValue::Present {
142+
value: v.clone(),
143+
last_download: Instant::now(),
144+
};
145+
Ok(v)
146+
}
147+
Err(e) => Err(e),
148+
}
149+
}
150+
}
151+
152+
enum CachedValue<T> {
153+
Empty,
154+
Present { value: T, last_download: Instant },
155+
}
156+
105157
async fn download<T: DeserializeOwned>(
106158
client: &Client,
107159
base_url: &str,

0 commit comments

Comments
 (0)