Skip to main content

jira_core/
field_cache.rs

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); // 5 minutes
8
9struct 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
20/// In-memory cache for Jira field metadata, keyed by "project:issue_type_id".
21///
22/// Safe to share across tokio tasks: interior mutability via `Mutex`. The lock
23/// is never held across an `.await`.
24pub 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    /// Return cached fields or fetch from the API if stale / missing.
36    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}