Skip to main content

openresponses_rust/
client.rs

1use reqwest::{Client as ReqwestClient, header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue}};
2use serde_json;
3use thiserror::Error;
4
5use crate::types::{CreateResponseBody, ResponseResource};
6
7const DEFAULT_BASE_URL: &str = "https://api.openai.com/v1";
8
9#[derive(Error, Debug)]
10pub enum ClientError {
11    #[error("HTTP request failed: {0}")]
12    HttpError(#[from] reqwest::Error),
13    
14    #[error("JSON parsing error: {0}")]
15    JsonError(#[from] serde_json::Error),
16    
17    #[error("API error: {code} - {message}")]
18    ApiError { code: String, message: String },
19    
20    #[error("Invalid header value: {0}")]
21    InvalidHeader(String),
22}
23
24#[derive(Clone)]
25pub struct Client {
26    inner: ReqwestClient,
27    base_url: String,
28    api_key: String,
29}
30
31impl Client {
32    pub fn new(api_key: impl Into<String>) -> Self {
33        Self::with_base_url(api_key, DEFAULT_BASE_URL)
34    }
35    
36    pub fn with_base_url(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
37        let api_key = api_key.into();
38        let base_url = base_url.into();
39        
40        let mut headers = HeaderMap::new();
41        headers.insert(
42            CONTENT_TYPE,
43            HeaderValue::from_static("application/json"),
44        );
45        
46        let inner = ReqwestClient::builder()
47            .default_headers(headers)
48            .build()
49            .expect("Failed to create HTTP client");
50        
51        Self { inner, base_url, api_key }
52    }
53    
54    pub async fn create_response(&self, request: CreateResponseBody) -> Result<ResponseResource, ClientError> {
55        let url = format!("{}/responses", self.base_url);
56        
57        let response = self.inner
58            .post(&url)
59            .header(AUTHORIZATION, format!("Bearer {}", self.api_key))
60            .json(&request)
61            .send()
62            .await?;
63        
64        let status = response.status();
65        
66        if !status.is_success() {
67            let error_text = response.text().await?;
68            return Err(ClientError::ApiError {
69                code: status.to_string(),
70                message: error_text,
71            });
72        }
73        
74        let response_body = response.json::<ResponseResource>().await?;
75        Ok(response_body)
76    }
77    
78    pub async fn create_response_raw(&self, request: CreateResponseBody) -> Result<String, ClientError> {
79        let url = format!("{}/responses", self.base_url);
80        
81        let response = self.inner
82            .post(&url)
83            .header(AUTHORIZATION, format!("Bearer {}", self.api_key))
84            .json(&request)
85            .send()
86            .await?;
87        
88        let status = response.status();
89        let body = response.text().await?;
90        
91        if !status.is_success() {
92            return Err(ClientError::ApiError {
93                code: status.to_string(),
94                message: body,
95            });
96        }
97        
98        Ok(body)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::types::{Input, Item};
106    
107    #[test]
108    fn test_client_creation() {
109        let client = Client::new("test-api-key");
110        assert_eq!(client.api_key, "test-api-key");
111        assert_eq!(client.base_url, DEFAULT_BASE_URL);
112    }
113    
114    #[test]
115    fn test_client_with_base_url() {
116        let client = Client::with_base_url("test-key", "https://custom.api.com");
117        assert_eq!(client.api_key, "test-key");
118        assert_eq!(client.base_url, "https://custom.api.com");
119    }
120    
121    #[tokio::test]
122    async fn test_request_serialization() {
123        let request = CreateResponseBody {
124            model: Some("gpt-4o".to_string()),
125            input: Some(Input::Items(vec![
126                Item::user_message("Hello, world!")
127            ])),
128            ..Default::default()
129        };
130        
131        let json = serde_json::to_string(&request).unwrap();
132        assert!(json.contains("gpt-4o"));
133        assert!(json.contains("Hello, world!"));
134    }
135}