bitwarden_state/
repository.rs1use std::any::TypeId;
2
3use crate::registry::RepositoryNotFoundError;
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 RepositoryNotFound(#[from] RepositoryNotFoundError),
23}
24
25#[async_trait::async_trait]
28pub trait Repository<V: RepositoryItem>: Send + Sync {
29 async fn get(&self, key: String) -> Result<Option<V>, RepositoryError>;
31 async fn list(&self) -> Result<Vec<V>, RepositoryError>;
33 async fn set(&self, key: String, value: V) -> Result<(), RepositoryError>;
35 async fn remove(&self, key: String) -> Result<(), RepositoryError>;
37}
38
39pub trait RepositoryItem: Internal + Send + Sync + 'static {
43 const NAME: &'static str;
45
46 fn type_id() -> TypeId {
48 TypeId::of::<Self>()
49 }
50
51 fn data() -> RepositoryItemData {
53 RepositoryItemData::new::<Self>()
54 }
55}
56
57#[allow(dead_code)]
59#[derive(Debug, Clone, Copy)]
60pub struct RepositoryItemData {
61 type_id: TypeId,
62 name: &'static str,
63}
64
65impl RepositoryItemData {
66 pub fn new<T: RepositoryItem + ?Sized>() -> Self {
68 Self {
69 type_id: TypeId::of::<T>(),
70 name: T::NAME,
71 }
72 }
73
74 pub fn type_id(&self) -> TypeId {
76 self.type_id
77 }
78 pub fn name(&self) -> &'static str {
81 self.name
82 }
83}
84
85pub const fn validate_registry_name(name: &str) -> bool {
90 let bytes = name.as_bytes();
91 let mut i = 0;
92 while i < bytes.len() {
93 let byte = bytes[i];
94 if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') || byte == b'_') {
96 return false;
97 }
98 i += 1;
99 }
100 true
101}
102
103#[derive(Debug, Clone)]
105pub struct RepositoryMigrations {
106 pub(crate) steps: Vec<RepositoryMigrationStep>,
107 #[allow(dead_code)]
109 pub(crate) version: u32,
110}
111
112#[derive(Debug, Clone, Copy)]
114pub enum RepositoryMigrationStep {
115 Add(RepositoryItemData),
117 Remove(RepositoryItemData),
119}
120
121impl RepositoryMigrations {
122 pub fn new(steps: Vec<RepositoryMigrationStep>) -> Self {
125 Self {
126 version: steps.len() as u32,
127 steps,
128 }
129 }
130
131 pub fn into_repository_items(self) -> Vec<RepositoryItemData> {
133 let mut map = std::collections::HashMap::new();
134 for step in self.steps {
135 match step {
136 RepositoryMigrationStep::Add(data) => {
137 map.insert(data.type_id, data);
138 }
139 RepositoryMigrationStep::Remove(data) => {
140 map.remove(&data.type_id);
141 }
142 }
143 }
144 map.into_values().collect()
145 }
146}
147
148#[macro_export]
151macro_rules! register_repository_item {
152 ($ty:ty, $name:literal) => {
153 const _: () = {
154 impl $crate::repository::___internal::Internal for $ty {}
155 impl $crate::repository::RepositoryItem for $ty {
156 const NAME: &'static str = $name;
157 }
158 assert!(
159 $crate::repository::validate_registry_name($name),
160 concat!(
161 "Repository name '",
162 $name,
163 "' must contain only alphabetic characters and underscores"
164 )
165 )
166 };
167 };
168}
169
170#[doc(hidden)]
173pub mod ___internal {
174
175 pub trait Internal {}
178}
179pub(crate) use ___internal::Internal;
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_validate_name() {
187 assert!(validate_registry_name("valid"));
188 assert!(validate_registry_name("Valid_Name"));
189 assert!(!validate_registry_name("Invalid-Name"));
190 assert!(!validate_registry_name("Invalid Name"));
191 assert!(!validate_registry_name("Invalid.Name"));
192 assert!(!validate_registry_name("Invalid123"));
193 }
194}