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 .map_err(ApiError::from)?;
56
57 let folder: Folder = resp.try_into()?;
58
59 debug_assert!(folder.id.unwrap_or_default() == folder_id);
60
61 repository.set(folder_id, folder.clone()).await?;
62
63 Ok(self.key_store.decrypt(&folder)?)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use std::sync::Arc;
70
71 use bitwarden_api_api::{apis::ApiClient, models::FolderResponseModel};
72 use bitwarden_core::{
73 client::ApiConfigurations, key_management::create_test_crypto_with_user_key,
74 };
75 use bitwarden_crypto::SymmetricCryptoKey;
76 use bitwarden_test::MemoryRepository;
77 use uuid::uuid;
78
79 use super::*;
80
81 fn create_client(api_client: ApiClient) -> FoldersClient {
82 FoldersClient {
83 key_store: create_test_crypto_with_user_key(
84 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
85 ),
86 api_configurations: Arc::new(ApiConfigurations::from_api_client(api_client)),
87 repository: Some(Arc::new(MemoryRepository::<Folder>::default())),
88 }
89 }
90
91 async fn repository_add_folder(client: &FoldersClient, folder_id: FolderId, name: &str) {
92 let folder_view = FolderView {
93 id: Some(folder_id),
94 name: name.to_string(),
95 revision_date: "2024-01-01T00:00:00Z".parse().unwrap(),
96 };
97 let folder: Folder = client.key_store.encrypt(folder_view).unwrap();
98 client
99 .repository
100 .as_ref()
101 .unwrap()
102 .set(folder_id, folder)
103 .await
104 .unwrap();
105 }
106
107 #[tokio::test]
108 async fn test_edit_folder() {
109 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
110
111 let client = create_client(ApiClient::new_mocked(move |mock| {
112 mock.folders_api
113 .expect_put()
114 .returning(move |id, model| {
115 assert_eq!(id, folder_id.to_string());
116 Ok(FolderResponseModel {
117 object: Some("folder".to_string()),
118 id: Some(folder_id.into()),
119 name: Some(model.unwrap().name),
120 revision_date: Some("2025-01-01T00:00:00Z".to_string()),
121 })
122 })
123 .once();
124 }));
125
126 repository_add_folder(&client, folder_id, "old_name").await;
127
128 let result = client
129 .edit(
130 folder_id,
131 FolderAddEditRequest {
132 name: "test".to_string(),
133 },
134 )
135 .await
136 .unwrap();
137
138 assert_eq!(
139 result,
140 FolderView {
141 id: Some(folder_id),
142 name: "test".to_string(),
143 revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
144 }
145 );
146
147 assert_eq!(
149 client
150 .key_store
151 .decrypt(
152 &client
153 .repository
154 .as_ref()
155 .unwrap()
156 .get(folder_id)
157 .await
158 .unwrap()
159 .unwrap()
160 )
161 .unwrap(),
162 result
163 );
164 }
165
166 #[tokio::test]
167 async fn test_edit_folder_does_not_exist() {
168 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
169
170 let client = create_client(ApiClient::new_mocked(|_| {}));
171
172 let result = client
173 .edit(
174 folder_id,
175 FolderAddEditRequest {
176 name: "test".to_string(),
177 },
178 )
179 .await;
180
181 assert!(result.is_err());
182 assert!(matches!(
183 result.unwrap_err(),
184 EditFolderError::ItemNotFound(_)
185 ));
186 }
187
188 #[tokio::test]
189 async fn test_edit_folder_http_error() {
190 let folder_id = FolderId::new(uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1"));
191
192 let client = create_client(ApiClient::new_mocked(move |mock| {
193 mock.folders_api.expect_put().returning(move |_id, _model| {
194 Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other(
195 "Simulated error",
196 )))
197 });
198 }));
199
200 repository_add_folder(&client, folder_id, "old_name").await;
201
202 let result = client
203 .edit(
204 folder_id,
205 FolderAddEditRequest {
206 name: "test".to_string(),
207 },
208 )
209 .await;
210
211 assert!(result.is_err());
212 assert!(matches!(result.unwrap_err(), EditFolderError::Api(_)));
213 }
214}