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