samod_core/
storage_key.rs

1use std::fmt;
2
3use automerge::ChangeHash;
4
5use crate::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(vec!["users", "123", "profile"]);
26/// let key2 = StorageKey::from(vec!["users", "123", "settings"]);
27/// let prefix = StorageKey::from(vec!["users", "123"]);
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_path(doc_id: &DocumentId, change_hash: ChangeHash) -> StorageKey {
42        StorageKey(vec![
43            doc_id.to_string(),
44            "incremental".to_string(),
45            change_hash.to_string(),
46        ])
47    }
48
49    pub fn snapshot_path(doc_id: DocumentId, compaction_hash: String) -> StorageKey {
50        StorageKey(vec![
51            doc_id.to_string(),
52            "snapshot".to_string(),
53            compaction_hash,
54        ])
55    }
56
57    /// Creates a storage key from a slice of string parts.
58    ///
59    /// # Arguments
60    ///
61    /// * `parts` - The parts that make up the key path
62    ///
63    /// # Example
64    ///
65    /// ```rust
66    /// use samod_core::StorageKey;
67    ///
68    /// let key = StorageKey::from_parts(&["users", "123", "profile"]);
69    /// ```
70    pub fn from_parts(parts: &[&str]) -> Self {
71        StorageKey(parts.iter().map(|s| s.to_string()).collect())
72    }
73
74    /// Checks if this key is a prefix of another key.
75    ///
76    /// # Arguments
77    ///
78    /// * `other` - The key to check against
79    pub fn is_prefix_of(&self, other: &StorageKey) -> bool {
80        if self.0.len() > other.0.len() {
81            return false;
82        }
83        self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b)
84    }
85
86    /// Checks if this key is one level deeper then  the given prefix
87    ///
88    /// # Examples
89    ///
90    /// ```rust
91    /// # use samod_core::StorageKey;
92    /// let key = StorageKey::from(vec!["a", "b", "c"]);
93    /// let prefix = StorageKey::from(vec!["a", "b"]);
94    /// assert_eq!(key.onelevel_deeper(&prefix), Some(StorageKey::from(vec!["a", "b", "c"])));
95    ///
96    /// let prefix2 = StorageKey::from(vec!["a"]);
97    /// assert_eq!(key.onelevel_deeper(&prefix2), Some(StorageKey::from(vec!["a", "b"])));
98    ///
99    /// let prefix3 = StorageKey::from(vec!["a", "b", "c", "d"]);
100    /// assert_eq!(key.onelevel_deeper(&prefix3), None);
101    /// ```
102    pub fn onelevel_deeper(&self, prefix: &StorageKey) -> Option<StorageKey> {
103        if prefix.is_prefix_of(self) && self.0.len() > prefix.0.len() {
104            let components = self.0.iter().take(prefix.0.len() + 1).cloned();
105            Some(StorageKey(components.collect()))
106        } else {
107            None
108        }
109    }
110
111    pub fn with_suffix(&self, suffix: StorageKey) -> StorageKey {
112        let mut new_key = self.0.clone();
113        new_key.extend(suffix.0);
114        StorageKey(new_key)
115    }
116
117    pub fn with_component(&self, component: String) -> StorageKey {
118        let mut new_key = self.0.clone();
119        new_key.push(component);
120        StorageKey(new_key)
121    }
122}
123
124impl IntoIterator for StorageKey {
125    type Item = String;
126    type IntoIter = std::vec::IntoIter<String>;
127
128    fn into_iter(self) -> Self::IntoIter {
129        self.0.into_iter()
130    }
131}
132
133impl<'a> IntoIterator for &'a StorageKey {
134    type Item = &'a String;
135    type IntoIter = std::slice::Iter<'a, String>;
136
137    fn into_iter(self) -> Self::IntoIter {
138        self.0.iter()
139    }
140}
141
142impl FromIterator<String> for StorageKey {
143    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
144        StorageKey(iter.into_iter().collect())
145    }
146}
147
148impl<'a> FromIterator<&'a str> for StorageKey {
149    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
150        StorageKey(iter.into_iter().map(String::from).collect())
151    }
152}
153
154impl<'a> From<Vec<&'a str>> for StorageKey {
155    fn from(vec: Vec<&'a str>) -> Self {
156        StorageKey(vec.into_iter().map(String::from).collect())
157    }
158}
159
160impl From<Vec<String>> for StorageKey {
161    fn from(vec: Vec<String>) -> Self {
162        StorageKey(vec)
163    }
164}
165
166impl fmt::Display for StorageKey {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        write!(f, "{}", self.0.join("/"))
169    }
170}