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}