1use bytes::Bytes;
2use serde::Serialize;
3
4use crate::{
5 config::Config,
6 error::OpenAIError,
7 types::files::{CreateFileRequest, DeleteFileResponse, ListFilesResponse, OpenAIFile},
8 Client,
9};
10
11pub struct Files<'c, C: Config> {
13 client: &'c Client<C>,
14}
15
16impl<'c, C: Config> Files<'c, C> {
17 pub fn new(client: &'c Client<C>) -> Self {
18 Self { client }
19 }
20
21 #[crate::byot(
31 T0 = Clone,
32 R = serde::de::DeserializeOwned,
33 where_clause = "reqwest::multipart::Form: crate::traits::AsyncTryFrom<T0, Error = OpenAIError>",
34 )]
35 pub async fn create(&self, request: CreateFileRequest) -> Result<OpenAIFile, OpenAIError> {
36 self.client.post_form("/files", request).await
37 }
38
39 #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)]
41 pub async fn list<Q>(&self, query: &Q) -> Result<ListFilesResponse, OpenAIError>
42 where
43 Q: Serialize + ?Sized,
44 {
45 self.client.get_with_query("/files", &query).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.get(format!("/files/{file_id}").as_str()).await
52 }
53
54 #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
56 pub async fn delete(&self, file_id: &str) -> Result<DeleteFileResponse, OpenAIError> {
57 self.client
58 .delete(format!("/files/{file_id}").as_str())
59 .await
60 }
61
62 pub async fn content(&self, file_id: &str) -> Result<Bytes, OpenAIError> {
64 let (bytes, _headers) = self
65 .client
66 .get_raw(format!("/files/{file_id}/content").as_str())
67 .await?;
68 Ok(bytes)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::{
75 types::files::{
76 CreateFileRequestArgs, FileExpirationAfter, FileExpirationAfterAnchor, FilePurpose,
77 },
78 Client,
79 };
80
81 #[tokio::test]
82 async fn test_file_mod() {
83 let test_file_path = "/tmp/test.jsonl";
84 let contents = concat!(
85 "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n", "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}"
87 );
88
89 tokio::fs::write(test_file_path, contents).await.unwrap();
90
91 let client = Client::new();
92
93 let request = CreateFileRequestArgs::default()
94 .file(test_file_path)
95 .purpose(FilePurpose::FineTune)
96 .expires_after(FileExpirationAfter {
97 anchor: FileExpirationAfterAnchor::CreatedAt,
98 seconds: 3600,
99 })
100 .build()
101 .unwrap();
102
103 let openai_file = client.files().create(request).await.unwrap();
104
105 assert_eq!(openai_file.bytes, 135);
106 assert_eq!(openai_file.filename, "test.jsonl");
107 let query = [("purpose", "fine-tune")];
111
112 let list_files = client.files().list(&query).await.unwrap();
113
114 assert_eq!(list_files.data.into_iter().last().unwrap(), openai_file);
115
116 let retrieved_file = client.files().retrieve(&openai_file.id).await.unwrap();
117
118 assert_eq!(openai_file.created_at, retrieved_file.created_at);
119 assert_eq!(openai_file.bytes, retrieved_file.bytes);
120 assert_eq!(openai_file.filename, retrieved_file.filename);
121 assert_eq!(openai_file.purpose, retrieved_file.purpose);
122 assert_eq!(openai_file.expires_at, retrieved_file.expires_at);
123
124 tokio::time::sleep(std::time::Duration::from_secs(15)).await;
135 let delete_response = client.files().delete(&openai_file.id).await.unwrap();
136
137 assert_eq!(openai_file.id, delete_response.id);
138 assert!(delete_response.deleted);
139 }
140}