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