openai_api_rs/v1/
api.rs

1use crate::v1::assistant::{
2    AssistantFileObject, AssistantFileRequest, AssistantObject, AssistantRequest, DeletionStatus,
3    ListAssistant, ListAssistantFile,
4};
5use crate::v1::audio::{
6    AudioSpeechRequest, AudioSpeechResponse, AudioTranscriptionRequest, AudioTranscriptionResponse,
7    AudioTranslationRequest, AudioTranslationResponse,
8};
9use crate::v1::batch::{BatchResponse, CreateBatchRequest, ListBatchResponse};
10use crate::v1::chat_completion::{ChatCompletionRequest, ChatCompletionResponse};
11use crate::v1::common;
12use crate::v1::completion::{CompletionRequest, CompletionResponse};
13use crate::v1::edit::{EditRequest, EditResponse};
14use crate::v1::embedding::{EmbeddingRequest, EmbeddingResponse};
15use crate::v1::error::APIError;
16use crate::v1::file::{
17    FileDeleteRequest, FileDeleteResponse, FileListResponse, FileRetrieveResponse,
18    FileUploadRequest, FileUploadResponse,
19};
20use crate::v1::fine_tuning::{
21    CancelFineTuningJobRequest, CreateFineTuningJobRequest, FineTuningJobEvent,
22    FineTuningJobObject, FineTuningPagination, ListFineTuningJobEventsRequest,
23    RetrieveFineTuningJobRequest,
24};
25use crate::v1::image::{
26    ImageEditRequest, ImageEditResponse, ImageGenerationRequest, ImageGenerationResponse,
27    ImageVariationRequest, ImageVariationResponse,
28};
29use crate::v1::message::{
30    CreateMessageRequest, ListMessage, ListMessageFile, MessageFileObject, MessageObject,
31    ModifyMessageRequest,
32};
33use crate::v1::moderation::{CreateModerationRequest, CreateModerationResponse};
34use crate::v1::run::{
35    CreateRunRequest, CreateThreadAndRunRequest, ListRun, ListRunStep, ModifyRunRequest, RunObject,
36    RunStepObject,
37};
38use crate::v1::thread::{CreateThreadRequest, ModifyThreadRequest, ThreadObject};
39
40use bytes::Bytes;
41use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
42use reqwest::multipart::{Form, Part};
43use reqwest::{Client, Method, Response};
44use serde::Serialize;
45use serde_json::Value;
46
47use std::error::Error;
48use std::fs::{create_dir_all, File};
49use std::io::Read;
50use std::io::Write;
51use std::path::Path;
52
53const API_URL_V1: &str = "https://api.openai.com/v1";
54
55#[derive(Default)]
56pub struct OpenAIClientBuilder {
57    api_endpoint: Option<String>,
58    api_key: Option<String>,
59    organization: Option<String>,
60    proxy: Option<String>,
61    timeout: Option<u64>,
62    headers: Option<HeaderMap>,
63}
64
65pub struct OpenAIClient {
66    api_endpoint: String,
67    api_key: String,
68    organization: Option<String>,
69    proxy: Option<String>,
70    timeout: Option<u64>,
71    pub headers: Option<HeaderMap>,
72}
73
74impl OpenAIClientBuilder {
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
80        self.api_key = Some(api_key.into());
81        self
82    }
83
84    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
85        self.api_endpoint = Some(endpoint.into());
86        self
87    }
88
89    pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
90        self.organization = Some(organization.into());
91        self
92    }
93
94    pub fn with_proxy(mut self, proxy: impl Into<String>) -> Self {
95        self.proxy = Some(proxy.into());
96        self
97    }
98
99    pub fn with_timeout(mut self, timeout: u64) -> Self {
100        self.timeout = Some(timeout);
101        self
102    }
103
104    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
105        let headers = self.headers.get_or_insert_with(HeaderMap::new);
106        headers.insert(
107            HeaderName::from_bytes(key.into().as_bytes()).expect("Invalid header name"),
108            HeaderValue::from_str(&value.into()).expect("Invalid header value"),
109        );
110        self
111    }
112
113    pub fn build(self) -> Result<OpenAIClient, Box<dyn Error>> {
114        let api_key = self.api_key.ok_or("API key is required")?;
115        let api_endpoint = self.api_endpoint.unwrap_or_else(|| {
116            std::env::var("OPENAI_API_BASE").unwrap_or_else(|_| API_URL_V1.to_owned())
117        });
118
119        Ok(OpenAIClient {
120            api_endpoint,
121            api_key,
122            organization: self.organization,
123            proxy: self.proxy,
124            timeout: self.timeout,
125            headers: self.headers,
126        })
127    }
128}
129
130impl OpenAIClient {
131    pub fn builder() -> OpenAIClientBuilder {
132        OpenAIClientBuilder::new()
133    }
134
135    async fn build_request(&self, method: Method, path: &str) -> reqwest::RequestBuilder {
136        let url = format!("{}/{}", self.api_endpoint, path);
137        let client = Client::builder();
138
139        #[cfg(feature = "rustls")]
140        let client = client.use_rustls_tls();
141
142        let client = if let Some(timeout) = self.timeout {
143            client.timeout(std::time::Duration::from_secs(timeout))
144        } else {
145            client
146        };
147
148        let client = if let Some(proxy) = &self.proxy {
149            client.proxy(reqwest::Proxy::all(proxy).unwrap())
150        } else {
151            client
152        };
153
154        let client = client.build().unwrap();
155
156        let mut request = client
157            .request(method, url)
158            .header("Authorization", format!("Bearer {}", self.api_key));
159
160        if let Some(organization) = &self.organization {
161            request = request.header("openai-organization", organization);
162        }
163
164        if let Some(headers) = &self.headers {
165            for (key, value) in headers {
166                request = request.header(key, value);
167            }
168        }
169
170        if Self::is_beta(path) {
171            request = request.header("OpenAI-Beta", "assistants=v2");
172        }
173
174        request
175    }
176
177    async fn post<T: serde::de::DeserializeOwned>(
178        &mut self,
179        path: &str,
180        body: &impl serde::ser::Serialize,
181    ) -> Result<T, APIError> {
182        let request = self.build_request(Method::POST, path).await;
183        let request = request.json(body);
184        let response = request.send().await?;
185        self.handle_response(response).await
186    }
187
188    async fn get<T: serde::de::DeserializeOwned>(&mut self, path: &str) -> Result<T, APIError> {
189        let request = self.build_request(Method::GET, path).await;
190        let response = request.send().await?;
191        self.handle_response(response).await
192    }
193
194    async fn get_raw(&self, path: &str) -> Result<Bytes, APIError> {
195        let request = self.build_request(Method::GET, path).await;
196        let response = request.send().await?;
197        Ok(response.bytes().await?)
198    }
199
200    async fn delete<T: serde::de::DeserializeOwned>(&mut self, path: &str) -> Result<T, APIError> {
201        let request = self.build_request(Method::DELETE, path).await;
202        let response = request.send().await?;
203        self.handle_response(response).await
204    }
205
206    async fn post_form<T: serde::de::DeserializeOwned>(
207        &mut self,
208        path: &str,
209        form: Form,
210    ) -> Result<T, APIError> {
211        let request = self.build_request(Method::POST, path).await;
212        let request = request.multipart(form);
213        let response = request.send().await?;
214        self.handle_response(response).await
215    }
216
217    async fn post_form_raw(&self, path: &str, form: Form) -> Result<Bytes, APIError> {
218        let request = self.build_request(Method::POST, path).await;
219        let request = request.multipart(form);
220        let response = request.send().await?;
221        Ok(response.bytes().await?)
222    }
223
224    async fn handle_response<T: serde::de::DeserializeOwned>(
225        &mut self,
226        response: Response,
227    ) -> Result<T, APIError> {
228        let status = response.status();
229        let headers = response.headers().clone();
230        if status.is_success() {
231            let text = response.text().await.unwrap_or_else(|_| "".to_string());
232            match serde_json::from_str::<T>(&text) {
233                Ok(parsed) => {
234                    self.headers = Some(headers);
235                    Ok(parsed)
236                }
237                Err(e) => Err(APIError::CustomError {
238                    message: format!("Failed to parse JSON: {} / response {}", e, text),
239                }),
240            }
241        } else {
242            let error_message = response
243                .text()
244                .await
245                .unwrap_or_else(|_| "Unknown error".to_string());
246            Err(APIError::CustomError {
247                message: format!("{}: {}", status, error_message),
248            })
249        }
250    }
251
252    pub async fn completion(
253        &mut self,
254        req: CompletionRequest,
255    ) -> Result<CompletionResponse, APIError> {
256        self.post("completions", &req).await
257    }
258
259    pub async fn edit(&mut self, req: EditRequest) -> Result<EditResponse, APIError> {
260        self.post("edits", &req).await
261    }
262
263    pub async fn image_generation(
264        &mut self,
265        req: ImageGenerationRequest,
266    ) -> Result<ImageGenerationResponse, APIError> {
267        self.post("images/generations", &req).await
268    }
269
270    pub async fn image_edit(
271        &mut self,
272        req: ImageEditRequest,
273    ) -> Result<ImageEditResponse, APIError> {
274        self.post("images/edits", &req).await
275    }
276
277    pub async fn image_variation(
278        &mut self,
279        req: ImageVariationRequest,
280    ) -> Result<ImageVariationResponse, APIError> {
281        self.post("images/variations", &req).await
282    }
283
284    pub async fn embedding(
285        &mut self,
286        req: EmbeddingRequest,
287    ) -> Result<EmbeddingResponse, APIError> {
288        self.post("embeddings", &req).await
289    }
290
291    pub async fn file_list(&mut self) -> Result<FileListResponse, APIError> {
292        self.get("files").await
293    }
294
295    pub async fn upload_file(
296        &mut self,
297        req: FileUploadRequest,
298    ) -> Result<FileUploadResponse, APIError> {
299        let form = Self::create_form(&req, "file")?;
300        self.post_form("files", form).await
301    }
302
303    pub async fn delete_file(
304        &mut self,
305        req: FileDeleteRequest,
306    ) -> Result<FileDeleteResponse, APIError> {
307        self.delete(&format!("files/{}", req.file_id)).await
308    }
309
310    pub async fn retrieve_file(
311        &mut self,
312        file_id: String,
313    ) -> Result<FileRetrieveResponse, APIError> {
314        self.get(&format!("files/{}", file_id)).await
315    }
316
317    pub async fn retrieve_file_content(&self, file_id: String) -> Result<Bytes, APIError> {
318        self.get_raw(&format!("files/{}/content", file_id)).await
319    }
320
321    pub async fn chat_completion(
322        &mut self,
323        req: ChatCompletionRequest,
324    ) -> Result<ChatCompletionResponse, APIError> {
325        self.post("chat/completions", &req).await
326    }
327
328    pub async fn audio_transcription(
329        &mut self,
330        req: AudioTranscriptionRequest,
331    ) -> Result<AudioTranscriptionResponse, APIError> {
332        // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format
333        if let Some(response_format) = &req.response_format {
334            if response_format != "json" && response_format != "verbose_json" {
335                return Err(APIError::CustomError {
336                    message: "response_format must be either 'json' or 'verbose_json' please use audio_transcription_raw".to_string(),
337                });
338            }
339        }
340        let form: Form;
341        if req.clone().file.is_some() {
342            form = Self::create_form(&req, "file")?;
343        } else if let Some(bytes) = req.clone().bytes {
344            form = Self::create_form_from_bytes(&req, bytes)?;
345        } else {
346            return Err(APIError::CustomError {
347                message: "Either file or bytes must be provided".to_string(),
348            });
349        }
350        self.post_form("audio/transcriptions", form).await
351    }
352
353    pub async fn audio_transcription_raw(
354        &mut self,
355        req: AudioTranscriptionRequest,
356    ) -> Result<Bytes, APIError> {
357        // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format
358        if let Some(response_format) = &req.response_format {
359            if response_format != "text" && response_format != "srt" && response_format != "vtt" {
360                return Err(APIError::CustomError {
361                    message: "response_format must be either 'text', 'srt' or 'vtt', please use audio_transcription".to_string(),
362                });
363            }
364        }
365        let form: Form;
366        if req.clone().file.is_some() {
367            form = Self::create_form(&req, "file")?;
368        } else if let Some(bytes) = req.clone().bytes {
369            form = Self::create_form_from_bytes(&req, bytes)?;
370        } else {
371            return Err(APIError::CustomError {
372                message: "Either file or bytes must be provided".to_string(),
373            });
374        }
375        self.post_form_raw("audio/transcriptions", form).await
376    }
377
378    pub async fn audio_translation(
379        &mut self,
380        req: AudioTranslationRequest,
381    ) -> Result<AudioTranslationResponse, APIError> {
382        let form = Self::create_form(&req, "file")?;
383        self.post_form("audio/translations", form).await
384    }
385
386    pub async fn audio_speech(
387        &mut self,
388        req: AudioSpeechRequest,
389    ) -> Result<AudioSpeechResponse, APIError> {
390        let request = self.build_request(Method::POST, "audio/speech").await;
391        let request = request.json(&req);
392        let response = request.send().await?;
393        let headers = response.headers().clone();
394        let bytes = response.bytes().await?;
395        let path = Path::new(req.output.as_str());
396        if let Some(parent) = path.parent() {
397            match create_dir_all(parent) {
398                Ok(_) => {}
399                Err(e) => {
400                    return Err(APIError::CustomError {
401                        message: e.to_string(),
402                    })
403                }
404            }
405        }
406        match File::create(path) {
407            Ok(mut file) => match file.write_all(&bytes) {
408                Ok(_) => {}
409                Err(e) => {
410                    return Err(APIError::CustomError {
411                        message: e.to_string(),
412                    })
413                }
414            },
415            Err(e) => {
416                return Err(APIError::CustomError {
417                    message: e.to_string(),
418                })
419            }
420        }
421
422        Ok(AudioSpeechResponse {
423            result: true,
424            headers: Some(headers),
425        })
426    }
427
428    pub async fn create_fine_tuning_job(
429        &mut self,
430        req: CreateFineTuningJobRequest,
431    ) -> Result<FineTuningJobObject, APIError> {
432        self.post("fine_tuning/jobs", &req).await
433    }
434
435    pub async fn list_fine_tuning_jobs(
436        &mut self,
437    ) -> Result<FineTuningPagination<FineTuningJobObject>, APIError> {
438        self.get("fine_tuning/jobs").await
439    }
440
441    pub async fn list_fine_tuning_job_events(
442        &mut self,
443        req: ListFineTuningJobEventsRequest,
444    ) -> Result<FineTuningPagination<FineTuningJobEvent>, APIError> {
445        self.get(&format!(
446            "fine_tuning/jobs/{}/events",
447            req.fine_tuning_job_id
448        ))
449        .await
450    }
451
452    pub async fn retrieve_fine_tuning_job(
453        &mut self,
454        req: RetrieveFineTuningJobRequest,
455    ) -> Result<FineTuningJobObject, APIError> {
456        self.get(&format!("fine_tuning/jobs/{}", req.fine_tuning_job_id))
457            .await
458    }
459
460    pub async fn cancel_fine_tuning_job(
461        &mut self,
462        req: CancelFineTuningJobRequest,
463    ) -> Result<FineTuningJobObject, APIError> {
464        self.post(
465            &format!("fine_tuning/jobs/{}/cancel", req.fine_tuning_job_id),
466            &req,
467        )
468        .await
469    }
470
471    pub async fn create_moderation(
472        &mut self,
473        req: CreateModerationRequest,
474    ) -> Result<CreateModerationResponse, APIError> {
475        self.post("moderations", &req).await
476    }
477
478    pub async fn create_assistant(
479        &mut self,
480        req: AssistantRequest,
481    ) -> Result<AssistantObject, APIError> {
482        self.post("assistants", &req).await
483    }
484
485    pub async fn retrieve_assistant(
486        &mut self,
487        assistant_id: String,
488    ) -> Result<AssistantObject, APIError> {
489        self.get(&format!("assistants/{}", assistant_id)).await
490    }
491
492    pub async fn modify_assistant(
493        &mut self,
494        assistant_id: String,
495        req: AssistantRequest,
496    ) -> Result<AssistantObject, APIError> {
497        self.post(&format!("assistants/{}", assistant_id), &req)
498            .await
499    }
500
501    pub async fn delete_assistant(
502        &mut self,
503        assistant_id: String,
504    ) -> Result<DeletionStatus, APIError> {
505        self.delete(&format!("assistants/{}", assistant_id)).await
506    }
507
508    pub async fn list_assistant(
509        &mut self,
510        limit: Option<i64>,
511        order: Option<String>,
512        after: Option<String>,
513        before: Option<String>,
514    ) -> Result<ListAssistant, APIError> {
515        let url = Self::query_params(limit, order, after, before, "assistants".to_string());
516        self.get(&url).await
517    }
518
519    pub async fn create_assistant_file(
520        &mut self,
521        assistant_id: String,
522        req: AssistantFileRequest,
523    ) -> Result<AssistantFileObject, APIError> {
524        self.post(&format!("assistants/{}/files", assistant_id), &req)
525            .await
526    }
527
528    pub async fn retrieve_assistant_file(
529        &mut self,
530        assistant_id: String,
531        file_id: String,
532    ) -> Result<AssistantFileObject, APIError> {
533        self.get(&format!("assistants/{}/files/{}", assistant_id, file_id))
534            .await
535    }
536
537    pub async fn delete_assistant_file(
538        &mut self,
539        assistant_id: String,
540        file_id: String,
541    ) -> Result<DeletionStatus, APIError> {
542        self.delete(&format!("assistants/{}/files/{}", assistant_id, file_id))
543            .await
544    }
545
546    pub async fn list_assistant_file(
547        &mut self,
548        assistant_id: String,
549        limit: Option<i64>,
550        order: Option<String>,
551        after: Option<String>,
552        before: Option<String>,
553    ) -> Result<ListAssistantFile, APIError> {
554        let url = Self::query_params(
555            limit,
556            order,
557            after,
558            before,
559            format!("assistants/{}/files", assistant_id),
560        );
561        self.get(&url).await
562    }
563
564    pub async fn create_thread(
565        &mut self,
566        req: CreateThreadRequest,
567    ) -> Result<ThreadObject, APIError> {
568        self.post("threads", &req).await
569    }
570
571    pub async fn retrieve_thread(&mut self, thread_id: String) -> Result<ThreadObject, APIError> {
572        self.get(&format!("threads/{}", thread_id)).await
573    }
574
575    pub async fn modify_thread(
576        &mut self,
577        thread_id: String,
578        req: ModifyThreadRequest,
579    ) -> Result<ThreadObject, APIError> {
580        self.post(&format!("threads/{}", thread_id), &req).await
581    }
582
583    pub async fn delete_thread(&mut self, thread_id: String) -> Result<DeletionStatus, APIError> {
584        self.delete(&format!("threads/{}", thread_id)).await
585    }
586
587    pub async fn create_message(
588        &mut self,
589        thread_id: String,
590        req: CreateMessageRequest,
591    ) -> Result<MessageObject, APIError> {
592        self.post(&format!("threads/{}/messages", thread_id), &req)
593            .await
594    }
595
596    pub async fn retrieve_message(
597        &mut self,
598        thread_id: String,
599        message_id: String,
600    ) -> Result<MessageObject, APIError> {
601        self.get(&format!("threads/{}/messages/{}", thread_id, message_id))
602            .await
603    }
604
605    pub async fn modify_message(
606        &mut self,
607        thread_id: String,
608        message_id: String,
609        req: ModifyMessageRequest,
610    ) -> Result<MessageObject, APIError> {
611        self.post(
612            &format!("threads/{}/messages/{}", thread_id, message_id),
613            &req,
614        )
615        .await
616    }
617
618    pub async fn list_messages(&mut self, thread_id: String) -> Result<ListMessage, APIError> {
619        self.get(&format!("threads/{}/messages", thread_id)).await
620    }
621
622    pub async fn retrieve_message_file(
623        &mut self,
624        thread_id: String,
625        message_id: String,
626        file_id: String,
627    ) -> Result<MessageFileObject, APIError> {
628        self.get(&format!(
629            "threads/{}/messages/{}/files/{}",
630            thread_id, message_id, file_id
631        ))
632        .await
633    }
634
635    pub async fn list_message_file(
636        &mut self,
637        thread_id: String,
638        message_id: String,
639        limit: Option<i64>,
640        order: Option<String>,
641        after: Option<String>,
642        before: Option<String>,
643    ) -> Result<ListMessageFile, APIError> {
644        let url = Self::query_params(
645            limit,
646            order,
647            after,
648            before,
649            format!("threads/{}/messages/{}/files", thread_id, message_id),
650        );
651        self.get(&url).await
652    }
653
654    pub async fn create_run(
655        &mut self,
656        thread_id: String,
657        req: CreateRunRequest,
658    ) -> Result<RunObject, APIError> {
659        self.post(&format!("threads/{}/runs", thread_id), &req)
660            .await
661    }
662
663    pub async fn retrieve_run(
664        &mut self,
665        thread_id: String,
666        run_id: String,
667    ) -> Result<RunObject, APIError> {
668        self.get(&format!("threads/{}/runs/{}", thread_id, run_id))
669            .await
670    }
671
672    pub async fn modify_run(
673        &mut self,
674        thread_id: String,
675        run_id: String,
676        req: ModifyRunRequest,
677    ) -> Result<RunObject, APIError> {
678        self.post(&format!("threads/{}/runs/{}", thread_id, run_id), &req)
679            .await
680    }
681
682    pub async fn list_run(
683        &mut self,
684        thread_id: String,
685        limit: Option<i64>,
686        order: Option<String>,
687        after: Option<String>,
688        before: Option<String>,
689    ) -> Result<ListRun, APIError> {
690        let url = Self::query_params(
691            limit,
692            order,
693            after,
694            before,
695            format!("threads/{}/runs", thread_id),
696        );
697        self.get(&url).await
698    }
699
700    pub async fn cancel_run(
701        &mut self,
702        thread_id: String,
703        run_id: String,
704    ) -> Result<RunObject, APIError> {
705        self.post(
706            &format!("threads/{}/runs/{}/cancel", thread_id, run_id),
707            &ModifyRunRequest::default(),
708        )
709        .await
710    }
711
712    pub async fn create_thread_and_run(
713        &mut self,
714        req: CreateThreadAndRunRequest,
715    ) -> Result<RunObject, APIError> {
716        self.post("threads/runs", &req).await
717    }
718
719    pub async fn retrieve_run_step(
720        &mut self,
721        thread_id: String,
722        run_id: String,
723        step_id: String,
724    ) -> Result<RunStepObject, APIError> {
725        self.get(&format!(
726            "threads/{}/runs/{}/steps/{}",
727            thread_id, run_id, step_id
728        ))
729        .await
730    }
731
732    pub async fn list_run_step(
733        &mut self,
734        thread_id: String,
735        run_id: String,
736        limit: Option<i64>,
737        order: Option<String>,
738        after: Option<String>,
739        before: Option<String>,
740    ) -> Result<ListRunStep, APIError> {
741        let url = Self::query_params(
742            limit,
743            order,
744            after,
745            before,
746            format!("threads/{}/runs/{}/steps", thread_id, run_id),
747        );
748        self.get(&url).await
749    }
750
751    pub async fn create_batch(
752        &mut self,
753        req: CreateBatchRequest,
754    ) -> Result<BatchResponse, APIError> {
755        self.post("batches", &req).await
756    }
757
758    pub async fn retrieve_batch(&mut self, batch_id: String) -> Result<BatchResponse, APIError> {
759        self.get(&format!("batches/{}", batch_id)).await
760    }
761
762    pub async fn cancel_batch(&mut self, batch_id: String) -> Result<BatchResponse, APIError> {
763        self.post(
764            &format!("batches/{}/cancel", batch_id),
765            &common::EmptyRequestBody {},
766        )
767        .await
768    }
769
770    pub async fn list_batch(
771        &mut self,
772        after: Option<String>,
773        limit: Option<i64>,
774    ) -> Result<ListBatchResponse, APIError> {
775        let url = Self::query_params(limit, None, after, None, "batches".to_string());
776        self.get(&url).await
777    }
778
779    fn query_params(
780        limit: Option<i64>,
781        order: Option<String>,
782        after: Option<String>,
783        before: Option<String>,
784        mut url: String,
785    ) -> String {
786        let mut params = vec![];
787        if let Some(limit) = limit {
788            params.push(format!("limit={}", limit));
789        }
790        if let Some(order) = order {
791            params.push(format!("order={}", order));
792        }
793        if let Some(after) = after {
794            params.push(format!("after={}", after));
795        }
796        if let Some(before) = before {
797            params.push(format!("before={}", before));
798        }
799        if !params.is_empty() {
800            url = format!("{}?{}", url, params.join("&"));
801        }
802        url
803    }
804
805    fn is_beta(path: &str) -> bool {
806        path.starts_with("assistants") || path.starts_with("threads")
807    }
808
809    fn create_form<T>(req: &T, file_field: &str) -> Result<Form, APIError>
810    where
811        T: Serialize,
812    {
813        let json = match serde_json::to_value(req) {
814            Ok(json) => json,
815            Err(e) => {
816                return Err(APIError::CustomError {
817                    message: e.to_string(),
818                })
819            }
820        };
821        let file_path = if let Value::Object(map) = &json {
822            map.get(file_field)
823                .and_then(|v| v.as_str())
824                .ok_or(APIError::CustomError {
825                    message: format!("Field '{}' not found or not a string", file_field),
826                })?
827        } else {
828            return Err(APIError::CustomError {
829                message: "Request is not a JSON object".to_string(),
830            });
831        };
832
833        let mut file = match File::open(file_path) {
834            Ok(file) => file,
835            Err(e) => {
836                return Err(APIError::CustomError {
837                    message: e.to_string(),
838                })
839            }
840        };
841        let mut buffer = Vec::new();
842        match file.read_to_end(&mut buffer) {
843            Ok(_) => {}
844            Err(e) => {
845                return Err(APIError::CustomError {
846                    message: e.to_string(),
847                })
848            }
849        }
850
851        let mut form =
852            Form::new().part("file", Part::bytes(buffer).file_name(file_path.to_string()));
853
854        if let Value::Object(map) = json {
855            for (key, value) in map.into_iter() {
856                if key != file_field {
857                    match value {
858                        Value::String(s) => {
859                            form = form.text(key, s);
860                        }
861                        Value::Number(n) => {
862                            form = form.text(key, n.to_string());
863                        }
864                        _ => {}
865                    }
866                }
867            }
868        }
869
870        Ok(form)
871    }
872
873    fn create_form_from_bytes<T>(req: &T, bytes: Vec<u8>) -> Result<Form, APIError>
874    where
875        T: Serialize,
876    {
877        let json = match serde_json::to_value(req) {
878            Ok(json) => json,
879            Err(e) => {
880                return Err(APIError::CustomError {
881                    message: e.to_string(),
882                })
883            }
884        };
885
886        let mut form = Form::new().part("file", Part::bytes(bytes.clone()).file_name("file.mp3"));
887
888        if let Value::Object(map) = json {
889            for (key, value) in map.into_iter() {
890                match value {
891                    Value::String(s) => {
892                        form = form.text(key, s);
893                    }
894                    Value::Number(n) => {
895                        form = form.text(key, n.to_string());
896                    }
897                    _ => {}
898                }
899            }
900        }
901
902        Ok(form)
903    }
904}