1use async_trait::async_trait;
7
8use super::{DbTransactionFinalizer, Error};
9
10pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str =
12 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
13
14pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
16
17pub fn validate_kvstore_string(s: &str) -> Result<(), Error> {
19 if s.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN {
20 return Err(Error::KVStoreInvalidKey(format!(
21 "{KVSTORE_NAMESPACE_KEY_MAX_LEN} exceeds maximum length of key characters"
22 )));
23 }
24
25 if !s
26 .chars()
27 .all(|c| KVSTORE_NAMESPACE_KEY_ALPHABET.contains(c))
28 {
29 return Err(Error::KVStoreInvalidKey("key contains invalid characters. Only ASCII letters, numbers, underscore, and hyphen are allowed".to_string()));
30 }
31
32 Ok(())
33}
34
35pub fn validate_kvstore_params(
37 primary_namespace: &str,
38 secondary_namespace: &str,
39 key: Option<&str>,
40) -> Result<(), Error> {
41 validate_kvstore_string(primary_namespace)?;
43
44 validate_kvstore_string(secondary_namespace)?;
46
47 if primary_namespace.is_empty() && !secondary_namespace.is_empty() {
49 return Err(Error::KVStoreInvalidKey(
50 "If primary_namespace is empty, secondary_namespace must also be empty".to_string(),
51 ));
52 }
53
54 if let Some(key) = key {
55 validate_kvstore_string(key)?;
57
58 let namespace_key = format!("{primary_namespace}/{secondary_namespace}");
60 if key == primary_namespace || key == secondary_namespace || key == namespace_key {
61 return Err(Error::KVStoreInvalidKey(format!(
62 "Key '{key}' conflicts with namespace names"
63 )));
64 }
65 }
66
67 Ok(())
68}
69
70#[async_trait]
72pub trait KVStoreTransaction<Error>: DbTransactionFinalizer<Err = Error> {
73 async fn kv_read(
75 &mut self,
76 primary_namespace: &str,
77 secondary_namespace: &str,
78 key: &str,
79 ) -> Result<Option<Vec<u8>>, Error>;
80
81 async fn kv_write(
83 &mut self,
84 primary_namespace: &str,
85 secondary_namespace: &str,
86 key: &str,
87 value: &[u8],
88 ) -> Result<(), Error>;
89
90 async fn kv_remove(
92 &mut self,
93 primary_namespace: &str,
94 secondary_namespace: &str,
95 key: &str,
96 ) -> Result<(), Error>;
97
98 async fn kv_list(
100 &mut self,
101 primary_namespace: &str,
102 secondary_namespace: &str,
103 ) -> Result<Vec<String>, Error>;
104}
105
106#[async_trait]
108pub trait KVStoreDatabase {
109 type Err: Into<Error> + From<Error>;
111
112 async fn kv_read(
114 &self,
115 primary_namespace: &str,
116 secondary_namespace: &str,
117 key: &str,
118 ) -> Result<Option<Vec<u8>>, Self::Err>;
119
120 async fn kv_list(
122 &self,
123 primary_namespace: &str,
124 secondary_namespace: &str,
125 ) -> Result<Vec<String>, Self::Err>;
126}
127
128#[async_trait]
130pub trait KVStore: KVStoreDatabase {
131 async fn begin_transaction(
133 &self,
134 ) -> Result<Box<dyn KVStoreTransaction<Self::Err> + Send + Sync>, Error>;
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{
140 validate_kvstore_params, validate_kvstore_string, KVSTORE_NAMESPACE_KEY_ALPHABET,
141 KVSTORE_NAMESPACE_KEY_MAX_LEN,
142 };
143
144 #[test]
145 fn test_validate_kvstore_string_valid_inputs() {
146 assert!(validate_kvstore_string("").is_ok());
148 assert!(validate_kvstore_string("abc").is_ok());
149 assert!(validate_kvstore_string("ABC").is_ok());
150 assert!(validate_kvstore_string("123").is_ok());
151 assert!(validate_kvstore_string("test_key").is_ok());
152 assert!(validate_kvstore_string("test-key").is_ok());
153 assert!(validate_kvstore_string("test_KEY-123").is_ok());
154
155 let max_length_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN);
157 assert!(validate_kvstore_string(&max_length_str).is_ok());
158 }
159
160 #[test]
161 fn test_validate_kvstore_string_invalid_length() {
162 let too_long_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN + 1);
164 let result = validate_kvstore_string(&too_long_str);
165 assert!(result.is_err());
166 assert!(result
167 .unwrap_err()
168 .to_string()
169 .contains("exceeds maximum length"));
170 }
171
172 #[test]
173 fn test_validate_kvstore_string_invalid_characters() {
174 let invalid_chars = vec![
176 "test@key", "test key", "test.key", "test/key", "test\\key", "test+key", "test=key", "test!key", "test#key", "test$key", "test%key", "test&key", "test*key", "test(key", "test)key", "test[key", "test]key", "test{key", "test}key", "test|key", "test;key", "test:key", "test'key", "test\"key", "test<key", "test>key", "test,key", "test?key", "test~key", "test`key", ];
207
208 for invalid_str in invalid_chars {
209 let result = validate_kvstore_string(invalid_str);
210 assert!(result.is_err(), "Expected '{}' to be invalid", invalid_str);
211 assert!(result
212 .unwrap_err()
213 .to_string()
214 .contains("invalid characters"));
215 }
216 }
217
218 #[test]
219 fn test_validate_kvstore_params_valid() {
220 assert!(validate_kvstore_params("primary", "secondary", Some("key")).is_ok());
222 assert!(validate_kvstore_params("primary", "", Some("key")).is_ok());
223 assert!(validate_kvstore_params("", "", Some("key")).is_ok());
224 assert!(validate_kvstore_params("p1", "s1", Some("different_key")).is_ok());
225 }
226
227 #[test]
228 fn test_validate_kvstore_params_empty_namespace_rules() {
229 let result = validate_kvstore_params("", "secondary", Some("key"));
231 assert!(result.is_err());
232 assert!(result
233 .unwrap_err()
234 .to_string()
235 .contains("If primary_namespace is empty"));
236 }
237
238 #[test]
239 fn test_validate_kvstore_params_collision_prevention() {
240 let test_cases = vec![
242 ("primary", "secondary", "primary"), ("primary", "secondary", "secondary"), ];
245
246 for (primary, secondary, key) in test_cases {
247 let result = validate_kvstore_params(primary, secondary, Some(key));
248 assert!(
249 result.is_err(),
250 "Expected collision for key '{}' with namespaces '{}'/'{}'",
251 key,
252 primary,
253 secondary
254 );
255 let error_msg = result.unwrap_err().to_string();
256 assert!(error_msg.contains("conflicts with namespace"));
257 }
258
259 let result = validate_kvstore_params("primary", "secondary", Some("primary_secondary"));
261 assert!(result.is_ok(), "This should be valid - no actual collision");
262 }
263
264 #[test]
265 fn test_validate_kvstore_params_invalid_strings() {
266 let result = validate_kvstore_params("primary@", "secondary", Some("key"));
268 assert!(result.is_err());
269
270 let result = validate_kvstore_params("primary", "secondary!", Some("key"));
271 assert!(result.is_err());
272
273 let result = validate_kvstore_params("primary", "secondary", Some("key with space"));
274 assert!(result.is_err());
275 }
276
277 #[test]
278 fn test_alphabet_constants() {
279 assert_eq!(
281 KVSTORE_NAMESPACE_KEY_ALPHABET,
282 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
283 );
284 assert_eq!(KVSTORE_NAMESPACE_KEY_MAX_LEN, 120);
285 }
286
287 #[test]
288 fn test_alphabet_coverage() {
289 for ch in KVSTORE_NAMESPACE_KEY_ALPHABET.chars() {
291 let test_str = ch.to_string();
292 assert!(
293 validate_kvstore_string(&test_str).is_ok(),
294 "Character '{}' should be valid",
295 ch
296 );
297 }
298 }
299
300 #[test]
301 fn test_namespace_segmentation_examples() {
302 let valid_examples = vec![
306 ("wallets", "user123", "balance"),
307 ("quotes", "mint", "quote_12345"),
308 ("keysets", "", "active_keyset"),
309 ("", "", "global_config"),
310 ("auth", "session_456", "token"),
311 ("mint_info", "", "version"),
312 ];
313
314 for (primary, secondary, key) in valid_examples {
315 assert!(
316 validate_kvstore_params(primary, secondary, Some(key)).is_ok(),
317 "Valid example should pass: '{}'/'{}'/'{}'",
318 primary,
319 secondary,
320 key
321 );
322 }
323 }
324
325 #[test]
326 fn test_per_namespace_uniqueness() {
327 assert!(validate_kvstore_params("ns1", "sub1", Some("key1")).is_ok());
334 assert!(validate_kvstore_params("ns2", "sub1", Some("key1")).is_ok()); assert!(validate_kvstore_params("ns1", "sub2", Some("key1")).is_ok()); assert!(validate_kvstore_params("ns1", "sub1", Some("ns1")).is_err()); assert!(validate_kvstore_params("ns1", "sub1", Some("sub1")).is_err()); }
341}