1use std::collections::HashMap;
2use std::sync::Mutex;
3use std::time::{Duration, Instant};
4
5use crate::{client::JiraClient, error::Result, model::field::Field};
6
7const TTL: Duration = Duration::from_secs(300); struct CacheEntry {
10 fields: Vec<Field>,
11 fetched_at: Instant,
12}
13
14impl CacheEntry {
15 fn is_fresh(&self) -> bool {
16 self.fetched_at.elapsed() < TTL
17 }
18}
19
20pub struct FieldCache {
25 entries: Mutex<HashMap<String, CacheEntry>>,
26}
27
28impl FieldCache {
29 pub fn new() -> Self {
30 Self {
31 entries: Mutex::new(HashMap::new()),
32 }
33 }
34
35 pub async fn get_or_fetch(
37 &self,
38 client: &JiraClient,
39 project_key: &str,
40 issue_type_id: &str,
41 ) -> Result<Vec<Field>> {
42 let key = format!("{project_key}:{issue_type_id}");
43
44 {
45 let entries = self.entries.lock().expect("FieldCache mutex poisoned");
46 if let Some(entry) = entries.get(&key) {
47 if entry.is_fresh() {
48 return Ok(entry.fields.clone());
49 }
50 }
51 }
52
53 let fields = client
54 .get_fields_for_issue_type(project_key, issue_type_id)
55 .await?;
56
57 {
58 let mut entries = self.entries.lock().expect("FieldCache mutex poisoned");
59 entries.insert(
60 key,
61 CacheEntry {
62 fields: fields.clone(),
63 fetched_at: Instant::now(),
64 },
65 );
66 }
67
68 Ok(fields)
69 }
70}
71
72impl Default for FieldCache {
73 fn default() -> Self {
74 Self::new()
75 }
76}