Skip to content

Commit 713bffc

Browse files
committed
Add generic data-store support and wasm macro
1 parent 8f0ea14 commit 713bffc

File tree

11 files changed

+525
-0
lines changed

11 files changed

+525
-0
lines changed

Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ bitwarden-cli = { path = "crates/bitwarden-cli", version = "=1.0.0" }
2525
bitwarden-core = { path = "crates/bitwarden-core", version = "=1.0.0" }
2626
bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=1.0.0" }
2727
bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=1.0.0" }
28+
bitwarden-ffi-macros = { path = "crates/bitwarden-ffi-macros", version = "=1.0.0" }
2829
bitwarden-fido = { path = "crates/bitwarden-fido", version = "=1.0.0" }
2930
bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0" }
3031
bitwarden-ipc = { path = "crates/bitwarden-ipc", version = "=1.0.0" }

crates/bitwarden-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ wasm = [
2828
] # WASM support
2929

3030
[dependencies]
31+
async-trait = ">=0.1.80, <0.2"
3132
base64 = ">=0.22.1, <0.23"
3233
bitwarden-api-api = { workspace = true }
3334
bitwarden-api-identity = { workspace = true }

crates/bitwarden-core/src/client/client.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::internal::InternalClient;
88
use crate::client::flags::Flags;
99
use crate::client::{
1010
client_settings::ClientSettings,
11+
data_store::DataStoreMap,
1112
internal::{ApiConfigurations, Tokens},
1213
};
1314

@@ -86,6 +87,8 @@ impl Client {
8687
})),
8788
external_client,
8889
key_store: KeyStore::default(),
90+
#[cfg(feature = "internal")]
91+
data_store_map: RwLock::new(DataStoreMap::default()),
8992
}),
9093
}
9194
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use std::{
2+
any::{Any, TypeId},
3+
collections::HashMap,
4+
sync::Arc,
5+
};
6+
7+
#[async_trait::async_trait]
8+
pub trait DataStore<T>: Send + Sync {
9+
async fn get(&self, key: String) -> Option<T>;
10+
async fn list(&self) -> Vec<T>;
11+
async fn set(&self, key: String, value: T);
12+
async fn remove(&self, key: String);
13+
}
14+
15+
#[derive(Default)]
16+
pub struct DataStoreMap {
17+
stores: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
18+
}
19+
20+
impl std::fmt::Debug for DataStoreMap {
21+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22+
f.debug_struct("DataStoreMap")
23+
.field("stores", &self.stores.keys())
24+
.finish()
25+
}
26+
}
27+
28+
impl DataStoreMap {
29+
pub fn new() -> Self {
30+
DataStoreMap {
31+
stores: HashMap::new(),
32+
}
33+
}
34+
35+
pub fn insert<T: 'static>(&mut self, value: Arc<dyn DataStore<T>>) {
36+
self.stores.insert(TypeId::of::<T>(), Box::new(value));
37+
}
38+
39+
pub fn get<T: 'static>(&self) -> Option<Arc<dyn DataStore<T>>> {
40+
self.stores
41+
.get(&TypeId::of::<T>())
42+
.and_then(|boxed| boxed.downcast_ref::<Arc<dyn DataStore<T>>>())
43+
.map(Arc::clone)
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
51+
macro_rules! impl_data_store {
52+
($name:ident, $ty:ty) => {
53+
#[async_trait::async_trait]
54+
impl DataStore<$ty> for $name {
55+
async fn get(&self, _key: String) -> Option<$ty> {
56+
Some(self.0.clone())
57+
}
58+
async fn list(&self) -> Vec<$ty> {
59+
unimplemented!()
60+
}
61+
async fn set(&self, _key: String, _value: $ty) {
62+
unimplemented!()
63+
}
64+
async fn remove(&self, _key: String) {
65+
unimplemented!()
66+
}
67+
}
68+
};
69+
}
70+
71+
#[derive(PartialEq, Eq, Debug)]
72+
struct TestA(usize);
73+
#[derive(PartialEq, Eq, Debug)]
74+
struct TestB(String);
75+
#[derive(PartialEq, Eq, Debug)]
76+
struct TestC(Vec<u8>);
77+
78+
impl_data_store!(TestA, usize);
79+
impl_data_store!(TestB, String);
80+
impl_data_store!(TestC, Vec<u8>);
81+
82+
#[tokio::test]
83+
async fn test_data_store_map() {
84+
let a = Arc::new(TestA(145832));
85+
let b = Arc::new(TestB("test".to_string()));
86+
let c = Arc::new(TestC(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]));
87+
88+
let mut map = DataStoreMap::new();
89+
90+
async fn get<T: 'static>(map: &DataStoreMap) -> Option<T> {
91+
map.get::<T>().unwrap().get(String::new()).await
92+
}
93+
94+
assert!(map.get::<usize>().is_none());
95+
assert!(map.get::<String>().is_none());
96+
assert!(map.get::<Vec<u8>>().is_none());
97+
98+
map.insert(a.clone());
99+
assert_eq!(get(&map).await, Some(a.0));
100+
assert!(map.get::<String>().is_none());
101+
assert!(map.get::<Vec<u8>>().is_none());
102+
103+
map.insert(b.clone());
104+
assert_eq!(get(&map).await, Some(a.0));
105+
assert_eq!(get(&map).await, Some(b.0.clone()));
106+
assert!(map.get::<Vec<u8>>().is_none());
107+
108+
map.insert(c.clone());
109+
assert_eq!(get(&map).await, Some(a.0));
110+
assert_eq!(get(&map).await, Some(b.0.clone()));
111+
assert_eq!(get(&map).await, Some(c.0.clone()));
112+
}
113+
}

