1use crate::prelude::*;
7use alloc::collections::BTreeMap;
8use core::fmt;
9
10#[cfg(any(test, feature = "storage-conformance"))]
11mod power_cut;
12#[cfg(any(test, feature = "storage-conformance"))]
13pub use power_cut::{PowerCutRig, power_cut_suite};
14
15pub const MAX_KEY_LEN: usize = 64;
17
18pub trait Storage: fmt::Debug {
25 fn read(&self, key: &str) -> Result<Option<String>, StorageError>;
28 fn write(&mut self, key: &str, value: &str) -> Result<(), StorageError>;
30 fn delete(&mut self, key: &str) -> Result<(), StorageError>;
32 fn list(&self) -> Result<Vec<String>, StorageError>;
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
38pub enum StorageError {
39 InvalidKey(String),
41 Backend(String),
43}
44
45impl fmt::Display for StorageError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::InvalidKey(key) => write!(f, "invalid storage key {key:?}"),
50 Self::Backend(message) => write!(f, "storage error: {message}"),
51 }
52 }
53}
54
55#[must_use]
58pub fn is_valid_key(key: &str) -> bool {
59 !key.is_empty()
60 && key.len() <= MAX_KEY_LEN
61 && key
62 .bytes()
63 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-')
64}
65
66fn check_key(key: &str) -> Result<(), StorageError> {
68 if is_valid_key(key) {
69 Ok(())
70 } else {
71 Err(StorageError::InvalidKey(key.to_owned()))
72 }
73}
74
75#[derive(Debug, Clone, Default)]
78pub struct MemoryStorage {
79 values: BTreeMap<String, String>,
80}
81
82impl MemoryStorage {
83 #[must_use]
85 pub fn new() -> Self {
86 Self::default()
87 }
88}
89
90impl Storage for MemoryStorage {
91 fn read(&self, key: &str) -> Result<Option<String>, StorageError> {
92 check_key(key)?;
93 Ok(self.values.get(key).cloned())
94 }
95
96 fn write(&mut self, key: &str, value: &str) -> Result<(), StorageError> {
97 check_key(key)?;
98 self.values.insert(key.to_owned(), value.to_owned());
99 Ok(())
100 }
101
102 fn delete(&mut self, key: &str) -> Result<(), StorageError> {
103 check_key(key)?;
104 self.values.remove(key);
105 Ok(())
106 }
107
108 fn list(&self) -> Result<Vec<String>, StorageError> {
109 Ok(self.values.keys().cloned().collect())
110 }
111}
112
113#[cfg(any(test, feature = "storage-conformance"))]
119fn expect_ok<T>(result: Result<T, StorageError>) -> T {
120 result.unwrap_or_else(|e| panic!("storage error: {e}"))
121}
122
123#[cfg(any(test, feature = "storage-conformance"))]
126pub fn conformance_suite(storage: &mut dyn Storage) {
127 assert_eq!(
128 expect_ok(storage.read("missing")),
129 None,
130 "reading an absent key returns None"
131 );
132 assert_eq!(expect_ok(storage.list()), Vec::<String>::new());
133
134 expect_ok(storage.write("save-slot-1", "hello"));
135 assert_eq!(
136 expect_ok(storage.read("save-slot-1")),
137 Some("hello".to_owned())
138 );
139 assert_eq!(expect_ok(storage.list()), ["save-slot-1"]);
140
141 expect_ok(storage.write("save-slot-1", "overwritten"));
142 assert_eq!(
143 expect_ok(storage.read("save-slot-1")),
144 Some("overwritten".to_owned())
145 );
146
147 expect_ok(storage.write("settings", "{}"));
148 let mut keys = expect_ok(storage.list());
149 keys.sort();
150 assert_eq!(keys, ["save-slot-1", "settings"]);
151
152 expect_ok(storage.delete("save-slot-1"));
153 assert_eq!(expect_ok(storage.read("save-slot-1")), None);
154 assert_eq!(expect_ok(storage.list()), ["settings"]);
155
156 expect_ok(storage.delete("save-slot-1"));
158
159 for bad in ["", "UPPER", "has space", "has/slash", &"x".repeat(65)] {
161 assert!(
162 matches!(storage.read(bad), Err(StorageError::InvalidKey(_))),
163 "{bad:?} should be rejected"
164 );
165 assert!(matches!(
166 storage.write(bad, "x"),
167 Err(StorageError::InvalidKey(_))
168 ));
169 assert!(matches!(
170 storage.delete(bad),
171 Err(StorageError::InvalidKey(_))
172 ));
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use proptest::prelude::*;
180
181 #[test]
182 fn memory_storage_passes_the_conformance_suite() {
183 conformance_suite(&mut MemoryStorage::new());
184 }
185
186 #[derive(Debug, Default)]
190 struct Broken;
191
192 impl Storage for Broken {
193 fn read(&self, _key: &str) -> Result<Option<String>, StorageError> {
194 Ok(None)
195 }
196 fn write(&mut self, _key: &str, _value: &str) -> Result<(), StorageError> {
197 Ok(())
198 }
199 fn delete(&mut self, _key: &str) -> Result<(), StorageError> {
200 Ok(())
201 }
202 fn list(&self) -> Result<Vec<String>, StorageError> {
203 Ok(Vec::new())
204 }
205 }
206
207 #[test]
209 #[should_panic(expected = "assertion `left == right` failed")]
210 fn conformance_suite_rejects_a_broken_storage() {
211 conformance_suite(&mut Broken);
212 }
213
214 #[test]
215 fn error_display() {
216 assert_eq!(
217 StorageError::InvalidKey("BAD".to_owned()).to_string(),
218 "invalid storage key \"BAD\""
219 );
220 assert_eq!(
221 StorageError::Backend("disk full".to_owned()).to_string(),
222 "storage error: disk full"
223 );
224 }
225
226 fn matches_pattern(key: &str) -> bool {
230 let chars: Vec<char> = key.chars().collect();
231 (1..=MAX_KEY_LEN).contains(&chars.len())
232 && chars
233 .iter()
234 .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '-'))
235 }
236
237 proptest! {
238 #[test]
239 fn accepted_iff_pattern_matches(key in ".{0,80}") {
240 prop_assert_eq!(is_valid_key(&key), matches_pattern(&key));
241 }
242
243 #[test]
244 fn generated_valid_keys_are_accepted(
245 key in "[a-z0-9_-]{1,64}"
246 ) {
247 prop_assert!(is_valid_key(&key));
248 }
249 }
250}