bitwarden_vault/folder/
edit.rs1use bitwarden_core::{ApiError, MissingFieldError};
2use bitwarden_crypto::CryptoError;
3use bitwarden_error::bitwarden_error;
4use bitwarden_state::repository::{RepositoryError, RepositoryOption};
5use thiserror::Error;
6#[cfg(feature = "wasm")]
7use wasm_bindgen::prelude::*;
8
9use crate::{
10 Folder, FolderAddEditRequest, FolderId, FolderView, FoldersClient, ItemNotFoundError,
11 VaultParseError,
12};
13
14#[allow(missing_docs)]
15#[bitwarden_error(flat)]
16#[derive(Debug, Error)]
17pub enum EditFolderError {
18 #[error(transparent)]
19 ItemNotFound(#[from] ItemNotFoundError),
20 #[error(transparent)]
21 Crypto(#[from] CryptoError),
22 #[error(transparent)]
23 Api(#[from] ApiError),
24 #[error(transparent)]
25 VaultParse(#[from] VaultParseError),
26 #[error(transparent)]
27 MissingField(#[from] MissingFieldError),
28 #[error(transparent)]
29 Repository(#[from] RepositoryError),
30 #[error(transparent)]
31 Uuid(#[from] uuid::Error),
32}
33
34#[cfg_attr(feature = "wasm", wasm_bindgen)]
35impl FoldersClient {
36 pub async fn edit(
38 &self,
39 folder_id: FolderId,
40 request: FolderAddEditRequest,
41 ) -> Result<FolderView, EditFolderError> {
42 let repository = self.repository.require()?;
43
44 repository.get(folder_id).await?.ok_or(ItemNotFoundError)?;
46
47 let folder_request = self.key_store.encrypt(request)?;
48
49 let resp = self
50 .api_configurations
51 .api_client
52 .folders_api()
53 .put(&folder_id.to_string(), Some(folder_request))
54 .await?;
55
56 let folder: Folder = resp.try_into()?;
57
58 debug_assert!(folder.id.unwrap_or_default() == folder_id);
59
60 repository.set(folder_id, folder.clone()).await?;
61
62 Ok(self.key_store.decrypt(&folder)?)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use std::sync::Arc;
69
70 use bitwarden_api_api::{apis::ApiClient, models::FolderResponseModel};
71 use bitwarden_core::{
72 client::ApiConfigurations, key_management::create_test_crypto_with_user_key,
73 };
74 use bitwarden_crypto::{SymmetricCryptoKey, SymmetricKeyAlgorithm};
75 use bitwarden_test::MemoryRepository;
76 use uuid::uuid;
77
78 use super::*;
79
80 fn create_client(api_client: ApiClient) -> FoldersClient {
81 FoldersClient {
82 key_store: create_test_crypto_with_user_key(SymmetricCryptoKey::make(
83 SymmetricKeyAlgorithm::Aes256CbcHmac,
84 )),
85 api_configurations: Arc::new(ApiConfigurations::from_api_client(api_client)),
86 repository: Some(Arc::new(MemoryRepository::<Folder>::default())),
87 }
88 }
89
90 async fn repository_add_folder(client: &FoldersClient, folder_id: FolderId, name: &str) {
91 let folder_view = FolderView {
92 id: Some(folder_id),
93 name: name.to_string(),
94 revision_date: "2024-01-01T00:00:00Z".parse().unwrap(),
95 };
96 let folder: Folder = client.key_store.encrypt(folder_view).unwrap();
97 client
98 .repository
99 .as_ref()
100 .unwrap()
101 .set(folder_id, folder)
102 .await
103 .unwrap();
104 }
105
106 #[tokio::test]
107 async fn test_edit_folder() {
108 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
109
110 let client = create_client(ApiClient::new_mocked(move |mock| {
111 mock.folders_api
112 .expect_put()
113 .returning(move |id, model| {
114 assert_eq!(id, folder_id.to_string());
115 Ok(FolderResponseModel {
116 object: Some("folder".to_string()),
117 id: Some(folder_id.into()),
118 name: Some(model.unwrap().name),
119 revision_date: Some("2025-01-01T00:00:00Z".to_string()),
120 })
121 })
122 .once();
123 }));
124
125 repository_add_folder(&client, folder_id, "old_name").await;
126
127 let result = client
128 .edit(
129 folder_id,
130 FolderAddEditRequest {
131 name: "test".to_string(),
132 },
133 )
134 .await
135 .unwrap();
136
137 assert_eq!(
138 result,
139 FolderView {
140 id: Some(folder_id),
141 name: "test".to_string(),
142 revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
143 }
144 );
145
146 assert_eq!(
148 client
149 .key_store
150 .decrypt(
151 &client
152 .repository
153 .as_ref()
154 .unwrap()
155 .get(folder_id)
156 .await
157 .unwrap()
158 .unwrap()
159 )
160 .unwrap(),
161 result
162 );
163 }
164
165 #[tokio::test]
166 async fn test_edit_folder_does_not_exist() {
167 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
168
169 let client = create_client(ApiClient::new_mocked(|_| {}));
170
171 let result = client
172 .edit(
173 folder_id,
174 FolderAddEditRequest {
175 name: "test".to_string(),
176 },
177 )
178 .await;
179
180 assert!(result.is_err());
181 assert!(matches!(
182 result.unwrap_err(),
183 EditFolderError::ItemNotFound(_)
184 ));
185 }
186
187 #[tokio::test]
188 async fn test_edit_folder_http_error() {
189 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
190
191 let client = create_client(ApiClient::new_mocked(move |mock| {
192 mock.folders_api
193 .expect_put()
194 .returning(move |_id, _model| Err(std::io::Error::other("Simulated error").into()));
195 }));
196
197 repository_add_folder(&client, folder_id, "old_name").await;
198
199 let result = client
200 .edit(
201 folder_id,
202 FolderAddEditRequest {
203 name: "test".to_string(),
204 },
205 )
206 .await;
207
208 assert!(result.is_err());
209 assert!(matches!(result.unwrap_err(), EditFolderError::Api(_)));
210 }
211}