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(|e| OapiError::UrlError(e))?;
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).finish();
31            }
32
33            println!("Built URL: {}", url);
34            Ok(url.to_string())
35        }
36    }
37
38    impl<'a> DeleteNoStream for DeleteRequest<'a> {
39        type Response = super::response::FileDeleted;
40    }
41}
42
43pub mod response {
44    use std::str::FromStr;
45
46    use serde::Deserialize;
47
48    use crate::errors::OapiError;
49
50    #[derive(Debug, Deserialize, Clone)]
51    pub struct FileDeleted {
52        pub id: String,
53        pub deleted: bool,
54        pub object: FileDeletedObject,
55    }
56
57    #[derive(Debug, Deserialize, Clone)]
58    #[serde(rename_all = "snake_case")]
59    pub enum FileDeletedObject {
60        File,
61    }
62
63    impl FromStr for FileDeleted {
64        type Err = crate::errors::OapiError;
65
66        fn from_str(s: &str) -> Result<Self, Self::Err> {
67            serde_json::from_str(s).map_err(|e| OapiError::DeserializationError(e.to_string()))
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use std::sync::LazyLock;
75
76    use anyhow::{Context, anyhow};
77
78    use crate::{
79        files::{delete::request::DeleteRequest, list::request::ListFilesRequest},
80        rest::{delete::DeleteNoStream, get::GetNoStream},
81    };
82
83    const MODELSTUDIO_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";
84    const MODELSTUDIO_KEY: LazyLock<&str> =
85        LazyLock::new(|| include_str!("../../keys/modelstudio_domestic_key").trim());
86
87    #[tokio::test]
88    async fn test_modelstudio_delete_files() -> anyhow::Result<()> {
89        let list_request = ListFilesRequest::default();
90        let list_response = list_request
91            .get_response(MODELSTUDIO_BASE_URL, &MODELSTUDIO_KEY)
92            .await
93            .with_context(|| anyhow!("Failed to list files."))?;
94
95        let files = list_response
96            .data
97            .iter()
98            .filter_map(|file_object| {
99                let name = file_object.filename.as_str();
100                let file_id = file_object.id.as_str();
101                if name.starts_with("test") {
102                    Some(file_id)
103                } else {
104                    None
105                }
106            })
107            .collect::<Vec<&str>>();
108
109        let futures = files
110            .iter()
111            .map(|file| async move {
112                let delete_request = DeleteRequest {
113                    file_id: file,
114                    ..Default::default()
115                };
116                delete_request
117                    .get_response(MODELSTUDIO_BASE_URL, &MODELSTUDIO_KEY)
118                    .await
119                    .with_context(|| anyhow!("Failed to delete file {}", file))
120            })
121            .collect::<Vec<_>>();
122
123        let results = futures_util::future::join_all(futures).await;
124
125        for result in results {
126            result?;
127        }
128
129        Ok(())
130    }
131}