Skip to main content

shiplog_cache/
key.rs

1//! Canonical key builders for shiplog API caching.
2//!
3//! This crate intentionally has one responsibility:
4//! constructing stable cache-key strings for API request shapes.
5
6/// Cache key builder for API requests.
7pub struct CacheKey;
8
9impl CacheKey {
10    /// Create a key for a search query.
11    #[must_use]
12    pub fn search(query: &str, page: u32, per_page: u32) -> String {
13        format!(
14            "search:{}:page{}:per{}",
15            Self::hash_query(query),
16            page,
17            per_page
18        )
19    }
20
21    /// Create a key for pull request details.
22    #[must_use]
23    pub fn pr_details(pr_api_url: &str) -> String {
24        format!("pr:details:{pr_api_url}")
25    }
26
27    /// Create a key for pull request reviews.
28    #[must_use]
29    pub fn pr_reviews(pr_api_url: &str, page: u32) -> String {
30        format!("pr:reviews:{pr_api_url}:page{page}")
31    }
32
33    /// Create a key for GitLab merge-request notes.
34    #[must_use]
35    pub fn mr_notes(project_id: u64, mr_iid: u64, page: u32) -> String {
36        format!("gitlab:mr:notes:project{project_id}:mr{mr_iid}:page{page}")
37    }
38
39    fn hash_query(query: &str) -> String {
40        use std::collections::hash_map::DefaultHasher;
41        use std::hash::{Hash, Hasher};
42
43        let mut hasher = DefaultHasher::new();
44        query.hash(&mut hasher);
45        format!("{:x}", hasher.finish())
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::CacheKey;
52
53    #[test]
54    fn search_key_has_expected_shape() {
55        let key = CacheKey::search("is:pr author:octocat", 3, 100);
56        assert!(key.starts_with("search:"));
57        assert!(key.ends_with(":page3:per100"));
58    }
59
60    #[test]
61    fn search_key_is_stable_for_same_input() {
62        let k1 = CacheKey::search("is:pr author:octocat", 1, 100);
63        let k2 = CacheKey::search("is:pr author:octocat", 1, 100);
64        assert_eq!(k1, k2);
65    }
66
67    #[test]
68    fn pr_details_key_has_expected_prefix() {
69        let key = CacheKey::pr_details("https://api.github.com/repos/o/r/pulls/1");
70        assert_eq!(key, "pr:details:https://api.github.com/repos/o/r/pulls/1");
71    }
72
73    #[test]
74    fn pr_reviews_key_has_expected_prefix_and_page() {
75        let key = CacheKey::pr_reviews("https://api.github.com/repos/o/r/pulls/1", 4);
76        assert_eq!(
77            key,
78            "pr:reviews:https://api.github.com/repos/o/r/pulls/1:page4"
79        );
80    }
81
82    #[test]
83    fn mr_notes_key_has_expected_segments() {
84        let key = CacheKey::mr_notes(42, 7, 2);
85        assert_eq!(key, "gitlab:mr:notes:project42:mr7:page2");
86    }
87}