aws_secretsmanager_cache/
cache.rs1use std::num::NonZeroUsize;
2
3use super::cache_item::CacheItem;
4use super::config::CacheConfig;
5use aws_sdk_config::error::SdkError;
6use aws_sdk_secretsmanager::operation::get_secret_value::GetSecretValueError;
7use aws_sdk_secretsmanager::Client as SecretsManagerClient;
8use lru::LruCache;
9
10pub struct SecretCache {
15 client: SecretsManagerClient,
16 config: CacheConfig,
17 cache: LruCache<String, CacheItem<String>>,
18}
19
20impl SecretCache {
21 pub fn new(client: SecretsManagerClient) -> Self {
23 SecretCache::new_cache(client, CacheConfig::new())
24 }
25
26 pub fn new_with_config(client: SecretsManagerClient, config: CacheConfig) -> Self {
28 SecretCache::new_cache(client, config)
29 }
30
31 fn new_cache(client: SecretsManagerClient, config: CacheConfig) -> Self {
32 let cache = LruCache::new(
33 NonZeroUsize::new(config.max_cache_size)
34 .unwrap_or(NonZeroUsize::new(1).expect("Default max_cache_size must be non-zero")),
35 );
36 Self {
37 client,
38 config,
39 cache,
40 }
41 }
42
43 pub fn get_secret_string(&mut self, secret_id: String) -> GetSecretStringBuilder {
47 GetSecretStringBuilder::new(self, secret_id)
48 }
49}
50
51pub struct GetSecretStringBuilder<'a> {
53 secret_cache: &'a mut SecretCache,
54 secret_id: String,
55 force_refresh: bool,
56}
57
58impl<'a> GetSecretStringBuilder<'a> {
59 pub fn new(secret_cache: &'a mut SecretCache, secret_id: String) -> Self {
60 GetSecretStringBuilder {
61 secret_cache,
62 secret_id,
63 force_refresh: false,
64 }
65 }
66
67 pub fn force_refresh(mut self) -> Self {
72 self.force_refresh = true;
73 self
74 }
75
76 pub async fn send(&mut self) -> Result<String, SdkError<GetSecretValueError>> {
86 if !self.force_refresh {
87 if let Some(cache_item) = self.secret_cache.cache.get(&self.secret_id) {
88 if !cache_item.is_expired() {
89 return Ok(cache_item.value.clone());
90 }
91 }
92 }
93
94 match self.fetch_secret().await {
95 Ok(secret_value) => {
96 let cache_item = CacheItem::new(
97 secret_value.clone(),
98 self.secret_cache.config.cache_item_ttl,
99 );
100 self.secret_cache
101 .cache
102 .put(self.secret_id.clone(), cache_item);
103 Ok(secret_value)
104 }
105 Err(e) => Err(e),
106 }
107 }
108
109 async fn fetch_secret(&mut self) -> Result<String, SdkError<GetSecretValueError>> {
110 match self
111 .secret_cache
112 .client
113 .get_secret_value()
114 .secret_id(self.secret_id.clone())
115 .version_stage(self.secret_cache.config.version_stage.clone())
116 .send()
117 .await
118 {
119 Ok(resp) => return Ok(resp.secret_string.as_deref().unwrap().to_string()),
120 Err(e) => Err(e),
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use aws_sdk_config::config::{Credentials, Region};
129 use aws_sdk_secretsmanager::{Client as SecretsManagerClient, Config};
130
131 #[test]
132 fn get_secret_string_builder_defaults() {
133 let mock_secrets_manager_client = get_mock_secretsmanager_client();
134 let mut secret_cache = SecretCache::new(mock_secrets_manager_client);
135
136 let builder = GetSecretStringBuilder::new(&mut secret_cache, "service/secret".to_string());
137
138 assert_eq!(builder.secret_id, "service/secret");
139 assert!(!builder.force_refresh);
140 }
141
142 #[test]
143 fn get_secret_string_builder_force_refresh() {
144 let mock_secrets_manager_client = get_mock_secretsmanager_client();
145 let mut secret_cache = SecretCache::new(mock_secrets_manager_client);
146
147 let builder = GetSecretStringBuilder::new(&mut secret_cache, "service/secret".to_string())
148 .force_refresh();
149
150 assert_eq!(builder.secret_id, "service/secret");
151 assert!(builder.force_refresh);
152 }
153
154 fn get_mock_secretsmanager_client() -> SecretsManagerClient {
156 let conf = Config::builder()
157 .region(Region::new("ap-southeast-2"))
158 .credentials_provider(Credentials::new("asdf", "asdf", None, None, "test"))
159 .build();
160
161 SecretsManagerClient::from_conf(conf)
162 }
163}