samod_core/
storage_key.rs

1use std::fmt;
2
3use automerge::ChangeHash;
4
5use crate::{CompactionHash, DocumentId};
6
7/// A hierarchical key for storage operations in the samod-core system.
8///
9/// `StorageKey` represents a path-like key structure that supports efficient
10/// prefix-based operations. Keys are composed of string components that form
11/// a hierarchy, similar to filesystem paths or namespaces.
12///
13/// ## Usage
14///
15/// Storage keys are used throughout samod-core for organizing data in the
16/// key-value store. They support operations like prefix matching for range
17/// queries and hierarchical organization of related data.
18///
19/// ## Examples
20///
21/// ```rust
22/// use samod_core::StorageKey;
23///
24/// // Create keys from string vectors
25/// let key1 = StorageKey::from_parts(vec!["users", "123", "profile"]).unwrap();
26/// let key2 = StorageKey::from_parts(vec!["users", "123", "settings"]).unwrap();
27/// let prefix = StorageKey::from_parts(vec!["users", "123"]).unwrap();
28///
29/// // Check prefix relationships
30/// assert!(prefix.is_prefix_of(&key1));
31/// assert!(prefix.is_prefix_of(&key2));
32/// ```
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub struct StorageKey(Vec<String>);
35
36impl StorageKey {
37    pub fn storage_id_path() -> StorageKey {
38        StorageKey(vec!["storage-adapter-id".to_string()])
39    }
40
41    pub fn incremental_prefix(doc_id: &DocumentId) -> StorageKey {
42        StorageKey(vec![doc_id.to_string(), "incremental".to_string()])
43    }
44
45    pub fn incremental_path(doc_id: &DocumentId, change_hash: ChangeHash) -> StorageKey {
46        StorageKey(vec![
47            doc_id.to_string(),
48            "incremental".to_string(),
49            change_hash.to_string(),
50        ])
51    }
52
53    pub fn snapshot_prefix(doc_id: &DocumentId) -> StorageKey {
54        StorageKey(vec![doc_id.to_string(), "snapshot".to_string()])
55    }
56
57    pub fn snapshot_path(doc_id: &DocumentId, compaction_hash: &CompactionHash) -> StorageKey {
58        StorageKey(vec![
59            doc_id.to_string(),
60            "snapshot".to_string(),
61            compaction_hash.to_string(),
62        ])
63    }
64
65    /// Creates a storage key from a slice of string parts.
66    ///
67    /// # Arguments
68    ///
69    /// * `parts` - The parts that make up the key path
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use samod_core::StorageKey;
75    ///
76    /// let key = StorageKey::from_parts(&["users", "123", "profile"]).unwrap();
77    /// ```
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if any part is empty or contains a slash.
82    pub fn from_parts<I: IntoIterator<Item = S>, S: AsRef<str>>(
83        parts: I,
84    ) -> Result<Self, InvalidStorageKey> {
85        let mut components = Vec::new();
86        for part in parts {
87            if part.as_ref().is_empty() || part.as_ref().contains("/") {
88                return Err(InvalidStorageKey);
89            }
90            components.push(part.as_ref().to_string());
91        }
92        Ok(StorageKey(components))
93    }
94
95    /// Checks if this key is a prefix of another key.
96    ///
97    /// # Arguments
98    ///
99    /// * `other` - The key to check against
100    pub fn is_prefix_of(&self, other: &StorageKey) -> bool {
101        if self.0.len() > other.0.len() {
102            return false;
103        }
104        self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b)
105    }
106
107    /// Checks if this key is one level deeper then  the given prefix
108    ///
109    /// # Examples
110    ///
111    /// ```rust
112    /// # use samod_core::StorageKey;
113    /// let key = StorageKey::from_parts(vec!["a", "b", "c"]).unwrap();
114    /// let prefix = StorageKey::from_parts(vec!["a", "b"]).unwrap();
115    /// assert_eq!(key.onelevel_deeper(&prefix), Some(StorageKey::from_parts(vec!["a", "b", "c"]).unwrap()));
116    ///
117    /// let prefix2 = StorageKey::from_parts(vec!["a"]).unwrap();
118    /// assert_eq!(key.onelevel_deeper(&prefix2), Some(StorageKey::from_parts(vec!["a", "b"]).unwrap()));
119    ///
120    /// let prefix3 = StorageKey::from_parts(vec!["a", "b", "c", "d"]).unwrap();
121    /// assert_eq!(key.onelevel_deeper(&prefix3), None);
122    /// ```
123    pub fn onelevel_deeper(&self, prefix: &StorageKey) -> Option<StorageKey> {
124        if prefix.is_prefix_of(self) && self.0.len() > prefix.0.len() {
125            let components = self.0.iter().take(prefix.0.len() + 1).cloned();
126            Some(StorageKey(components.collect()))
127        } else {
128            None
129        }
130    }
131
132    pub fn with_suffix(&self, suffix: StorageKey) -> StorageKey {
133        let mut new_key = self.0.clone();
134        new_key.extend(suffix.0);
135        StorageKey(new_key)
136    }
137
138    /// Create a new StorageKey with the given component appended.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if the new component is empty or contains a forward slash.
143    pub fn with_component(&self, component: String) -> Result<StorageKey, InvalidStorageKey> {
144        if component.is_empty() || component.contains('/') {
145            Err(InvalidStorageKey)
146        } else {
147            let mut new_key = self.0.clone();
148            new_key.push(component);
149            Ok(StorageKey(new_key))
150        }
151    }
152}
153
154impl IntoIterator for StorageKey {
155    type Item = String;
156    type IntoIter = std::vec::IntoIter<String>;
157
158    fn into_iter(self) -> Self::IntoIter {
159        self.0.into_iter()
160    }
161}
162
163impl<'a> IntoIterator for &'a StorageKey {
164    type Item = &'a String;
165    type IntoIter = std::slice::Iter<'a, String>;
166
167    fn into_iter(self) -> Self::IntoIter {
168        self.0.iter()
169    }
170}
171
172impl fmt::Display for StorageKey {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(f, "{}", self.0.join("/"))
175    }
176}
177
178#[derive(Debug)]
179pub struct InvalidStorageKey;
180
181impl std::fmt::Display for InvalidStorageKey {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        write!(f, "InvalidStorageKey")
184    }
185}
186
187impl std::error::Error for InvalidStorageKey {}