launchdarkly_server_sdk/stores/
store.rs

1use crate::stores::store_types::{AllData, PatchTarget, StorageItem};
2use std::collections::HashMap;
3use thiserror::Error;
4
5use launchdarkly_server_sdk_evaluation::{Flag, Segment, Store, Versioned};
6
7use super::persistent_store::PersistentStoreError;
8
9#[non_exhaustive]
10#[derive(Debug, Error)]
11pub enum UpdateError {
12    #[error("invalid path: {0}")]
13    InvalidPath(String),
14
15    #[error("expected a {0}, got a {0}")]
16    InvalidTarget(String, String),
17
18    #[error("couldn't parse json as a flag or segment: {0}")]
19    ParseError(serde_json::Error),
20
21    #[error("underlying persistent store returned an error: {0}")]
22    PersistentStore(#[from] PersistentStoreError),
23}
24
25/// Trait for a data store that holds and updates feature flags and related data received by the
26/// SDK.
27pub trait DataStore: Store + Send + Sync {
28    fn init(&mut self, new_data: AllData<Flag, Segment>);
29    fn all_flags(&self) -> HashMap<String, Flag>;
30    fn upsert(&mut self, key: &str, data: PatchTarget) -> Result<(), UpdateError>;
31    fn to_store(&self) -> &dyn Store;
32}
33
34/// Default implementation of [DataStore] which holds information in-memory.
35pub struct InMemoryDataStore {
36    pub data: AllData<StorageItem<Flag>, StorageItem<Segment>>,
37}
38
39impl InMemoryDataStore {
40    pub fn new() -> Self {
41        Self {
42            data: AllData {
43                flags: HashMap::new(),
44                segments: HashMap::new(),
45            },
46        }
47    }
48
49    fn upsert_flag(&mut self, key: &str, item: StorageItem<Flag>) {
50        match self.data.flags.get(key) {
51            Some(existing) if existing.is_greater_than_or_equal(item.version()) => None,
52            _ => self.data.flags.insert(key.to_string(), item),
53        };
54    }
55
56    fn upsert_segment(&mut self, key: &str, item: StorageItem<Segment>) {
57        match self.data.segments.get(key) {
58            Some(existing) if existing.is_greater_than_or_equal(item.version()) => None,
59            _ => self.data.segments.insert(key.to_string(), item),
60        };
61    }
62}
63
64impl Store for InMemoryDataStore {
65    fn flag(&self, flag_key: &str) -> Option<Flag> {
66        match self.data.flags.get(flag_key) {
67            Some(StorageItem::Item(f)) => Some(f.clone()),
68            _ => None,
69        }
70    }
71
72    fn segment(&self, segment_key: &str) -> Option<Segment> {
73        match self.data.segments.get(segment_key) {
74            Some(StorageItem::Item(s)) => Some(s.clone()),
75            _ => None,
76        }
77    }
78}
79
80impl DataStore for InMemoryDataStore {
81    fn init(&mut self, new_data: AllData<Flag, Segment>) {
82        self.data = new_data.into();
83        debug!("data store has been updated with new flag data");
84    }
85
86    fn all_flags(&self) -> HashMap<String, Flag> {
87        self.data
88            .flags
89            .iter()
90            .filter_map(|(key, item)| match item {
91                StorageItem::Tombstone(_) => None,
92                StorageItem::Item(f) => Some((key.clone(), f.clone())),
93            })
94            .collect()
95    }
96
97    fn upsert(&mut self, key: &str, data: PatchTarget) -> Result<(), UpdateError> {
98        match data {
99            PatchTarget::Flag(item) => {
100                self.upsert_flag(key, item);
101                Ok(())
102            }
103            PatchTarget::Segment(item) => {
104                self.upsert_segment(key, item);
105                Ok(())
106            }
107            PatchTarget::Other(v) => Err(UpdateError::InvalidTarget(
108                "flag or segment".to_string(),
109                format!("{:?}", v),
110            )),
111        }
112    }
113
114    fn to_store(&self) -> &dyn Store {
115        self
116    }
117}
118
119impl Default for InMemoryDataStore {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::test_common::{basic_flag, basic_segment};
129    use maplit::hashmap;
130    use test_case::test_case;
131
132    fn basic_data() -> AllData<Flag, Segment> {
133        AllData {
134            flags: hashmap! {"flag-key".into() => basic_flag("flag-key")},
135            segments: hashmap! {"segment-key".into() => basic_segment("segment-key")},
136        }
137    }
138
139    #[test]
140    fn in_memory_can_be_initialized() {
141        let mut data_store = InMemoryDataStore::new();
142        assert!(data_store.flag("flag-key").is_none());
143        assert!(data_store.segment("segment-key").is_none());
144
145        data_store.init(basic_data());
146
147        assert_eq!(data_store.flag("flag-key").unwrap().key, "flag-key");
148        assert_eq!(
149            data_store.segment("segment-key").unwrap().key,
150            "segment-key"
151        );
152    }
153
154    #[test]
155    fn in_memory_can_return_all_flags() {
156        let mut data_store = InMemoryDataStore::new();
157
158        assert!(!data_store.all_flags().contains_key("flag-key"));
159        data_store.init(basic_data());
160        assert!(data_store.all_flags().contains_key("flag-key"));
161    }
162
163    #[test]
164    fn in_memory_patch_can_upsert_flag() {
165        let mut data_store = InMemoryDataStore::new();
166        data_store.init(basic_data());
167
168        // Verify patch can insert
169        assert!(data_store.flag("flag-does-not-exist").is_none());
170        let patch_target = PatchTarget::Flag(StorageItem::Item(basic_flag("flag-does-not-exist")));
171        let result = data_store.upsert("flag-does-not-exist", patch_target);
172        assert!(result.is_ok());
173        assert_eq!(
174            data_store.flag("flag-does-not-exist").unwrap().key,
175            "flag-does-not-exist"
176        );
177
178        // Verify that patch can update
179        let mut flag = basic_flag("new-key");
180        flag.version = 43;
181        let patch_target = PatchTarget::Flag(StorageItem::Item(flag));
182        let result = data_store.upsert("flag-key", patch_target);
183        assert!(result.is_ok());
184        assert_eq!(data_store.flag("flag-key").unwrap().key, "new-key");
185    }
186
187    #[test]
188    fn in_memory_patch_can_upsert_flag_deleted_flag() {
189        let mut data_store = InMemoryDataStore::new();
190        data_store.init(basic_data());
191
192        let mut flag = data_store.flag("flag-key").unwrap();
193        flag.version += 1;
194
195        assert!(data_store
196            .upsert(
197                "flag-key",
198                PatchTarget::Flag(StorageItem::Tombstone(flag.version))
199            )
200            .is_ok());
201        assert!(data_store.flag("flag-key").is_none());
202
203        flag.version -= 1;
204
205        let patch_target = PatchTarget::Flag(StorageItem::Item(flag.clone()));
206        assert!(data_store.upsert("flag-key", patch_target).is_ok());
207        assert!(data_store.flag("flag-key").is_none());
208
209        flag.version += 2;
210        let patch_target = PatchTarget::Flag(StorageItem::Item(flag));
211        assert!(data_store.upsert("flag-key", patch_target).is_ok());
212        assert!(data_store.flag("flag-key").is_some());
213    }
214
215    #[test_case(41, 42)]
216    #[test_case(43, 43)]
217    fn in_memory_patch_does_not_update_flag_with_older_version(
218        updated_version: u64,
219        expected_version: u64,
220    ) {
221        let mut data_store = InMemoryDataStore::new();
222        data_store.init(basic_data());
223
224        let mut flag = data_store.flag("flag-key").unwrap();
225        assert_eq!(42, flag.version);
226
227        flag.version = updated_version;
228
229        let patch_target = PatchTarget::Flag(StorageItem::Item(flag));
230        let result = data_store.upsert("flag-key", patch_target);
231        assert!(result.is_ok());
232
233        let flag = data_store.flag("flag-key").unwrap();
234        assert_eq!(expected_version, flag.version);
235    }
236
237    #[test]
238    fn in_memory_patch_can_upsert_segment() {
239        let mut data_store = InMemoryDataStore::new();
240        data_store.init(basic_data());
241
242        // Verify patch can insert
243        assert!(data_store.segment("segment-does-not-exist").is_none());
244        let patch_target =
245            PatchTarget::Segment(StorageItem::Item(basic_segment("segment-does-not-exist")));
246        let result = data_store.upsert("segment-does-not-exist", patch_target);
247        assert!(result.is_ok());
248        assert_eq!(
249            data_store.segment("segment-does-not-exist").unwrap().key,
250            "segment-does-not-exist"
251        );
252
253        // Verify that patch can update
254        let patch_target = PatchTarget::Segment(StorageItem::Item(basic_segment("new-key")));
255        let result = data_store.upsert("my-boolean-segment", patch_target);
256        assert!(result.is_ok());
257        assert_eq!(
258            data_store.segment("my-boolean-segment").unwrap().key,
259            "new-key"
260        );
261    }
262
263    #[test]
264    fn in_memory_patch_can_upsert_segment_deleted_segment() {
265        let mut data_store = InMemoryDataStore::new();
266        data_store.init(basic_data());
267
268        let mut segment = data_store.segment("segment-key").unwrap();
269        segment.version += 1;
270
271        assert!(data_store
272            .upsert(
273                "segment-key",
274                PatchTarget::Segment(StorageItem::Tombstone(segment.version))
275            )
276            .is_ok());
277        assert!(data_store.segment("segment-key").is_none());
278
279        segment.version -= 1;
280
281        let patch_target = PatchTarget::Segment(StorageItem::Item(segment.clone()));
282        assert!(data_store.upsert("segment-key", patch_target).is_ok());
283        assert!(data_store.segment("segment-key").is_none());
284
285        segment.version += 2;
286        let patch_target = PatchTarget::Segment(StorageItem::Item(segment));
287        assert!(data_store.upsert("segment-key", patch_target).is_ok());
288        assert!(data_store.segment("segment-key").is_some());
289    }
290
291    #[test_case(0, 1)]
292    #[test_case(2, 2)]
293    fn in_memory_patch_does_not_update_segment_with_older_version(
294        updated_version: u64,
295        expected_version: u64,
296    ) {
297        let mut data_store = InMemoryDataStore::new();
298        data_store.init(basic_data());
299
300        let mut segment = data_store.segment("segment-key").unwrap();
301        assert_eq!(1, segment.version);
302
303        segment.version = updated_version;
304
305        let patch_target = PatchTarget::Segment(StorageItem::Item(segment));
306        let result = data_store.upsert("segment-key", patch_target);
307        assert!(result.is_ok());
308
309        let segment = data_store.segment("segment-key").unwrap();
310        assert_eq!(expected_version, segment.version);
311    }
312
313    #[test]
314    fn in_memory_can_delete_flag() {
315        let mut data_store = InMemoryDataStore::new();
316        data_store.init(basic_data());
317
318        assert!(data_store.flag("flag-key").is_some());
319        assert!(data_store
320            .upsert("flag-key", PatchTarget::Flag(StorageItem::Tombstone(41)))
321            .is_ok());
322        assert!(data_store.flag("flag-key").is_some());
323
324        assert!(data_store
325            .upsert("flag-key", PatchTarget::Flag(StorageItem::Tombstone(42)))
326            .is_ok());
327        assert!(data_store.flag("flag-key").is_some());
328
329        data_store.init(basic_data());
330
331        assert!(data_store
332            .upsert("flag-key", PatchTarget::Flag(StorageItem::Tombstone(43)))
333            .is_ok());
334        assert!(data_store.flag("flag-key").is_none());
335    }
336
337    #[test]
338    fn in_memory_can_delete_segment() {
339        let mut data_store = InMemoryDataStore::new();
340        data_store.init(basic_data());
341
342        assert!(data_store.segment("segment-key").is_some());
343        assert!(data_store
344            .upsert(
345                "segment-key",
346                PatchTarget::Segment(StorageItem::Tombstone(0))
347            )
348            .is_ok());
349        assert!(data_store.segment("segment-key").is_some());
350
351        assert!(data_store
352            .upsert(
353                "segment-key",
354                PatchTarget::Segment(StorageItem::Tombstone(1))
355            )
356            .is_ok());
357        assert!(data_store.segment("segment-key").is_some());
358
359        data_store.init(basic_data());
360
361        assert!(data_store
362            .upsert(
363                "segment-key",
364                PatchTarget::Segment(StorageItem::Tombstone(2))
365            )
366            .is_ok());
367        assert!(data_store.segment("segment-key").is_none());
368    }
369}