openai_interface/files/
retrieve.rs

1//! This module provides functionality for retrieving files from the OpenAI API.
2//!
3//! It includes the `RetrieveRequest` struct which implements the `Get` and `GetNoStream` traits
4//! to build URLs and fetch file data asynchronously.
5//!
6//! # Example
7//!
8//! ```rust
9//! use std::sync::LazyLock;
10//!
11//! use openai_interface::files::retrieve::*;
12//! use openai_interface::{
13//!     files::{list::request::ListFilesRequest, retrieve::request::RetrieveRequest},
14//!     rest::get::{Get, GetNoStream},
15//! };
16//! use anyhow::bail;
17//! use futures_util::future::{self};
18//!
19//! const MODELSCOPE_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";
20//! const MODELSCOPE_KEY: LazyLock<&str> =
21//!     LazyLock::new(|| include_str!("../../keys/modelstudio_domestic_key").trim());
22//!
23//! #[tokio::main]
24//! async fn main() -> Result<(), anyhow::Error> {
25//!     // first get all files
26//!     let list_request = ListFilesRequest {
27//!         limit: Some(5), // avoid rate limit
28//!         ..Default::default()
29//!     };
30//!
31//!     let list_response = list_request
32//!         .get_response(MODELSCOPE_BASE_URL, &MODELSCOPE_KEY)
33//!         .await?;
34//!
35//!     let futures: Vec<_> = list_response
36//!         .data
37//!         .iter()
38//!         .map(|file_object| {
39//!             let file_id = file_object.id.clone();
40//!             let base_url = MODELSCOPE_BASE_URL.to_string();
41//!             let key = MODELSCOPE_KEY.to_string();
42//!             async move {
43//!                 let retrieve_request = RetrieveRequest { file_id: &file_id };
44//!                 retrieve_request.get_response(&base_url, &key).await
45//!             }
46//!         })
47//!         .collect();
48//!
49//!     let results = future::join_all(futures).await;
50//!
51//!     for (i, result) in results.iter().enumerate() {
52//!         match result {
53//!             Ok(file_object) => {
54//!                 assert_eq!(&list_response.data[i].id, &file_object.id);
55//!                 assert_eq!(&list_response.data[i].filename, &file_object.filename);
56//!                 assert_eq!(&list_response.data[i].purpose, &file_object.purpose);
57//!                 // assert_eq!(&list_response.data[i], file_object);
58//!             }
59//!             Err(e) => {
60//!                 bail!(
61//!                     "Failed to get response: {e:#}. The file is: index {i}, {:?}",
62//!                     list_response.data[i]
63//!                 )
64//!             }
65//!         }
66//!     }
67//!
68//!     Ok(())
69//! }
70//! ```
71
72pub mod request {
73    use url::Url;
74
75    use crate::{
76        errors::OapiError,
77        rest::get::{Get, GetNoStream},
78    };
79
80    pub struct RetrieveRequest<'a> {
81        pub file_id: &'a str,
82    }
83
84    impl<'a> Get for RetrieveRequest<'a> {
85        /// base_url should look like <https://api.openai.com/v1/> (must ends with '/')
86        fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
87            let url = Url::parse(base_url)
88                .map_err(|e| OapiError::UrlError(e))?
89                .join("files/")
90                .unwrap()
91                .join(self.file_id)
92                .map_err(|e| OapiError::UrlError(e))?;
93
94            Ok(url.to_string())
95        }
96    }
97
98    impl<'a> GetNoStream for RetrieveRequest<'a> {
99        type Response = crate::files::FileObject;
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use std::sync::LazyLock;
106
107    use super::*;
108    use crate::{
109        files::{list::request::ListFilesRequest, retrieve::request::RetrieveRequest},
110        rest::get::{Get, GetNoStream},
111    };
112    use anyhow::bail;
113    use futures_util::future::{self};
114
115    const MODELSCOPE_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1/";
116    const MODELSCOPE_KEY: LazyLock<&str> =
117        LazyLock::new(|| include_str!("../../keys/modelstudio_domestic_key").trim());
118
119    #[test]
120    fn test_build_url() {
121        let request = request::RetrieveRequest { file_id: "file_id" };
122        let url = request.build_url("https://api.openai.com/v1/").unwrap();
123        assert_eq!(url, "https://api.openai.com/v1/files/file_id");
124    }
125
126    #[tokio::test]
127    async fn test_retrieve_file() -> Result<(), anyhow::Error> {
128        // first get all files
129        let list_request = ListFilesRequest {
130            limit: Some(5), // avoid rate limit
131            ..Default::default()
132        };
133
134        let list_response = list_request
135            .get_response(MODELSCOPE_BASE_URL, &MODELSCOPE_KEY)
136            .await?;
137
138        let futures: Vec<_> = list_response
139            .data
140            .iter()
141            .map(|file_object| {
142                let file_id = file_object.id.clone();
143                let base_url = MODELSCOPE_BASE_URL.to_string();
144                let key = MODELSCOPE_KEY.to_string();
145                async move {
146                    let retrieve_request = RetrieveRequest { file_id: &file_id };
147                    retrieve_request.get_response(&base_url, &key).await
148                }
149            })
150            .collect();
151
152        let results = future::join_all(futures).await;
153
154        for (i, result) in results.iter().enumerate() {
155            match result {
156                Ok(file_object) => {
157                    assert_eq!(&list_response.data[i].id, &file_object.id);
158                    assert_eq!(&list_response.data[i].filename, &file_object.filename);
159                    assert_eq!(&list_response.data[i].purpose, &file_object.purpose);
160                    // assert_eq!(&list_response.data[i], file_object);
161                }
162                Err(e) => {
163                    bail!(
164                        "Failed to get response: {e:#}. The file is: index {i}, {:?}",
165                        list_response.data[i]
166                    )
167                }
168            }
169        }
170
171        Ok(())
172    }
173}