Skip to content

Commit 0c9fb99

Browse files
committed
Added tree and test with implementation for CollectionView
1 parent 5cb4eb0 commit 0c9fb99

File tree

3 files changed

+262
-2
lines changed

3 files changed

+262
-2
lines changed

crates/bitwarden-vault/src/collection.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use bitwarden_core::{
66
use bitwarden_crypto::{CryptoError, Decryptable, EncString, IdentifyKey, KeyStoreContext};
77
use schemars::JsonSchema;
88
use serde::{Deserialize, Serialize};
9+
use std::fmt::Debug;
910
use uuid::Uuid;
10-
11+
use crate::tree::TreeItem;
1112
use crate::VaultParseError;
1213

1314
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
@@ -25,7 +26,7 @@ pub struct Collection {
2526
pub manage: bool,
2627
}
2728

28-
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
29+
#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
2930
#[serde(rename_all = "camelCase", deny_unknown_fields)]
3031
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
3132
pub struct CollectionView {
@@ -81,3 +82,22 @@ impl TryFrom<CollectionDetailsResponseModel> for Collection {
8182
})
8283
}
8384
}
85+
86+
impl TreeItem for CollectionView {
87+
fn id(&self) -> Option<Uuid> {
88+
self.id
89+
}
90+
91+
fn short_name(&self) -> &str {
92+
&self.path().last().unwrap()
93+
}
94+
95+
fn path(&self) -> Vec<&str> {
96+
self.name
97+
.split(Self::DELIMITER)
98+
.filter(|s| !s.is_empty())
99+
.collect::<Vec<&str>>()
100+
}
101+
102+
const DELIMITER: char = '/';
103+
}

crates/bitwarden-vault/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ mod mobile;
2727
pub use mobile::attachment_client::{DecryptFileError, EncryptFileError};
2828
mod sync;
2929
mod totp_client;
30+
mod tree;
31+
3032
pub use sync::{SyncRequest, SyncResponse};

