bitwarden_state/
repository.rs1use std::any::TypeId;
2
3use serde::{Serialize, de::DeserializeOwned};
4
5use crate::registry::StateRegistryError;
6
7#[derive(thiserror::Error, Debug)]
9pub enum RepositoryError {
10 #[error("Internal error: {0}")]
12 Internal(String),
13
14 #[error(transparent)]
16 Serde(#[from] serde_json::Error),
17
18 #[error(transparent)]
20 Database(#[from] crate::sdk_managed::DatabaseError),
21
22 #[error(transparent)]
24 StateRegistry(#[from] StateRegistryError),
25}
26
27#[async_trait::async_trait]
30pub trait Repository<V: RepositoryItem>: Send + Sync {
31 async fn get(&self, key: V::Key) -> Result<Option<V>, RepositoryError>;
33 async fn list(&self) -> Result<Vec<V>, RepositoryError>;
35 async fn set(&self, key: V::Key, value: V) -> Result<(), RepositoryError>;
37 async fn set_bulk(&self, values: Vec<(V::Key, V)>) -> Result<(), RepositoryError>;
39 async fn remove(&self, key: V::Key) -> Result<(), RepositoryError>;
41 async fn remove_bulk(&self, keys: Vec<V::Key>) -> Result<(), RepositoryError>;
43 async fn remove_all(&self) -> Result<(), RepositoryError>;
45}
46
47pub trait RepositoryItem: Internal + Serialize + DeserializeOwned + Send + Sync + 'static {
54 const NAME: &'static str;
56
57 type Key: ToString + Send + Sync + 'static;
59
60 fn type_id() -> TypeId {
62 TypeId::of::<Self>()
63 }
64
65 fn data() -> RepositoryItemData {
67 RepositoryItemData::new::<Self>()
68 }
69}
70
71#[allow(dead_code)]
73#[derive(Debug, Clone, Copy)]
74pub struct RepositoryItemData {
75 type_id: TypeId,
76 name: &'static str,
77}
78
79impl RepositoryItemData {
80 pub fn new<T: RepositoryItem>() -> Self {
82 Self {
83 type_id: TypeId::of::<T>(),
84 name: T::NAME,
85 }
86 }
87
88 pub fn type_id(&self) -> TypeId {
90 self.type_id
91 }
92 pub fn name(&self) -> &'static str {
95 self.name
96 }
97}
98
99pub const fn validate_registry_name(name: &str) -> bool {
104 let bytes = name.as_bytes();
105 let mut i = 0;
106 while i < bytes.len() {
107 let byte = bytes[i];
108 if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') || byte == b'_') {
110 return false;
111 }
112 i += 1;
113 }
114 true
115}
116
117#[derive(Debug, Clone)]
119pub struct RepositoryMigrations {
120 pub(crate) steps: Vec<RepositoryMigrationStep>,
121 #[allow(dead_code)]
123 pub(crate) version: u32,
124}
125
126#[derive(Debug, Clone, Copy)]
128pub enum RepositoryMigrationStep {
129 Add(RepositoryItemData),
131 Remove(RepositoryItemData),
133}
134
135impl RepositoryMigrations {
136 pub fn new(steps: Vec<RepositoryMigrationStep>) -> Self {
139 Self {
140 version: steps.len() as u32,
141 steps,
142 }
143 }
144
145 pub fn into_repository_items(self) -> Vec<RepositoryItemData> {
147 let mut map = std::collections::HashMap::new();
148 for step in self.steps {
149 match step {
150 RepositoryMigrationStep::Add(data) => {
151 map.insert(data.type_id, data);
152 }
153 RepositoryMigrationStep::Remove(data) => {
154 map.remove(&data.type_id);
155 }
156 }
157 }
158 map.into_values().collect()
159 }
160}
161
162#[macro_export]
165macro_rules! register_repository_item {
166 ($keyty:ty => $ty:ty, $name:literal) => {
167 const _: () = {
168 impl $crate::repository::___internal::Internal for $ty {}
169 impl $crate::repository::RepositoryItem for $ty {
170 const NAME: &'static str = $name;
171 type Key = $keyty;
172 }
173 assert!(
174 $crate::repository::validate_registry_name($name),
175 concat!(
176 "Repository name '",
177 $name,
178 "' must contain only alphabetic characters and underscores"
179 )
180 )
181 };
182 };
183}
184
185#[doc(hidden)]
188pub mod ___internal {
189
190 pub trait Internal {}
193}
194pub(crate) use ___internal::Internal;
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_validate_name() {
202 assert!(validate_registry_name("valid"));
203 assert!(validate_registry_name("Valid_Name"));
204 assert!(!validate_registry_name("Invalid-Name"));
205 assert!(!validate_registry_name("Invalid Name"));
206 assert!(!validate_registry_name("Invalid.Name"));
207 assert!(!validate_registry_name("Invalid123"));
208 }
209}