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