openai-interface 0.14.0

A low-level Rust interface for the OpenAI API
Documentation
//! Delete a file by file ID.

pub mod request {
    use std::collections::HashMap;

    use url::Url;

    use crate::{
        errors::OapiError,
        rest::delete::{Delete, DeleteNoStream},
    };

    #[derive(Debug, Default)]
    pub struct DeleteRequest<'a> {
        pub file_id: &'a str,
        pub extra_query: HashMap<&'a str, &'a str>,
    }

    impl Delete for DeleteRequest<'_> {
        /// base_url should look like <https://api.openai.com/v1>
        fn build_url(&self, base_url: &str) -> Result<String, crate::errors::OapiError> {
            let mut url =
                Url::parse(base_url.trim_end_matches('/')).map_err(OapiError::UrlError)?;
            url.path_segments_mut()
                .map_err(|_| OapiError::UrlCannotBeBase(base_url.to_string()))?
                .push("files")
                .push(self.file_id);

            for (key, value) in &self.extra_query {
                url.query_pairs_mut().append_pair(key, value);
            }

            Ok(url.to_string())
        }
    }

    impl<'a> DeleteNoStream for DeleteRequest<'a> {
        type Response = super::response::FileDeleted;
    }
}

pub mod response {
    use serde::{Deserialize, Serialize};

    #[derive(Debug, Deserialize, Serialize, Clone)]
    pub struct FileDeleted {
        pub id: String,
        pub deleted: bool,
        pub object: FileDeletedObject,
    }

    #[derive(Debug, Deserialize, Serialize, Clone)]
    #[serde(rename_all = "snake_case")]
    pub enum FileDeletedObject {
        File,
    }

    crate::impl_from_str!(FileDeleted);
}

#[cfg(test)]
mod tests {
    use anyhow::{Context, anyhow};

    use crate::{
        files::{delete::request::DeleteRequest, list::request::ListFilesRequest},
        rest::{RequestOptions, default_client, delete::DeleteNoStream, get::GetNoStream},
    };

    const MODELSTUDIO_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";

    fn modelstudio_key() -> Option<String> {
        std::env::var("QWEN_API_KEY")
            .ok()
            .map(|key| key.trim().to_string())
            .filter(|key| !key.is_empty())
    }

    #[tokio::test]
    async fn test_modelstudio_delete_files() -> anyhow::Result<()> {
        let Some(api_key) = modelstudio_key() else {
            println!("Skipping: set QWEN_API_KEY to run this test");
            return Ok(());
        };

        let client = default_client();

        let list_request = ListFilesRequest::default();
        let list_response = list_request
            .get_response(
                &client,
                MODELSTUDIO_BASE_URL,
                &RequestOptions::bearer(&api_key),
            )
            .await
            .with_context(|| anyhow!("Failed to list files."))?;

        let files = list_response
            .data
            .iter()
            .filter_map(|file_object| {
                let name = file_object.filename.as_str();
                let file_id = file_object.id.as_str();
                if name.starts_with("test") {
                    Some(file_id)
                } else {
                    None
                }
            })
            .collect::<Vec<&str>>();

        let futures = files
            .iter()
            .map(|file| {
                let client = client.clone();
                let api_key = api_key.clone();
                async move {
                    let delete_request = DeleteRequest {
                        file_id: file,
                        ..Default::default()
                    };
                    delete_request
                        .get_response(
                            &client,
                            MODELSTUDIO_BASE_URL,
                            &RequestOptions::bearer(&api_key),
                        )
                        .await
                        .with_context(|| anyhow!("Failed to delete file {}", file))
                }
            })
            .collect::<Vec<_>>();

        let results = futures_util::future::join_all(futures).await;

        for result in results {
            result?;
        }

        Ok(())
    }
}