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
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        // Confirm the folder was updated in the repository
147        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}