crates/bitwarden-vault/src/tree.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use std::collections::HashMap;
2+
use std::fmt::Debug;
3+
use uuid::Uuid;
4+
5+
pub trait TreeItem: Clone + Debug {
6+
fn id(&self) -> Option<Uuid>;
7+
/*
8+
This is the name that will be output when getting the tree nodes
9+
*/
10+
fn short_name(&self) -> &str;
11+
/*
12+
This is the path that the item is stored into a tree
13+
*/
14+
fn path(&self) -> Vec<&str>;
15+
const DELIMITER: char;
16+
}
17+
18+
#[derive(Clone, Debug)]
19+
pub struct TreeIndex<T: TreeItem> {
20+
pub id: usize, // location in the tree
21+
pub data: T, // this will be the raw value
22+
pub path: Vec<String>
23+
}
24+
25+
impl<T: TreeItem> TreeIndex<T> {
26+
pub fn new(id: usize, data: T) -> Self {
27+
TreeIndex {
28+
id,
29+
data: data.clone(),
30+
path: data.path().iter().map(|s| s.to_string()).collect(),
31+
}
32+
}
33+
}
34+
35+
pub struct NodeItem<T: TreeItem> {
36+
pub item: T,
37+
pub parent: Option<T>,
38+
pub children: Vec<T>
39+
}
40+
41+
pub struct TreeNode {
42+
pub id: usize,
43+
pub item_id: Uuid,
44+
pub parent_idx: Option<usize>,
45+
pub children_idx: Vec<usize>,
46+
pub path: Vec<String>
47+
}
48+
49+
impl TreeNode {
50+
pub fn new<T: TreeItem>(id: usize, parent_idx: Option<usize>, children_idx: Vec<usize>, index: TreeIndex<T>) -> Self {
51+
TreeNode {
52+
id,
53+
item_id: index.data.id().unwrap(),
54+
parent_idx,
55+
children_idx,
56+
path: index.path,
57+
}
58+
}
59+
}
60+
61+
pub struct Tree<T: TreeItem> {
62+
pub nodes: Vec<TreeNode>,
63+
pub items: Vec<TreeIndex<T>>,
64+
path_to_node: HashMap<Vec<String>, usize>
65+
}
66+
67+
impl<T: TreeItem> Tree<T> {
68+
pub fn from_items(items: &mut Vec<T>) -> Self {
69+
let mut tree = Tree{
70+
nodes: Vec::new(),
71+
items: Vec::new(),
72+
path_to_node: HashMap::new()
73+
};
74+
75+
// sort items
76+
items.sort_by(|a, b| a.path().len().cmp(&b.path().len()));
77+
78+
// add items
79+
for (index, item) in items.iter().enumerate() {
80+
let tree_index = TreeIndex::new(index, item.clone());
81+
tree.items.push(tree_index.clone());
82+
83+
tree.add_item(tree_index);
84+
}
85+
86+
tree
87+
}
88+
89+
fn add_item(&mut self, index: TreeIndex<T>) {
90+
let parent_path = index.path[0..index.path.len() - 1].to_vec();
91+
92+
let parent_id = self.path_to_node.get(&parent_path).map(|&id| {
93+
let parent = &mut self.nodes[id];
94+
parent.children_idx.push(index.id);
95+
parent.id
96+
});
97+
98+
// add new node
99+
let node = TreeNode::new(index.id, parent_id, vec![], index);
100+
self.path_to_node.insert(node.path.clone(), node.id);
101+
self.nodes.push(node);
102+
103+
}
104+
105+
fn get_item_by_id(&self, tree_item_id: Uuid) -> Option<NodeItem<T>> {
106+
let item = self.items.iter().find(|i| i.data.id() == Some(tree_item_id));
107+
108+
if let Some(item) = item {
109+
let node = self.nodes.get(item.id)?;
110+
111+
// Get the parent if it exists
112+
let parent = node.parent_idx
113+
.and_then(|pid| self.nodes.get(pid));
114+
115+
// Get all children nodes
116+
let children: Vec<&TreeNode> = node.children_idx.iter()
117+
.filter_map(|&child_id| self.nodes.get(child_id))
118+
.collect();
119+
120+
// Get corresponding items
121+
let parent_item = parent.and_then(|p| self.items.get(p.id));
122+
let children_items: Vec<&TreeIndex<T>> = children.iter()
123+
.filter_map(|child| self.items.get(child.id))
124+
.collect();
125+
126+
return Some(NodeItem {
127+
item: item.data.clone(),
128+
parent: parent_item.map(|p| p.data.clone()),
129+
children: children_items.iter().map(|i| i.data.clone()).collect()
130+
})
131+
}
132+
133+
None
134+
}
135+
}
136+
137+
#[cfg(test)]
138+
mod tests {
139+
use super::*;
140+
use uuid::Uuid;
141+
use crate::tree::TreeItem;
142+
143+
#[derive(Clone, Debug)]
144+
pub struct TestItem {
145+
pub id: Uuid,
146+
pub name: String
147+
}
148+
149+
impl TreeItem for TestItem {
150+
fn id(&self) -> Option<Uuid> {
151+
Some(self.id)
152+
}
153+
154+
fn short_name(&self) -> &str { self.path().last().unwrap() }
155+
156+
fn path(&self) -> Vec<&str> {
157+
self.name
158+
.split(Self::DELIMITER)
159+
.filter(|s| !s.is_empty())
160+
.collect::<Vec<&str>>()
161+
}
162+
163+
const DELIMITER: char = '/';
164+
}
165+
166+
#[test]
167+
fn given_collection_with_one_parent_and_two_children_when_getting_parent_then_parent_is_returned_with_children_and_no_parent() {
168+
let parent_id = Uuid::new_v4();
169+
let mut items = vec![
170+
TestItem {
171+
id: Uuid::new_v4(),
172+
name: "parent/child1".to_string()
173+
},
174+
TestItem {
175+
id: parent_id,
176+
name: "parent".to_string()
177+
},
178+
TestItem {
179+
id: Uuid::new_v4(),
180+
name: "parent/child2".to_string()
181+
},
182+
];
183+
184+
let node_option = Tree::from_items(&mut items)
185+
.get_item_by_id(parent_id);
186+
187+
if let Some(node) = node_option {
188+
let item = node.item;
189+
let parent = node.parent;
190+
let children = node.children;
191+
192+
assert_eq!(children.len(), 2);
193+
assert_eq!(item.id(), Some(parent_id));
194+
assert_eq!(item.short_name(), "parent");
195+
assert_eq!(item.path(), ["parent"]);
196+
assert!(parent.is_none());
197+
} else {
198+
panic!("Node not found");
199+
}
200+
}
201+
202+
#[test]
203+
fn given_collection_with_one_parent_and_two_children_when_getting_child1_then_child1_is_returned_with_no_children_and_a_parent() {
204+
let child_1_id = Uuid::new_v4();
205+
let parent_id = Uuid::new_v4();
206+
let mut items = vec![
207+
TestItem {
208+
id: child_1_id,
209+
name: "parent/child1".to_string()
210+
},
211+
TestItem {
212+
id: parent_id,
213+
name: "parent".to_string()
214+
},
215+
TestItem {
216+
id: Uuid::new_v4(),
217+
name: "parent/child2".to_string()
218+
},
219+
];
220+
221+
let node_option = Tree::from_items(&mut items)
222+
.get_item_by_id(child_1_id);
223+
224+
if let Some(node) = node_option {
225+
let item = node.item;
226+
let parent = node.parent;
227+
let children = node.children;
228+
229+
assert_eq!(children.len(), 0);
230+
assert_eq!(item.id(), Some(child_1_id));
231+
assert_eq!(item.short_name(), "child1");
232+
assert_eq!(item.path(), ["parent", "child1"]);
233+
assert_eq!(parent.unwrap().id, parent_id);
234+
} else {
235+
panic!("Node not found");
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)