Skip to main content

openai_interface/files/
delete.rs

1//! Delete a file by file ID.
2
3pub mod request {
4    use std::collections::HashMap;
5
6    use url::Url;
7
8    use crate::{
9        errors::OapiError,
10        rest::delete::{Delete, DeleteNoStream},
11    };
12
13    #[derive(Debug, Default)]
14    pub struct DeleteRequest<'a> {
15        pub file_id: &'a str,
16        pub extra_query: HashMap<&'a str, &'a str>,
17    }
18
19    impl Delete for DeleteRequest<'_> {
20        /// base_url should look like <https://api.openai.com/v1>
21        fn build_url(&self, base_url: &str) -> Result<String, crate::errors::OapiError> {
22            let mut url =
23                Url::parse(base_url.trim_end_matches('/')).map_err(OapiError::UrlError)?;
24            url.path_segments_mut()
25                .map_err(|_| OapiError::UrlCannotBeBase(base_url.to_string()))?
26                .push("files")
27                .push(self.file_id);
28
29            for (key, value) in &self.extra_query {
30                url.query_pairs_mut().append_pair(key, value);
31            }
32
33            Ok(url.to_string())
34        }
35    }
36
37    impl<'a> DeleteNoStream for DeleteRequest<'a> {
38        type Response = super::response::FileDeleted;
39    }
40}
41
42pub mod response {
43    use serde::Deserialize;
44
45    #[derive(Debug, Deserialize, Clone)]
46    pub struct FileDeleted {
47        pub id: String,
48        pub deleted: bool,
49        pub object: FileDeletedObject,
50    }
51
52    #[derive(Debug, Deserialize, Clone)]
53    #[serde(rename_all = "snake_case")]
54    pub enum FileDeletedObject {
55        File,
56    }
57
58    crate::impl_from_str!(FileDeleted);
59}
60
61#[cfg(test)]
62mod tests {
63    use anyhow::{Context, anyhow};
64
65    use crate::{
66        files::{delete::request::DeleteRequest, list::request::ListFilesRequest},
67        rest::{RequestOptions, default_client, delete::DeleteNoStream, get::GetNoStream},
68    };
69
70    const MODELSTUDIO_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";
71
72    fn modelstudio_key() -> Option<String> {
73        std::env::var("QWEN_API_KEY")
74            .ok()
75            .map(|key| key.trim().to_string())
76            .filter(|key| !key.is_empty())
77    }
78
79    #[tokio::test]
80    async fn test_modelstudio_delete_files() -> anyhow::Result<()> {
81        let Some(api_key) = modelstudio_key() else {
82            println!("Skipping: set QWEN_API_KEY to run this test");
83            return Ok(());
84        };
85
86        let client = default_client();
87
88        let list_request = ListFilesRequest::default();
89        let list_response = list_request
90            .get_response(
91                &client,
92                MODELSTUDIO_BASE_URL,
93                &RequestOptions::bearer(&api_key),
94            )
95            .await
96            .with_context(|| anyhow!("Failed to list files."))?;
97
98        let files = list_response
99            .data
100            .iter()
101            .filter_map(|file_object| {
102                let name = file_object.filename.as_str();
103                let file_id = file_object.id.as_str();
104                if name.starts_with("test") {
105                    Some(file_id)
106                } else {
107                    None
108                }
109            })
110            .collect::<Vec<&str>>();
111
112        let futures = files
113            .iter()
114            .map(|file| {
115                let client = client.clone();
116                let api_key = api_key.clone();
117                async move {
118                    let delete_request = DeleteRequest {
119                        file_id: file,
120                        ..Default::default()
121                    };
122                    delete_request
123                        .get_response(
124                            &client,
125                            MODELSTUDIO_BASE_URL,
126                            &RequestOptions::bearer(&api_key),
127                        )
128                        .await
129                        .with_context(|| anyhow!("Failed to delete file {}", file))
130                }
131            })
132            .collect::<Vec<_>>();
133
134        let results = futures_util::future::join_all(futures).await;
135
136        for result in results {
137            result?;
138        }
139
140        Ok(())
141    }
142}