use crate::{
errors::Result,
http::{HttpClient, HttpClientError},
};
use async_trait::async_trait;
use serde_json::Value;
#[async_trait]
pub trait HttpClientWithAdminSupport: Send + Sync {
async fn get_value_with_admin_override(
&self,
url: &str,
admin_tenant: Option<&str>,
) -> Result<Value>;
}
pub struct HttpClientAdapter<T: HttpClient> {
inner: T,
}
impl<T: HttpClient> HttpClientAdapter<T> {
pub fn new(client: T) -> Self {
Self { inner: client }
}
}
#[async_trait]
impl<T: HttpClient> HttpClientWithAdminSupport for HttpClientAdapter<T> {
async fn get_value_with_admin_override(
&self,
url: &str,
admin_tenant: Option<&str>,
) -> Result<Value> {
if admin_tenant.is_some() {
tracing::warn!(
"Admin tenant override requested but not supported by underlying client: {:?}",
admin_tenant
);
}
self.inner
.get_value(url)
.await
.map_err(|e| crate::errors::Error::Network(e.to_string()))
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "http-reqwest"))]
#[cfg_attr(docsrs, doc(cfg(all(not(target_arch = "wasm32"), feature = "http-reqwest"))))]
pub mod reqwest_tenant {
use super::*;
use reqwest::{Client, header::{HeaderMap, HeaderName, HeaderValue}};
use std::time::Duration;
#[derive(Clone)]
pub struct ReqwestHttpClientWithAdminSupport {
client: Client,
admin_key: Option<String>,
}
impl ReqwestHttpClientWithAdminSupport {
pub fn new() -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.use_rustls_tls()
.build()
.map_err(|e| crate::errors::Error::Network(e.to_string()))?;
Ok(Self { client, admin_key: None })
}
pub fn with_admin_key(admin_key: String) -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.use_rustls_tls()
.build()
.map_err(|e| crate::errors::Error::Network(e.to_string()))?;
Ok(Self { client, admin_key: Some(admin_key) })
}
pub fn with_timeout(timeout_secs: u64) -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.use_rustls_tls()
.build()
.map_err(|e| crate::errors::Error::Network(e.to_string()))?;
Ok(Self { client, admin_key: None })
}
}
impl Default for ReqwestHttpClientWithAdminSupport {
fn default() -> Self {
Self::new().expect("Failed to create default HTTP client")
}
}
#[async_trait]
impl HttpClientWithAdminSupport for ReqwestHttpClientWithAdminSupport {
async fn get_value_with_admin_override(
&self,
url: &str,
admin_tenant: Option<&str>,
) -> Result<Value> {
let mut request = self.client.get(url);
let mut header_added = false;
if let (Some(admin_key), Some(tenant)) = (&self.admin_key, admin_tenant) {
let mut header_map = HeaderMap::new();
let admin_key_header = HeaderValue::from_str(admin_key)
.map_err(|e| crate::errors::Error::Network(format!("Invalid admin key: {}", e)))?;
header_map.insert("x-admin-key", admin_key_header);
let tenant_header = HeaderValue::from_str(tenant)
.map_err(|e| crate::errors::Error::Network(format!("Invalid tenant: {}", e)))?;
header_map.insert("x-admin-tenant", tenant_header);
request = request.headers(header_map);
header_added = true;
}
tracing::info!(
target: "xjp_oidc::http",
"HTTP 请求: {} (admin_override: {})",
url,
header_added
);
let response = request
.send()
.await
.map_err(|e| crate::errors::Error::Network(format!("Request failed: {}", e)))?;
let status = response.status();
let duration = std::time::Instant::now();
if !status.is_success() {
let error_body = response
.text()
.await
.unwrap_or_else(|_| "Failed to read error response".to_string());
tracing::error!(
target: "xjp_oidc::http",
"HTTP 请求失败 url={} status={} error={}",
url,
status,
error_body
);
return Err(crate::errors::Error::Network(format!(
"HTTP {} - {}",
status, error_body
)));
}
let value = response
.json::<Value>()
.await
.map_err(|e| crate::errors::Error::Network(format!("Failed to parse JSON: {}", e)))?;
tracing::info!(
target: "xjp_oidc::http",
"HTTP 请求完成 url={} method=\"GET\" duration_ms={} status={}",
url,
duration.elapsed().as_millis(),
status.as_u16()
);
Ok(value)
}
}
#[async_trait]
impl HttpClient for ReqwestHttpClientWithAdminSupport {
async fn get_value(&self, url: &str) -> std::result::Result<Value, HttpClientError> {
let response = self
.client
.get(url)
.send()
.await
.map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
let status = response.status();
if !status.is_success() {
let error_body = response
.text()
.await
.unwrap_or_else(|_| "Failed to read error response".to_string());
return Err(HttpClientError::InvalidStatus {
status: status.as_u16(),
message: error_body,
});
}
response
.json::<Value>()
.await
.map_err(|e| HttpClientError::ParseError(e.to_string()))
}
async fn post_form_value(
&self,
url: &str,
form: &[(String, String)],
auth_header: Option<(&str, &str)>,
) -> std::result::Result<Value, HttpClientError> {
let mut request = self.client.post(url).form(form);
if let Some((key, value)) = auth_header {
let header_value = HeaderValue::from_str(value)
.map_err(|e| HttpClientError::RequestFailed(format!("Invalid header value: {}", e)))?;
let header_name = HeaderName::from_bytes(key.as_bytes())
.map_err(|e| HttpClientError::RequestFailed(format!("Invalid header name: {}", e)))?;
request = request.header(header_name, header_value);
}
let response = request
.send()
.await
.map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
let status = response.status();
if !status.is_success() {
let error_body = response
.text()
.await
.unwrap_or_else(|_| "Failed to read error response".to_string());
return Err(HttpClientError::InvalidStatus {
status: status.as_u16(),
message: error_body,
});
}
response
.json::<Value>()
.await
.map_err(|e| HttpClientError::ParseError(e.to_string()))
}
async fn post_json_value(
&self,
url: &str,
body: &Value,
auth_header: Option<(&str, &str)>,
) -> std::result::Result<Value, HttpClientError> {
let mut request = self.client.post(url).json(body);
if let Some((key, value)) = auth_header {
let header_value = HeaderValue::from_str(value)
.map_err(|e| HttpClientError::RequestFailed(format!("Invalid header value: {}", e)))?;
let header_name = HeaderName::from_bytes(key.as_bytes())
.map_err(|e| HttpClientError::RequestFailed(format!("Invalid header name: {}", e)))?;
request = request.header(header_name, header_value);
}
let response = request
.send()
.await
.map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
let status = response.status();
if !status.is_success() {
let error_body = response
.text()
.await
.unwrap_or_else(|_| "Failed to read error response".to_string());
return Err(HttpClientError::InvalidStatus {
status: status.as_u16(),
message: error_body,
});
}
response
.json::<Value>()
.await
.map_err(|e| HttpClientError::ParseError(e.to_string()))
}
}
}