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(
32 T0 = Clone,
33 R = serde::de::DeserializeOwned,
34 where_clause = "reqwest::multipart::Form: crate::traits::AsyncTryFrom<T0, Error = OpenAIError>, T0: crate::traits::MaybeSend + 'static",
35 )]
36 pub async fn create(&self, request: CreateFileRequest) -> Result<OpenAIFile, OpenAIError> {
37 self.client
38 .post_form("/files", request, &self.request_options)
39 .await
40 }
41
42 #[crate::byot(R = serde::de::DeserializeOwned)]
44 pub async fn list(&self) -> Result<ListFilesResponse, OpenAIError> {
45 self.client.get("/files", &self.request_options).await
46 }
47
48 #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
50 pub async fn retrieve(&self, file_id: &str) -> Result<OpenAIFile, OpenAIError> {
51 self.client
52 .get(format!("/files/{file_id}").as_str(), &self.request_options)
53 .await
54 }
55
56 #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
58 pub async fn delete(&self, file_id: &str) -> Result<DeleteFileResponse, OpenAIError> {
59 self.client
60 .delete(format!("/files/{file_id}").as_str(), &self.request_options)
61 .await
62 }
63
64 pub async fn content(&self, file_id: &str) -> Result<Bytes, OpenAIError> {
66 let (bytes, _headers) = self
67 .client
68 .get_raw(
69 format!("/files/{file_id}/content").as_str(),
70 &self.request_options,
71 )
72 .await?;
73 Ok(bytes)
74 }
75}
76
77#[cfg(all(test, feature = "file"))]
78mod tests {
79 use crate::{
80 traits::RequestOptionsBuilder,
81 types::files::{
82 CreateFileRequestArgs, FileExpirationAfter, FileExpirationAfterAnchor, FilePurpose,
83 },
84 Client,
85 };
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;
141 let delete_response = client.files().delete(&openai_file.id).await.unwrap();
142
143 assert_eq!(openai_file.id, delete_response.id);
144 assert!(delete_response.deleted);
145 }
146
147 #[tokio::test]
150 async fn test_empty_file_list() {
151 let client = Client::new();
152 let result = client.files().list().await.unwrap();
153 assert!(result.data.is_empty());
154 }
155}