1use std::collections::HashMap;
10use std::sync::Mutex;
11
12use crate::helper::{Credentials, Helper, HelperError};
13use crate::query::Query;
14
15#[derive(Debug, Default)]
22pub struct CachingHelper {
23 cache: Mutex<HashMap<Query, Credentials>>,
24}
25
26impl CachingHelper {
27 pub fn new() -> Self {
29 Self::default()
30 }
31}
32
33impl Helper for CachingHelper {
34 fn fill(&self, query: &Query) -> Result<Option<Credentials>, HelperError> {
35 Ok(self.cache.lock().unwrap().get(query).cloned())
36 }
37
38 fn approve(&self, query: &Query, creds: &Credentials) -> Result<(), HelperError> {
41 self.cache
42 .lock()
43 .unwrap()
44 .insert(query.clone(), creds.clone());
45 Ok(())
46 }
47
48 fn reject(&self, query: &Query, _creds: &Credentials) -> Result<(), HelperError> {
50 self.cache.lock().unwrap().remove(query);
51 Ok(())
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 fn q() -> Query {
60 Query {
61 protocol: "https".into(),
62 host: "git.example.com".into(),
63 path: String::new(),
64 }
65 }
66
67 #[test]
68 fn fill_misses_until_approve() {
69 let h = CachingHelper::new();
70 assert!(h.fill(&q()).unwrap().is_none());
71 let c = Credentials::new("alice", "hunter2");
72 h.approve(&q(), &c).unwrap();
73 assert_eq!(h.fill(&q()).unwrap(), Some(c));
74 }
75
76 #[test]
77 fn reject_evicts() {
78 let h = CachingHelper::new();
79 let c = Credentials::new("alice", "hunter2");
80 h.approve(&q(), &c).unwrap();
81 h.reject(&q(), &c).unwrap();
82 assert!(h.fill(&q()).unwrap().is_none());
83 }
84
85 #[test]
86 fn fill_keys_on_full_query_tuple() {
87 let h = CachingHelper::new();
88 let c = Credentials::new("alice", "hunter2");
89 h.approve(&q(), &c).unwrap();
90 let other = Query {
91 protocol: "https".into(),
92 host: "other.example.com".into(),
93 path: String::new(),
94 };
95 assert!(h.fill(&other).unwrap().is_none());
96 }
97}