1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{auth, error::OpenAIAPIError, OpenAI, OpenAIResult};
5
6pub mod chat;
7pub mod chat_reasoning;
8pub mod embeddings;
9
10const API_BASE_URL: &str = "https://api.openai.com/v1";
11
12#[derive(Deserialize)]
15#[serde(untagged)]
16enum GenericOpenAIResponse<T> {
17 Success(T),
18 Error(ResponseDeserializableOpenAIAPIError),
19}
20
21#[derive(Deserialize)]
22struct ResponseDeserializableOpenAIAPIError {
23 error: OpenAIAPIError,
24}
25
26impl<T> From<GenericOpenAIResponse<T>> for OpenAIResult<T> {
27 fn from(value: GenericOpenAIResponse<T>) -> Self {
28 match value {
29 GenericOpenAIResponse::Success(success) => Ok(success),
30 GenericOpenAIResponse::Error(error) => Err(crate::OpenAIError::API(error.error)),
31 }
32 }
33}
34
35pub(super) async fn send_request<Auth, R>(
36 openai: &OpenAI<Auth>,
37 request: &R,
38) -> OpenAIResult<R::Response>
39where
40 Auth: auth::AuthTokenProvider,
41 R: OpenAIRequestProvider,
42{
43 let bearer_token = openai
44 .auth
45 .resolve()
46 .await
47 .ok_or(crate::error::OpenAIError::MissingAuthToken)?;
48
49 let response_text = openai
52 .client
53 .request(
54 R::METHOD,
55 format!("{API_BASE_URL}{}", R::path_with_leading_slash()),
56 )
57 .header("Authorization", format!("Bearer {bearer_token}"))
58 .json(request)
60 .send()
61 .await?
62 .text()
63 .await?;
64
65 match serde_json::from_str::<GenericOpenAIResponse<R::Response>>(&response_text) {
66 Ok(response) => response.into(),
67 Err(err) => Err(crate::error::OpenAIError::Serde(response_text, err)),
68 }
69}
70
71mod private {
72 pub trait Sealed {}
73}
74
75pub trait OpenAIRequestProvider: Serialize + private::Sealed {
77 type Response: for<'de> Deserialize<'de>;
78 const METHOD: Method;
79
80 fn path_with_leading_slash() -> String;
81}