pcloud/folder/
create.rs

1use std::borrow::Cow;
2
3use super::{Folder, FolderIdentifier, FolderResponse};
4
5/// Parameters used for folder creation requests.
6///
7/// This struct is used internally to send a `parent` folder and `name`
8/// to the `createfolder` or `createfolderifnotexists` API endpoints.
9#[derive(Debug, serde::Serialize)]
10struct Params<'a> {
11    /// The parent folder in which the new folder will be created.
12    #[serde(flatten)]
13    parent: FolderIdentifier<'a>,
14
15    /// The name of the new folder.
16    name: Cow<'a, str>,
17}
18
19impl crate::Client {
20    /// Creates a new folder inside the specified parent folder on pCloud.
21    ///
22    /// This function calls the `createfolder` API endpoint and will return
23    /// an error if a folder with the same name already exists in the target location.
24    ///
25    /// # Arguments
26    ///
27    /// * `parent` - A value convertible into a [`FolderIdentifier`] representing the parent folder.
28    /// * `name` - The name of the folder to create.
29    ///
30    /// # Returns
31    ///
32    /// On success, returns a [`Folder`] representing the newly created folder.
33    ///
34    /// # Errors
35    ///
36    /// Returns a [`crate::Error`] if the folder already exists or the request fails.
37    ///
38    /// # Examples
39    ///
40    /// ```rust,no_run
41    /// # async fn example(client: &pcloud::Client) -> Result<(), pcloud::Error> {
42    /// let folder = client.create_folder(0, "new-folder").await?;
43    /// println!("Created folder: {}", folder.base.name);
44    /// # Ok(())
45    /// # }
46    /// ```
47    pub async fn create_folder<'a>(
48        &self,
49        parent: impl Into<FolderIdentifier<'a>>,
50        name: impl Into<Cow<'a, str>>,
51    ) -> crate::Result<Folder> {
52        let params = Params {
53            parent: parent.into(),
54            name: name.into(),
55        };
56        self.get_request::<FolderResponse, _>("createfolder", params)
57            .await
58            .map(|res| res.metadata)
59    }
60}
61
62impl crate::Client {
63    /// Creates a new folder if it does not already exist in the specified location.
64    ///
65    /// This function calls the `createfolderifnotexists` API endpoint, which ensures
66    /// the operation is idempotent: if a folder with the given name exists, it will be returned.
67    /// Otherwise, a new folder is created.
68    ///
69    /// # Arguments
70    ///
71    /// * `parent` - A value convertible into a [`FolderIdentifier`] representing the parent folder.
72    /// * `name` - The name of the folder to create or return if it exists.
73    ///
74    /// # Returns
75    ///
76    /// A [`Folder`] representing the existing or newly created folder.
77    ///
78    /// # Errors
79    ///
80    /// Returns a [`crate::Error`] if the request fails for any reason other than the folder already existing.
81    ///
82    /// # Examples
83    ///
84    /// ```rust,no_run
85    /// # async fn example(client: &pcloud::Client) -> Result<(), pcloud::Error> {
86    /// let folder = client.create_folder_if_not_exists(0, "my-folder").await?;
87    /// println!("Folder ID: {}", folder.folder_id);
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub async fn create_folder_if_not_exists<'a>(
92        &self,
93        parent: impl Into<FolderIdentifier<'a>>,
94        name: impl Into<Cow<'a, str>>,
95    ) -> crate::Result<Folder> {
96        let params = Params {
97            parent: parent.into(),
98            name: name.into(),
99        };
100        self.get_request::<FolderResponse, _>("createfolderifnotexists", params)
101            .await
102            .map(|res| res.metadata)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::{Client, Credentials};
109    use mockito::Matcher;
110
111    #[tokio::test]
112    async fn success() {
113        let mut server = mockito::Server::new_async().await;
114        let m = server
115            .mock("GET", "/createfolder")
116            .match_query(Matcher::AllOf(vec![
117                Matcher::UrlEncoded("access_token".into(), "access-token".into()),
118                Matcher::UrlEncoded("folderid".into(), "0".into()),
119                Matcher::UrlEncoded("name".into(), "testing".into()),
120            ]))
121            .with_status(200)
122            .with_body(
123                r#"{
124    "result": 0,
125    "metadata": {
126        "path": "\/testing",
127        "name": "testing",
128        "created": "Fri, 23 Jul 2021 19:39:09 +0000",
129        "ismine": true,
130        "thumb": false,
131        "modified": "Fri, 23 Jul 2021 19:39:09 +0000",
132        "id": "d10",
133        "isshared": false,
134        "icon": "folder",
135        "isfolder": true,
136        "parentfolderid": 0,
137        "folderid": 10
138    }
139}"#,
140            )
141            .create();
142        let client = Client::new(server.url(), Credentials::access_token("access-token")).unwrap();
143        let result = client.create_folder(0, "testing").await.unwrap();
144        assert_eq!(result.base.name, "testing");
145        m.assert();
146    }
147
148    #[tokio::test]
149    async fn error() {
150        let mut server = mockito::Server::new_async().await;
151        let m = server
152            .mock("GET", "/createfolder")
153            .match_query(Matcher::AllOf(vec![
154                Matcher::UrlEncoded("access_token".into(), "access-token".into()),
155                Matcher::UrlEncoded("folderid".into(), "0".into()),
156                Matcher::UrlEncoded("name".into(), "testing".into()),
157            ]))
158            .with_status(200)
159            .with_body(r#"{ "result": 1020, "error": "something went wrong" }"#)
160            .create();
161        let client = Client::new(server.url(), Credentials::access_token("access-token")).unwrap();
162        let error = client.create_folder(0, "testing").await.unwrap_err();
163        assert!(matches!(error, crate::Error::Protocol(_, _)));
164        m.assert();
165    }
166}