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