bitwarden_state/
repository.rs1use std::{any::TypeId, sync::Arc};
2
3use crate::{persist::Persist, registry::StateRegistryError};
4
5#[derive(thiserror::Error, Debug)]
7pub enum RepositoryError {
8 #[error("Internal error: {0}")]
10 Internal(String),
11
12 #[error(transparent)]
14 Serde(#[from] serde_json::Error),
15
16 #[error(transparent)]
18 Database(#[from] crate::sdk_managed::DatabaseError),
19
20 #[error(transparent)]
22 StateRegistry(#[from] StateRegistryError),
23}
24
25pub trait RepositoryOption<V: RepositoryItem> {
28 fn require(&self) -> Result<&Arc<dyn Repository<V>>, RepositoryError>;
31}
32
33impl<V: RepositoryItem> RepositoryOption<V> for Option<Arc<dyn Repository<V>>> {
34 fn require(&self) -> Result<&Arc<dyn Repository<V>>, RepositoryError> {
35 self.as_ref()
36 .ok_or(StateRegistryError::DatabaseNotInitialized.into())
37 }
38}
39
40#[async_trait::async_trait]
43pub trait Repository<V: RepositoryItem>: Send + Sync {
44 async fn get(&self, key: V::Key) -> Result<Option<V>, RepositoryError>;
46 async fn list(&self) -> Result<Vec<V>, RepositoryError>;
48 async fn set(&self, key: V::Key, value: V) -> Result<(), RepositoryError>;
50 async fn set_bulk(&self, values: Vec<(V::Key, V)>) -> Result<(), RepositoryError>;
52 async fn remove(&self, key: V::Key) -> Result<(), RepositoryError>;
54 async fn remove_bulk(&self, keys: Vec<V::Key>) -> Result<(), RepositoryError>;
56 async fn remove_all(&self) -> Result<(), RepositoryError>;
58
59 async fn replace_all(&self, values: Vec<(V::Key, V)>) -> Result<(), RepositoryError> {
64 self.remove_all().await?;
65 self.set_bulk(values).await
66 }
67}
68
69pub trait RepositoryItem: Internal + Persist {
76 const NAME: &'static str;
78
79 type Key: ToString + Send + Sync + 'static;
81
82 fn type_id() -> TypeId {
84 TypeId::of::<Self>()
85 }
86
87 fn data() -> RepositoryItemData {
89 RepositoryItemData::new::<Self>()
90 }
91}
92
93#[allow(dead_code)]
95#[derive(Debug, Clone, Copy)]
96pub struct RepositoryItemData {
97 type_id: TypeId,
98 name: &'static str,
99}
100
101impl RepositoryItemData {
102 pub fn new<T: RepositoryItem>() -> Self {
104 Self {
105 type_id: TypeId::of::<T>(),
106 name: T::NAME,
107 }
108 }
109
110 pub fn type_id(&self) -> TypeId {
112 self.type_id
113 }
114 pub fn name(&self) -> &'static str {
117 self.name
118 }
119}
120
121pub const fn validate_registry_name(name: &str) -> bool {
126 let bytes = name.as_bytes();
127 let mut i = 0;
128 while i < bytes.len() {
129 let byte = bytes[i];
130 if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') || byte == b'_') {
132 return false;
133 }
134 i += 1;
135 }
136 true
137}
138
139#[derive(Debug, Clone)]
141pub struct RepositoryMigrations {
142 pub(crate) steps: Vec<RepositoryMigrationStep>,
143 #[allow(dead_code)]
145 pub(crate) version: u32,
146}
147
148#[derive(Debug, Clone, Copy)]
150pub enum RepositoryMigrationStep {
151 Add(RepositoryItemData),
153 Remove(RepositoryItemData),
155}
156
157impl RepositoryMigrations {
158 pub fn new(steps: Vec<RepositoryMigrationStep>) -> Self {
161 Self {
162 version: steps.len() as u32,
163 steps,
164 }
165 }
166
167 pub fn into_repository_items(self) -> Vec<RepositoryItemData> {
169 let mut map = std::collections::HashMap::new();
170 for step in self.steps {
171 match step {
172 RepositoryMigrationStep::Add(data) => {
173 map.insert(data.type_id, data);
174 }
175 RepositoryMigrationStep::Remove(data) => {
176 map.remove(&data.type_id);
177 }
178 }
179 }
180 map.into_values().collect()
181 }
182}
183
184#[macro_export]
187macro_rules! register_repository_item {
188 ($keyty:ty => $ty:ty, $name:literal) => {
189 const _: () = {
190 impl $crate::repository::___internal::Internal for $ty {}
191 impl $crate::repository::RepositoryItem for $ty {
192 const NAME: &'static str = $name;
193 type Key = $keyty;
194 }
195 assert!(
196 $crate::repository::validate_registry_name($name),
197 concat!(
198 "Repository name '",
199 $name,
200 "' must contain only alphabetic characters and underscores"
201 )
202 )
203 };
204 };
205}
206
207#[doc(hidden)]
210pub mod ___internal {
211
212 pub trait Internal {}
215}
216pub(crate) use ___internal::Internal;
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_validate_name() {
224 assert!(validate_registry_name("valid"));
225 assert!(validate_registry_name("Valid_Name"));
226 assert!(!validate_registry_name("Invalid-Name"));
227 assert!(!validate_registry_name("Invalid Name"));
228 assert!(!validate_registry_name("Invalid.Name"));
229 assert!(!validate_registry_name("Invalid123"));
230 }
231}