use http::Error as HttpError; use reqwest::IntoUrl; use reqwest::Method; use reqwest::Response; use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; use serde::Serialize; use std::collections::HashMap; use std::fmt::Display; use std::time::Duration; #[derive(Clone, Debug)] pub struct CodexHttpClient { inner: reqwest::Client, } impl CodexHttpClient { pub fn new(inner: reqwest::Client) -> Self { Self { inner } } pub fn get(&self, url: U) -> CodexRequestBuilder where U: IntoUrl, { self.request(Method::GET, url) } pub fn post(&self, url: U) -> CodexRequestBuilder where U: IntoUrl, { self.request(Method::POST, url) } pub fn request(&self, method: Method, url: U) -> CodexRequestBuilder where U: IntoUrl, { let url_str = url.as_str().to_string(); CodexRequestBuilder::new(self.inner.request(method.clone(), url), method, url_str) } } #[must_use = "requests are not sent unless `send` is awaited"] #[derive(Debug)] pub struct CodexRequestBuilder { builder: reqwest::RequestBuilder, method: Method, url: String, } impl CodexRequestBuilder { fn new(builder: reqwest::RequestBuilder, method: Method, url: String) -> Self { Self { builder, method, url, } } fn map(self, f: impl FnOnce(reqwest::RequestBuilder) -> reqwest::RequestBuilder) -> Self { Self { builder: f(self.builder), method: self.method, url: self.url, } } pub fn headers(self, headers: HeaderMap) -> Self { self.map(|builder| builder.headers(headers)) } pub fn header(self, key: K, value: V) -> Self where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { self.map(|builder| builder.header(key, value)) } pub fn bearer_auth(self, token: T) -> Self where T: Display, { self.map(|builder| builder.bearer_auth(token)) } pub fn timeout(self, timeout: Duration) -> Self { self.map(|builder| builder.timeout(timeout)) } pub fn json(self, value: &T) -> Self where T: ?Sized + Serialize, { self.map(|builder| builder.json(value)) } pub async fn send(self) -> Result { match self.builder.send().await { Ok(response) => { let request_ids = Self::extract_request_ids(&response); tracing::debug!( method = %self.method, url = %self.url, status = %response.status(), request_ids = ?request_ids, version = ?response.version(), "Request completed" ); Ok(response) } Err(error) => { let status = error.status(); tracing::debug!( method = %self.method, url = %self.url, status = status.map(|s| s.as_u16()), error = %error, "Request failed" ); Err(error) } } } fn extract_request_ids(response: &Response) -> HashMap { ["cf-ray", "x-request-id", "x-oai-request-id"] .iter() .filter_map(|&name| { let header_name = HeaderName::from_static(name); let value = response.headers().get(header_name)?; let value = value.to_str().ok()?.to_owned(); Some((name.to_owned(), value)) }) .collect() } }