crates/bitwarden-core/src/client/internal.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::{
1818
};
1919
#[cfg(feature = "internal")]
2020
use crate::{
21+
client::data_store::{DataStore, DataStoreMap},
2122
client::encryption_settings::EncryptionSettingsError,
2223
client::{flags::Flags, login_method::UserLoginMethod},
2324
error::NotAuthenticatedError,
@@ -60,6 +61,9 @@ pub struct InternalClient {
6061
pub(crate) external_client: reqwest::Client,
6162

6263
pub(super) key_store: KeyStore<KeyIds>,
64+
65+
#[cfg(feature = "internal")]
66+
pub(super) data_store_map: RwLock<DataStoreMap>,
6367
}
6468

6569
impl InternalClient {
@@ -219,4 +223,20 @@ impl InternalClient {
219223
) -> Result<(), EncryptionSettingsError> {
220224
EncryptionSettings::set_org_keys(org_keys, &self.key_store)
221225
}
226+
227+
#[cfg(feature = "internal")]
228+
pub fn register_data_store<T: 'static + DataStore<V>, V: 'static>(&self, store: Arc<T>) {
229+
self.data_store_map
230+
.write()
231+
.expect("RwLock is not poisoned")
232+
.insert(store);
233+
}
234+
235+
#[cfg(feature = "internal")]
236+
pub fn get_data_store<T: 'static>(&self) -> Option<Arc<dyn DataStore<T>>> {
237+
self.data_store_map
238+
.read()
239+
.expect("RwLock is not poisoned")
240+
.get()
241+
}
222242
}

crates/bitwarden-core/src/client/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ pub use client_settings::{ClientSettings, DeviceType};
1818

1919
#[cfg(feature = "internal")]
2020
pub mod test_accounts;
21+
22+
#[cfg(feature = "internal")]
23+
pub mod data_store;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "bitwarden-ffi-macros"
3+
description = """
4+
Internal crate for the bitwarden crate. Do not use.
5+
"""
6+
7+
version.workspace = true
8+
authors.workspace = true
9+
edition.workspace = true
10+
rust-version.workspace = true
11+
readme.workspace = true
12+
homepage.workspace = true
13+
repository.workspace = true
14+
license-file.workspace = true
15+
keywords.workspace = true
16+
17+
[features]
18+
wasm = []
19+
20+
[dependencies]
21+
darling = "0.20.10"
22+
proc-macro2 = "1.0.89"
23+
quote = "1.0.37"
24+
syn = "2.0.87"
25+
26+
[lints]
27+
workspace = true
28+
29+
[lib]
30+
proc-macro = true
31+
32+
[dev-dependencies]
33+
js-sys.workspace = true
34+
serde.workspace = true
35+
thiserror.workspace = true
36+
tsify-next.workspace = true
37+
wasm-bindgen.workspace = true

crates/bitwarden-ffi-macros/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Bitwarden WASM Macros
2+
3+
Provides utility macros for simplifying FFI with WebAssembly and UniFFI.

0 commit comments

Comments
 (0)