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: String) -> Result<Option<V>, RepositoryError>;
33 async fn list(&self) -> Result<Vec<V>, RepositoryError>;
35 async fn set(&self, key: String, value: V) -> Result<(), RepositoryError>;
37 async fn remove(&self, key: String) -> Result<(), RepositoryError>;
39}
40
41pub trait RepositoryItem: Internal + Serialize + DeserializeOwned + Send + Sync + 'static {
48 const NAME: &'static str;
50
51 fn type_id() -> TypeId {
53 TypeId::of::<Self>()
54 }
55
56 fn data() -> RepositoryItemData {
58 RepositoryItemData::new::<Self>()
59 }
60}
61
62#[allow(dead_code)]
64#[derive(Debug, Clone, Copy)]
65pub struct RepositoryItemData {
66 type_id: TypeId,
67 name: &'static str,
68}
69
70impl RepositoryItemData {
71 pub fn new<T: RepositoryItem>() -> Self {
73 Self {
74 type_id: TypeId::of::<T>(),
75 name: T::NAME,
76 }
77 }
78
79 pub fn type_id(&self) -> TypeId {
81 self.type_id
82 }
83 pub fn name(&self) -> &'static str {
86 self.name
87 }
88}
89
90pub const fn validate_registry_name(name: &str) -> bool {
95 let bytes = name.as_bytes();
96 let mut i = 0;
97 while i < bytes.len() {
98 let byte = bytes[i];
99 if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') || byte == b'_') {
101 return false;
102 }
103 i += 1;
104 }
105 true
106}
107
108#[derive(Debug, Clone)]
110pub struct RepositoryMigrations {
111 pub(crate) steps: Vec<RepositoryMigrationStep>,
112 #[allow(dead_code)]
114 pub(crate) version: u32,
115}
116
117#[derive(Debug, Clone, Copy)]
119pub enum RepositoryMigrationStep {
120 Add(RepositoryItemData),
122 Remove(RepositoryItemData),
124}
125
126impl RepositoryMigrations {
127 pub fn new(steps: Vec<RepositoryMigrationStep>) -> Self {
130 Self {
131 version: steps.len() as u32,
132 steps,
133 }
134 }
135
136 pub fn into_repository_items(self) -> Vec<RepositoryItemData> {
138 let mut map = std::collections::HashMap::new();
139 for step in self.steps {
140 match step {
141 RepositoryMigrationStep::Add(data) => {
142 map.insert(data.type_id, data);
143 }
144 RepositoryMigrationStep::Remove(data) => {
145 map.remove(&data.type_id);
146 }
147 }
148 }
149 map.into_values().collect()
150 }
151}
152
153#[macro_export]
156macro_rules! register_repository_item {
157 ($ty:ty, $name:literal) => {
158 const _: () = {
159 impl $crate::repository::___internal::Internal for $ty {}
160 impl $crate::repository::RepositoryItem for $ty {
161 const NAME: &'static str = $name;
162 }
163 assert!(
164 $crate::repository::validate_registry_name($name),
165 concat!(
166 "Repository name '",
167 $name,
168 "' must contain only alphabetic characters and underscores"
169 )
170 )
171 };
172 };
173}
174
175#[doc(hidden)]
178pub mod ___internal {
179
180 pub trait Internal {}
183}
184pub(crate) use ___internal::Internal;
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_validate_name() {
192 assert!(validate_registry_name("valid"));
193 assert!(validate_registry_name("Valid_Name"));
194 assert!(!validate_registry_name("Invalid-Name"));
195 assert!(!validate_registry_name("Invalid Name"));
196 assert!(!validate_registry_name("Invalid.Name"));
197 assert!(!validate_registry_name("Invalid123"));
198 }
199}