openai_interface/files/
delete.rs

1//! Delete a file by file ID.
2
3pub mod request {
4    use url::Url;
5
6    use crate::{
7        errors::OapiError,
8        rest::delete::{Delete, DeleteNoStream},
9    };
10
11    #[derive(Debug, Default)]
12    pub struct DeleteRequest<'a> {
13        pub file_id: &'a str,
14        /// This parameter makes no difference yet.
15        pub extra_headers: serde_json::Map<String, serde_json::Value>,
16    }
17
18    impl Delete for DeleteRequest<'_> {
19        /// base_url should look like https://api.openai.com/v1/ (must ends with '/')
20        fn build_url(&self, base_url: &str) -> Result<String, crate::errors::OapiError> {
21            let url = Url::parse(base_url)
22                .map_err(|e| OapiError::UrlError(e))?
23                .join("files/")
24                .unwrap()
25                .join(self.file_id)
26                .map_err(|e| OapiError::UrlError(e))?;
27
28            println!("Built URL: {}", url);
29            Ok(url.to_string())
30        }
31    }
32
33    impl<'a> DeleteNoStream for DeleteRequest<'a> {
34        type Response = super::response::FileDeleted;
35    }
36}
37
38mod response {
39    use std::str::FromStr;
40
41    use serde::Deserialize;
42
43    use crate::errors::OapiError;
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    impl FromStr for FileDeleted {
59        type Err = crate::errors::OapiError;
60
61        fn from_str(s: &str) -> Result<Self, Self::Err> {
62            serde_json::from_str(s).map_err(|e| OapiError::DeserializationError(e.to_string()))
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use std::sync::LazyLock;
70
71    use anyhow::{Context, anyhow};
72
73    use crate::{
74        files::{delete::request::DeleteRequest, list::request::ListFilesRequest},
75        rest::{delete::DeleteNoStream, get::GetNoStream},
76    };
77
78    const MODELSTUDIO_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";
79    const MODELSTUDIO_KEY: LazyLock<&str> =
80        LazyLock::new(|| include_str!("../../keys/modelstudio_domestic_key").trim());
81
82    #[tokio::test]
83    async fn test_modelstudio_delete_files() -> anyhow::Result<()> {
84        let list_request = ListFilesRequest::default();
85        let list_response = list_request
86            .get_response(MODELSTUDIO_BASE_URL, &MODELSTUDIO_KEY)
87            .await
88            .with_context(|| anyhow!("Failed to list files."))?;
89
90        let files = list_response
91            .data
92            .iter()
93            .filter_map(|file_object| {
94                let name = file_object.filename.as_str();
95                let file_id = file_object.id.as_str();
96                if name.starts_with("test") {
97                    Some(file_id)
98                } else {
99                    None
100                }
101            })
102            .collect::<Vec<&str>>();
103
104        let futures = files
105            .iter()
106            .map(|file| async move {
107                let delete_request = DeleteRequest {
108                    file_id: file,
109                    ..Default::default()
110                };
111                delete_request
112                    .get_response(MODELSTUDIO_BASE_URL, &MODELSTUDIO_KEY)
113                    .await
114                    .with_context(|| anyhow!("Failed to delete file {}", file))
115            })
116            .collect::<Vec<_>>();
117
118        let results = futures_util::future::join_all(futures).await;
119
120        for result in results {
121            result?;
122        }
123
124        Ok(())
125    }
126}