1use bytes::Bytes;
2
3use crate::config::Config;
4use crate::error::OpenAIError;
5use crate::spec::files::{CreateFileRequest, DeleteFileResponse, ListFilesResponse, OpenAIFile};
6use crate::{Client, RequestOptions};
7
8pub struct Files<'c, C: Config> {
11 client: &'c Client<C>,
12 pub(crate) request_options: RequestOptions,
13}
14
15impl<'c, C: Config> Files<'c, C> {
16 pub fn new(client: &'c Client<C>) -> Self {
17 Self {
18 client,
19 request_options: RequestOptions::new(),
20 }
21 }
22
23 #[crate::byot(
34 T0 = Clone,
35 R = serde::de::DeserializeOwned,
36 where_clause = "reqwest::multipart::Form: crate::traits::AsyncTryFrom<T0, Error = OpenAIError>",
37 )]
38 pub async fn create(&self, request: CreateFileRequest) -> Result<OpenAIFile, OpenAIError> {
39 self.client
40 .post_form("/files", request, &self.request_options)
41 .await
42 }
43
44 #[crate::byot(R = serde::de::DeserializeOwned)]
46 pub async fn list(&self) -> Result<ListFilesResponse, OpenAIError> {
47 self.client.get("/files", &self.request_options).await
48 }
49
50 #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
52 pub async fn retrieve(&self, file_id: &str) -> Result<OpenAIFile, OpenAIError> {
53 self.client
54 .get(format!("/files/{file_id}").as_str(), &self.request_options)
55 .await
56 }
57
58 #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
60 pub async fn delete(&self, file_id: &str) -> Result<DeleteFileResponse, OpenAIError> {
61 self.client
62 .delete(format!("/files/{file_id}").as_str(), &self.request_options)
63 .await
64 }
65
66 pub async fn content(&self, file_id: &str) -> Result<Bytes, OpenAIError> {
68 let (bytes, _headers) = self
69 .client
70 .get_raw(
71 format!("/files/{file_id}/content").as_str(),
72 &self.request_options,
73 )
74 .await?;
75 Ok(bytes)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use crate::Client;
82 use crate::spec::files::{
83 CreateFileRequestArgs, FileExpirationAfter, FileExpirationAfterAnchor, FilePurpose,
84 };
85 use crate::traits::RequestOptionsBuilder;
86
87 #[tokio::test]
88 async fn test_file_mod() {
89 let test_file_path = "/tmp/test.jsonl";
90 let contents = concat!(
91 "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n", "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}"
93 );
94
95 tokio::fs::write(test_file_path, contents).await.unwrap();
96
97 let client = Client::new();
98
99 let request = CreateFileRequestArgs::default()
100 .file(test_file_path)
101 .purpose(FilePurpose::FineTune)
102 .expires_after(FileExpirationAfter {
103 anchor: FileExpirationAfterAnchor::CreatedAt,
104 seconds: 3600,
105 })
106 .build()
107 .unwrap();
108
109 let openai_file = client.files().create(request).await.unwrap();
110
111 assert_eq!(openai_file.bytes, 135);
112 assert_eq!(openai_file.filename, "test.jsonl");
113 let query = [("purpose", "fine-tune")];
117
118 let list_files = client.files().query(&query).unwrap().list().await.unwrap();
119
120 assert_eq!(list_files.data.into_iter().last().unwrap(), openai_file);
121
122 let retrieved_file = client.files().retrieve(&openai_file.id).await.unwrap();
123
124 assert_eq!(openai_file.created_at, retrieved_file.created_at);
125 assert_eq!(openai_file.bytes, retrieved_file.bytes);
126 assert_eq!(openai_file.filename, retrieved_file.filename);
127 assert_eq!(openai_file.purpose, retrieved_file.purpose);
128 assert_eq!(openai_file.expires_at, retrieved_file.expires_at);
129
130 tokio::time::sleep(std::time::Duration::from_secs(15)).await;
139 let delete_response = client.files().delete(&openai_file.id).await.unwrap();
140
141 assert_eq!(openai_file.id, delete_response.id);
142 assert!(delete_response.deleted);
143 }
144
145 #[tokio::test]
148 async fn test_empty_file_list() {
149 let client = Client::new();
150 let result = client.files().list().await.unwrap();
151 assert!(result.data.is_empty());
152 }
153}