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#[macro_export]
106macro_rules! register_repository_item {
107 ($ty:ty, $name:literal) => {
108 const _: () = {
109 impl $crate::repository::___internal::Internal for $ty {}
110 impl $crate::repository::RepositoryItem for $ty {
111 const NAME: &'static str = $name;
112 }
113 assert!(
114 $crate::repository::validate_registry_name($name),
115 concat!(
116 "Repository name '",
117 $name,
118 "' must contain only alphabetic characters and underscores"
119 )
120 )
121 };
122 };
123}
124
125#[doc(hidden)]
128pub mod ___internal {
129
130 pub trait Internal {}
133}
134pub(crate) use ___internal::Internal;
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_validate_name() {
142 assert!(validate_registry_name("valid"));
143 assert!(validate_registry_name("Valid_Name"));
144 assert!(!validate_registry_name("Invalid-Name"));
145 assert!(!validate_registry_name("Invalid Name"));
146 assert!(!validate_registry_name("Invalid.Name"));
147 assert!(!validate_registry_name("Invalid123"));
148 }
149}