Skip to main content

bitwarden_vault/folder/
edit.rs

1use 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    /// Edit the [Folder] and save it to the server.
37    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        // Verify the folder we're updating exists
45        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        // Confirm the folder was updated in the repository
148        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}