1use std::{
19 any::TypeId,
20 collections::HashMap,
21 future::Future,
22 pin::Pin,
23 str::FromStr,
24 sync::{Arc, RwLock},
25};
26
27use serde_json::Value;
28
29use crate::{
30 registry::StateRegistry,
31 repository::{RepositoryItem, RepositoryMigrations},
32};
33
34#[bitwarden_ffi::wasm_object]
41pub struct StateDebug {
42 registry: Arc<StateRegistry>,
43}
44
45impl StateDebug {
46 pub(crate) fn new(registry: Arc<StateRegistry>) -> Self {
48 Self { registry }
49 }
50}
51
52pub trait StateRegistryDebugExt {
58 fn debug(&self) -> StateDebug;
60}
61
62impl StateRegistryDebugExt for Arc<StateRegistry> {
63 fn debug(&self) -> StateDebug {
64 StateDebug::new(self.clone())
65 }
66}
67
68#[bitwarden_ffi::wasm_export]
69impl StateDebug {
70 pub fn types(&self) -> Vec<String> {
72 self.registry.debug_types()
73 }
74
75 pub async fn list(&self, type_name: String) -> String {
78 Value::Array(self.registry.debug_list(&type_name).await).to_string()
79 }
80
81 pub async fn get(&self, type_name: String, key: String) -> String {
84 self.registry
85 .debug_get(&type_name, &key)
86 .await
87 .unwrap_or(Value::Null)
88 .to_string()
89 }
90
91 pub async fn set(&self, type_name: String, key: String, value: String) -> bool {
96 let Ok(value) = serde_json::from_str::<Value>(&value) else {
97 return false;
98 };
99 self.registry.debug_set(&type_name, &key, value).await
100 }
101}
102
103type DebugFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
106type DebugListFn = for<'a> fn(&'a StateRegistry) -> DebugFuture<'a, Vec<Value>>;
108type DebugGetFn = for<'a> fn(&'a StateRegistry, &'a str) -> DebugFuture<'a, Option<Value>>;
110type DebugSetFn = for<'a> fn(&'a StateRegistry, &'a str, Value) -> DebugFuture<'a, bool>;
112
113#[derive(Debug, Clone, Copy)]
117pub(crate) struct DebugRepo {
118 name: &'static str,
119 list: DebugListFn,
120 get: DebugGetFn,
121 set: DebugSetFn,
122}
123
124impl DebugRepo {
125 pub(crate) fn for_type<T: RepositoryItem>() -> Self {
127 Self {
128 name: T::NAME,
129 list: |registry| Box::pin(list_repo::<T>(registry)),
130 get: |registry, key| Box::pin(get_repo::<T>(registry, key)),
131 set: |registry, key, value| Box::pin(set_repo::<T>(registry, key, value)),
132 }
133 }
134}
135
136#[derive(Default)]
140pub(crate) struct DebugRegistry {
141 repos: RwLock<HashMap<TypeId, DebugRepo>>,
142}
143
144impl DebugRegistry {
145 pub(crate) fn new() -> Self {
147 Self::default()
148 }
149
150 pub(crate) fn register<T: RepositoryItem>(&self) {
152 self.insert(TypeId::of::<T>(), DebugRepo::for_type::<T>());
153 }
154
155 pub(crate) fn register_migrations(&self, migrations: &RepositoryMigrations) {
159 for item in migrations.clone().into_repository_items() {
160 self.insert(item.type_id(), item.debug);
161 }
162 }
163
164 fn insert(&self, type_id: TypeId, repo: DebugRepo) {
165 self.repos
166 .write()
167 .expect("RwLock should not be poisoned")
168 .insert(type_id, repo);
169 }
170
171 fn named(&self, type_name: &str) -> Option<DebugRepo> {
173 self.repos
174 .read()
175 .expect("RwLock should not be poisoned")
176 .values()
177 .find(|repo| repo.name == type_name)
178 .copied()
179 }
180
181 fn names(&self) -> Vec<String> {
183 self.repos
184 .read()
185 .expect("RwLock should not be poisoned")
186 .values()
187 .map(|repo| repo.name.to_string())
188 .collect()
189 }
190}
191
192impl StateRegistry {
195 pub fn debug_register_migrations(&self, migrations: &RepositoryMigrations) {
202 self.debug.register_migrations(migrations);
203 }
204
205 pub(crate) fn debug_types(&self) -> Vec<String> {
207 self.debug.names()
208 }
209
210 pub(crate) async fn debug_list(&self, type_name: &str) -> Vec<Value> {
213 match self.debug.named(type_name) {
214 Some(repo) => (repo.list)(self).await,
215 None => Vec::new(),
216 }
217 }
218
219 pub(crate) async fn debug_get(&self, type_name: &str, key: &str) -> Option<Value> {
221 let repo = self.debug.named(type_name)?;
222 (repo.get)(self, key).await
223 }
224
225 pub(crate) async fn debug_set(&self, type_name: &str, key: &str, value: Value) -> bool {
228 match self.debug.named(type_name) {
229 Some(repo) => (repo.set)(self, key, value).await,
230 None => false,
231 }
232 }
233}
234
235async fn list_repo<T: RepositoryItem>(registry: &StateRegistry) -> Vec<Value> {
237 let Ok(repository) = registry.get::<T>() else {
238 return Vec::new();
239 };
240 match repository.list().await {
241 Ok(items) => items
242 .iter()
243 .filter_map(|item| serde_json::to_value(item).ok())
244 .collect(),
245 Err(_) => Vec::new(),
246 }
247}
248
249async fn get_repo<T: RepositoryItem>(registry: &StateRegistry, key: &str) -> Option<Value> {
251 let key = <T::Key as FromStr>::from_str(key).ok()?;
252 let value = registry.get::<T>().ok()?.get(key).await.ok().flatten()?;
253 serde_json::to_value(&value).ok()
254}
255
256async fn set_repo<T: RepositoryItem>(registry: &StateRegistry, key: &str, value: Value) -> bool {
259 let Ok(key) = <T::Key as FromStr>::from_str(key) else {
260 return false;
261 };
262 let Ok(value) = serde_json::from_value::<T>(value) else {
263 return false;
264 };
265 match registry.get::<T>() {
266 Ok(repository) => repository.set(key, value).await.is_ok(),
267 Err(_) => false,
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use serde::{Deserialize, Serialize};
274 use serde_json::json;
275
276 use super::*;
277 use crate::{
278 register_repository_item,
279 repository::{RepositoryMigrationStep, RepositoryMigrations},
280 };
281
282 #[derive(Serialize, Deserialize, PartialEq, Debug)]
283 struct SdkOnlyItem {
284 value: String,
285 }
286 register_repository_item!(String => SdkOnlyItem, "SdkOnlyItem");
287
288 #[tokio::test]
293 async fn debug_register_migrations_exposes_sdk_managed_types() {
294 let registry = StateRegistry::new_with_memory_db();
295 assert!(!registry.debug_types().contains(&"SdkOnlyItem".to_string()));
296
297 let migrations =
298 RepositoryMigrations::new(vec![RepositoryMigrationStep::Add(SdkOnlyItem::data())]);
299 registry.debug_register_migrations(&migrations);
300 assert!(registry.debug_types().contains(&"SdkOnlyItem".to_string()));
301
302 let value = json!({ "value": "hello" });
303 assert!(registry.debug_set("SdkOnlyItem", "k1", value.clone()).await);
304 assert_eq!(registry.debug_get("SdkOnlyItem", "k1").await, Some(value));
305 assert_eq!(registry.debug_list("SdkOnlyItem").await.len(), 1);
306 }
307}