launchdarkly_server_sdk/stores/
persistent_store.rs

1use core::fmt;
2use std::collections::HashMap;
3
4use super::store_types::{AllData, DataKind, SerializedItem};
5
6/// Error type used to represent failures when interacting with the underlying persistent stores.
7#[derive(Debug)]
8pub struct PersistentStoreError {
9    message: String,
10}
11
12impl PersistentStoreError {
13    /// Create a new PersistentStoreError with the provided message.
14    pub fn new(message: impl Into<String>) -> Self {
15        Self {
16            message: message.into(),
17        }
18    }
19}
20
21impl std::error::Error for PersistentStoreError {
22    fn description(&self) -> &str {
23        &self.message
24    }
25}
26
27impl fmt::Display for PersistentStoreError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{}", self.message)
30    }
31}
32
33/// PersistentDataStore is an interface for a data store that holds feature flags and related data
34/// in a serialized form.
35///
36/// This interface should be used for database integrations, or any other data store implementation
37/// that stores data in some external service. The SDK will provide its own caching layer on top of
38/// the persistent data store; the data store implementation should not provide caching, but simply
39/// do every query or update that the SDK tells it to do.
40pub trait PersistentDataStore: Send + Sync {
41    /// Overwrites the store's contents with a set of items for each collection.
42    ///
43    /// All previous data should be discarded, regardless of versioning.
44    ///
45    /// The update should be done atomically. If it cannot be done atomically, then the store
46    /// must first add or update each item in the same order that they are given in the input
47    /// data, and then delete any previously stored items that were not in the input data.
48    fn init(
49        &mut self,
50        all_data: AllData<SerializedItem, SerializedItem>,
51    ) -> Result<(), PersistentStoreError>;
52
53    /// Retrieves a flag item from the specified collection, if available.
54    ///
55    /// If the specified key does not exist in the collection, it should result Ok(None).
56    ///
57    /// If the item has been deleted and the store contains a placeholder, it should return that
58    /// placeholder rather than filtering it out.
59    fn flag(&self, key: &str) -> Result<Option<SerializedItem>, PersistentStoreError>;
60
61    /// Retrieves a segment item from the specified collection, if available.
62    ///
63    /// If the specified key does not exist in the collection, it should result Ok(None).
64    ///
65    /// If the item has been deleted and the store contains a placeholder, it should return that
66    /// placeholder rather than filtering it out.
67    fn segment(&self, key: &str) -> Result<Option<SerializedItem>, PersistentStoreError>;
68
69    /// Retrieves all flag items from the specified collection.
70    ///
71    /// If the store contains placeholders for deleted items, it should include them in the results,
72    /// not filter them out.
73    fn all_flags(&self) -> Result<HashMap<String, SerializedItem>, PersistentStoreError>;
74
75    /// Updates or inserts an item in the specified collection. For updates, the object will only be
76    /// updated if the existing version is less than the new version.
77    ///
78    /// The SDK may pass a [SerializedItem] that represents a placeholder for a deleted item. In
79    /// that case, assuming the version is greater than any existing version of that item, the store should
80    /// retain that placeholder rather than simply not storing anything.
81    fn upsert(
82        &mut self,
83        kind: DataKind,
84        key: &str,
85        serialized_item: SerializedItem,
86    ) -> Result<bool, PersistentStoreError>;
87
88    /// Returns true if the data store contains a data set, meaning that [PersistentDataStore::init] has been called at
89    /// least once.
90    ///
91    /// In a shared data store, it should be able to detect this even if [PersistentDataStore::init] was called in a
92    /// different process: that is, the test should be based on looking at what is in the data store.
93    fn is_initialized(&self) -> bool;
94}
95
96#[cfg(test)]
97pub(crate) mod tests {
98    use crate::stores::persistent_store::PersistentDataStore;
99    use crate::stores::store_types::{AllData, DataKind, SerializedItem};
100    use std::collections::HashMap;
101
102    use super::PersistentStoreError;
103
104    pub struct NullPersistentDataStore {
105        pub(crate) initialized: bool,
106    }
107
108    impl PersistentDataStore for NullPersistentDataStore {
109        fn init(
110            &mut self,
111            _all_data: AllData<SerializedItem, SerializedItem>,
112        ) -> Result<(), PersistentStoreError> {
113            self.initialized = true;
114            Ok(())
115        }
116
117        fn flag(&self, _key: &str) -> Result<Option<SerializedItem>, PersistentStoreError> {
118            Ok(None)
119        }
120
121        fn segment(&self, _key: &str) -> Result<Option<SerializedItem>, PersistentStoreError> {
122            Ok(None)
123        }
124
125        fn all_flags(&self) -> Result<HashMap<String, SerializedItem>, PersistentStoreError> {
126            Ok(HashMap::new())
127        }
128
129        fn upsert(
130            &mut self,
131            _kind: DataKind,
132            _key: &str,
133            _serialized_item: SerializedItem,
134        ) -> Result<bool, PersistentStoreError> {
135            Ok(true)
136        }
137
138        fn is_initialized(&self) -> bool {
139            self.initialized
140        }
141    }
142
143    pub struct InMemoryPersistentDataStore {
144        pub(crate) data: AllData<SerializedItem, SerializedItem>,
145        pub(crate) initialized: bool,
146    }
147
148    impl PersistentDataStore for InMemoryPersistentDataStore {
149        fn init(
150            &mut self,
151            all_data: AllData<SerializedItem, SerializedItem>,
152        ) -> Result<(), PersistentStoreError> {
153            self.data = all_data;
154            self.initialized = true;
155            Ok(())
156        }
157
158        fn flag(&self, key: &str) -> Result<Option<SerializedItem>, PersistentStoreError> {
159            Ok(self.data.flags.get(key).map(|value| (*value).clone()))
160        }
161
162        fn segment(&self, key: &str) -> Result<Option<SerializedItem>, PersistentStoreError> {
163            Ok(self.data.segments.get(key).map(|value| (*value).clone()))
164        }
165
166        fn all_flags(&self) -> Result<HashMap<String, SerializedItem>, PersistentStoreError> {
167            Ok(self.data.flags.clone())
168        }
169
170        fn upsert(
171            &mut self,
172            kind: DataKind,
173            key: &str,
174            serialized_item: SerializedItem,
175        ) -> Result<bool, PersistentStoreError> {
176            let original = match kind {
177                DataKind::Flag => self.data.flags.insert(key.to_string(), serialized_item),
178                DataKind::Segment => self.data.segments.insert(key.to_string(), serialized_item),
179            };
180
181            Ok(original.is_some())
182        }
183
184        fn is_initialized(&self) -> bool {
185            self.initialized
186        }
187    }
188}