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