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_write_if_absent(
97 &mut self,
98 primary_namespace: &str,
99 secondary_namespace: &str,
100 key: &str,
101 value: &[u8],
102 ) -> Result<bool, Error>;
103
104 async fn kv_remove(
106 &mut self,
107 primary_namespace: &str,
108 secondary_namespace: &str,
109 key: &str,
110 ) -> Result<(), Error>;
111
112 async fn kv_write_if_equals(
119 &mut self,
120 primary_namespace: &str,
121 secondary_namespace: &str,
122 key: &str,
123 expected: &[u8],
124 replacement: &[u8],
125 ) -> Result<bool, Error>;
126
127 async fn kv_list(
129 &mut self,
130 primary_namespace: &str,
131 secondary_namespace: &str,
132 ) -> Result<Vec<String>, Error>;
133}
134
135#[async_trait]
137pub trait KVStoreDatabase {
138 type Err: Into<Error> + From<Error>;
140
141 async fn kv_read(
143 &self,
144 primary_namespace: &str,
145 secondary_namespace: &str,
146 key: &str,
147 ) -> Result<Option<Vec<u8>>, Self::Err>;
148
149 async fn kv_list(
151 &self,
152 primary_namespace: &str,
153 secondary_namespace: &str,
154 ) -> Result<Vec<String>, Self::Err>;
155}
156
157#[async_trait]
162pub trait KVStoreCompareAndSwap: KVStoreDatabase {
163 async fn kv_compare_and_swap(
169 &self,
170 primary_namespace: &str,
171 secondary_namespace: &str,
172 key: &str,
173 expected: Option<&[u8]>,
174 replacement: &[u8],
175 ) -> Result<bool, Self::Err>;
176}
177
178#[async_trait]
180pub trait KVStore: KVStoreDatabase {
181 async fn begin_transaction(
183 &self,
184 ) -> Result<Box<dyn KVStoreTransaction<Self::Err> + Send + Sync>, Error>;
185}
186
187#[cfg(test)]
188mod tests {
189 use super::{
190 validate_kvstore_params, validate_kvstore_string, KVSTORE_NAMESPACE_KEY_ALPHABET,
191 KVSTORE_NAMESPACE_KEY_MAX_LEN,
192 };
193
194 #[test]
195 fn test_validate_kvstore_string_valid_inputs() {
196 assert!(validate_kvstore_string("").is_ok());
198 assert!(validate_kvstore_string("abc").is_ok());
199 assert!(validate_kvstore_string("ABC").is_ok());
200 assert!(validate_kvstore_string("123").is_ok());
201 assert!(validate_kvstore_string("test_key").is_ok());
202 assert!(validate_kvstore_string("test-key").is_ok());
203 assert!(validate_kvstore_string("test_KEY-123").is_ok());
204
205 let max_length_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN);
207 assert!(validate_kvstore_string(&max_length_str).is_ok());
208 }
209
210 #[test]
211 fn test_validate_kvstore_string_invalid_length() {
212 let too_long_str = "a".repeat(KVSTORE_NAMESPACE_KEY_MAX_LEN + 1);
214 let result = validate_kvstore_string(&too_long_str);
215 assert!(result.is_err());
216 assert!(result
217 .unwrap_err()
218 .to_string()
219 .contains("exceeds maximum length"));
220 }
221
222 #[test]
223 fn test_validate_kvstore_string_invalid_characters() {
224 let invalid_chars = vec![
226 "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", ];
257
258 for invalid_str in invalid_chars {
259 let result = validate_kvstore_string(invalid_str);
260 assert!(result.is_err(), "Expected '{}' to be invalid", invalid_str);
261 assert!(result
262 .unwrap_err()
263 .to_string()
264 .contains("invalid characters"));
265 }
266 }
267
268 #[test]
269 fn test_validate_kvstore_params_valid() {
270 assert!(validate_kvstore_params("primary", "secondary", Some("key")).is_ok());
272 assert!(validate_kvstore_params("primary", "", Some("key")).is_ok());
273 assert!(validate_kvstore_params("", "", Some("key")).is_ok());
274 assert!(validate_kvstore_params("p1", "s1", Some("different_key")).is_ok());
275 }
276
277 #[test]
278 fn test_validate_kvstore_params_empty_namespace_rules() {
279 let result = validate_kvstore_params("", "secondary", Some("key"));
281 assert!(result.is_err());
282 assert!(result
283 .unwrap_err()
284 .to_string()
285 .contains("If primary_namespace is empty"));
286 }
287
288 #[test]
289 fn test_validate_kvstore_params_collision_prevention() {
290 let test_cases = vec![
292 ("primary", "secondary", "primary"), ("primary", "secondary", "secondary"), ];
295
296 for (primary, secondary, key) in test_cases {
297 let result = validate_kvstore_params(primary, secondary, Some(key));
298 assert!(
299 result.is_err(),
300 "Expected collision for key '{}' with namespaces '{}'/'{}'",
301 key,
302 primary,
303 secondary
304 );
305 let error_msg = result.unwrap_err().to_string();
306 assert!(error_msg.contains("conflicts with namespace"));
307 }
308
309 let result = validate_kvstore_params("primary", "secondary", Some("primary_secondary"));
311 assert!(result.is_ok(), "This should be valid - no actual collision");
312 }
313
314 #[test]
315 fn test_validate_kvstore_params_invalid_strings() {
316 let result = validate_kvstore_params("primary@", "secondary", Some("key"));
318 assert!(result.is_err());
319
320 let result = validate_kvstore_params("primary", "secondary!", Some("key"));
321 assert!(result.is_err());
322
323 let result = validate_kvstore_params("primary", "secondary", Some("key with space"));
324 assert!(result.is_err());
325 }
326
327 #[test]
328 fn test_alphabet_constants() {
329 assert_eq!(
331 KVSTORE_NAMESPACE_KEY_ALPHABET,
332 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
333 );
334 assert_eq!(KVSTORE_NAMESPACE_KEY_MAX_LEN, 120);
335 }
336
337 #[test]
338 fn test_alphabet_coverage() {
339 for ch in KVSTORE_NAMESPACE_KEY_ALPHABET.chars() {
341 let test_str = ch.to_string();
342 assert!(
343 validate_kvstore_string(&test_str).is_ok(),
344 "Character '{}' should be valid",
345 ch
346 );
347 }
348 }
349
350 #[test]
351 fn test_namespace_segmentation_examples() {
352 let valid_examples = vec![
356 ("wallets", "user123", "balance"),
357 ("quotes", "mint", "quote_12345"),
358 ("keysets", "", "active_keyset"),
359 ("", "", "global_config"),
360 ("auth", "session_456", "token"),
361 ("mint_info", "", "version"),
362 ];
363
364 for (primary, secondary, key) in valid_examples {
365 assert!(
366 validate_kvstore_params(primary, secondary, Some(key)).is_ok(),
367 "Valid example should pass: '{}'/'{}'/'{}'",
368 primary,
369 secondary,
370 key
371 );
372 }
373 }
374
375 #[test]
376 fn test_per_namespace_uniqueness() {
377 assert!(validate_kvstore_params("ns1", "sub1", Some("key1")).is_ok());
384 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()); }
391}