xjp_oidc/
http_tenant.rs

1//! HTTP client extension for multi-tenant support
2
3use crate::{
4    errors::Result,
5    http::{HttpClient, HttpClientError},
6};
7use async_trait::async_trait;
8use serde_json::Value;
9
10/// Enhanced HTTP client trait with admin header support for multi-tenant
11#[async_trait]
12pub trait HttpClientWithAdminSupport: Send + Sync {
13    /// Perform a GET request with optional admin override header
14    async fn get_value_with_admin_override(
15        &self,
16        url: &str,
17        admin_tenant: Option<&str>,
18    ) -> Result<Value>;
19}
20
21/// Adapter to add admin header support to existing HttpClient implementations
22pub struct HttpClientAdapter<T: HttpClient> {
23    inner: T,
24}
25
26impl<T: HttpClient> HttpClientAdapter<T> {
27    /// Create a new adapter wrapping an existing HTTP client
28    pub fn new(client: T) -> Self {
29        Self { inner: client }
30    }
31}
32
33#[async_trait]
34impl<T: HttpClient> HttpClientWithAdminSupport for HttpClientAdapter<T> {
35    async fn get_value_with_admin_override(
36        &self,
37        url: &str,
38        admin_tenant: Option<&str>,
39    ) -> Result<Value> {
40        // For basic adapter, admin override is not supported
41        // This will just use the standard client
42        if admin_tenant.is_some() {
43            tracing::warn!(
44                "Admin tenant override requested but not supported by underlying client: {:?}",
45                admin_tenant
46            );
47        }
48        self.inner
49            .get_value(url)
50            .await
51            .map_err(|e| crate::errors::Error::Network(e.to_string()))
52    }
53}
54
55/// Enhanced Reqwest implementation with header support
56#[cfg(all(not(target_arch = "wasm32"), feature = "http-reqwest"))]
57#[cfg_attr(docsrs, doc(cfg(all(not(target_arch = "wasm32"), feature = "http-reqwest"))))]
58pub mod reqwest_tenant {
59    use super::*;
60    use reqwest::{Client, header::{HeaderMap, HeaderName, HeaderValue}};
61    use std::time::Duration;
62
63    /// Enhanced Reqwest HTTP client with admin override support
64    #[derive(Clone)]
65    pub struct ReqwestHttpClientWithAdminSupport {
66        client: Client,
67        admin_key: Option<String>,
68    }
69
70    impl ReqwestHttpClientWithAdminSupport {
71        /// Create a new HTTP client with default settings
72        pub fn new() -> Result<Self> {
73            let client = Client::builder()
74                .timeout(Duration::from_secs(30))
75                .use_rustls_tls()
76                .build()
77                .map_err(|e| crate::errors::Error::Network(e.to_string()))?;
78
79            Ok(Self { client, admin_key: None })
80        }
81
82        /// Create a new HTTP client with admin key support
83        pub fn with_admin_key(admin_key: String) -> Result<Self> {
84            let client = Client::builder()
85                .timeout(Duration::from_secs(30))
86                .use_rustls_tls()
87                .build()
88                .map_err(|e| crate::errors::Error::Network(e.to_string()))?;
89
90            Ok(Self { client, admin_key: Some(admin_key) })
91        }
92
93        /// Create a new HTTP client with custom timeout
94        pub fn with_timeout(timeout_secs: u64) -> Result<Self> {
95            let client = Client::builder()
96                .timeout(Duration::from_secs(timeout_secs))
97                .use_rustls_tls()
98                .build()
99                .map_err(|e| crate::errors::Error::Network(e.to_string()))?;
100
101            Ok(Self { client, admin_key: None })
102        }
103    }
104
105    impl Default for ReqwestHttpClientWithAdminSupport {
106        fn default() -> Self {
107            Self::new().expect("Failed to create default HTTP client")
108        }
109    }
110
111    #[async_trait]
112    impl HttpClientWithAdminSupport for ReqwestHttpClientWithAdminSupport {
113        async fn get_value_with_admin_override(
114            &self,
115            url: &str,
116            admin_tenant: Option<&str>,
117        ) -> Result<Value> {
118            let mut request = self.client.get(url);
119
120            // Add admin override headers if available
121            let mut header_added = false;
122            if let (Some(admin_key), Some(tenant)) = (&self.admin_key, admin_tenant) {
123                let mut header_map = HeaderMap::new();
124                
125                // Add admin key header
126                let admin_key_header = HeaderValue::from_str(admin_key)
127                    .map_err(|e| crate::errors::Error::Network(format!("Invalid admin key: {}", e)))?;
128                header_map.insert("x-admin-key", admin_key_header);
129                
130                // Add tenant override header
131                let tenant_header = HeaderValue::from_str(tenant)
132                    .map_err(|e| crate::errors::Error::Network(format!("Invalid tenant: {}", e)))?;
133                header_map.insert("x-admin-tenant", tenant_header);
134                
135                request = request.headers(header_map);
136                header_added = true;
137            }
138
139            tracing::info!(
140                target: "xjp_oidc::http",
141                "HTTP 请求: {} (admin_override: {})",
142                url,
143                header_added
144            );
145
146            let response = request
147                .send()
148                .await
149                .map_err(|e| crate::errors::Error::Network(format!("Request failed: {}", e)))?;
150
151            let status = response.status();
152            let duration = std::time::Instant::now();
153
154            if !status.is_success() {
155                let error_body = response
156                    .text()
157                    .await
158                    .unwrap_or_else(|_| "Failed to read error response".to_string());
159
160                tracing::error!(
161                    target: "xjp_oidc::http",
162                    "HTTP 请求失败 url={} status={} error={}",
163                    url,
164                    status,
165                    error_body
166                );
167
168                return Err(crate::errors::Error::Network(format!(
169                    "HTTP {} - {}",
170                    status, error_body
171                )));
172            }
173
174            let value = response
175                .json::<Value>()
176                .await
177                .map_err(|e| crate::errors::Error::Network(format!("Failed to parse JSON: {}", e)))?;
178
179            tracing::info!(
180                target: "xjp_oidc::http",
181                "HTTP 请求完成 url={} method=\"GET\" duration_ms={} status={}",
182                url,
183                duration.elapsed().as_millis(),
184                status.as_u16()
185            );
186
187            Ok(value)
188        }
189    }
190
191    // Also implement the standard HttpClient trait
192    #[async_trait]
193    impl HttpClient for ReqwestHttpClientWithAdminSupport {
194        async fn get_value(&self, url: &str) -> std::result::Result<Value, HttpClientError> {
195            let response = self
196                .client
197                .get(url)
198                .send()
199                .await
200                .map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
201
202            let status = response.status();
203            if !status.is_success() {
204                let error_body = response
205                    .text()
206                    .await
207                    .unwrap_or_else(|_| "Failed to read error response".to_string());
208
209                return Err(HttpClientError::InvalidStatus {
210                    status: status.as_u16(),
211                    message: error_body,
212                });
213            }
214
215            response
216                .json::<Value>()
217                .await
218                .map_err(|e| HttpClientError::ParseError(e.to_string()))
219        }
220
221        async fn post_form_value(
222            &self,
223            url: &str,
224            form: &[(String, String)],
225            auth_header: Option<(&str, &str)>,
226        ) -> std::result::Result<Value, HttpClientError> {
227            let mut request = self.client.post(url).form(form);
228
229            if let Some((key, value)) = auth_header {
230                let header_value = HeaderValue::from_str(value)
231                    .map_err(|e| HttpClientError::RequestFailed(format!("Invalid header value: {}", e)))?;
232                let header_name = HeaderName::from_bytes(key.as_bytes())
233                    .map_err(|e| HttpClientError::RequestFailed(format!("Invalid header name: {}", e)))?;
234                request = request.header(header_name, header_value);
235            }
236
237            let response = request
238                .send()
239                .await
240                .map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
241
242            let status = response.status();
243            if !status.is_success() {
244                let error_body = response
245                    .text()
246                    .await
247                    .unwrap_or_else(|_| "Failed to read error response".to_string());
248
249                return Err(HttpClientError::InvalidStatus {
250                    status: status.as_u16(),
251                    message: error_body,
252                });
253            }
254
255            response
256                .json::<Value>()
257                .await
258                .map_err(|e| HttpClientError::ParseError(e.to_string()))
259        }
260
261        async fn post_json_value(
262            &self,
263            url: &str,
264            body: &Value,
265            auth_header: Option<(&str, &str)>,
266        ) -> std::result::Result<Value, HttpClientError> {
267            let mut request = self.client.post(url).json(body);
268
269            if let Some((key, value)) = auth_header {
270                let header_value = HeaderValue::from_str(value)
271                    .map_err(|e| HttpClientError::RequestFailed(format!("Invalid header value: {}", e)))?;
272                let header_name = HeaderName::from_bytes(key.as_bytes())
273                    .map_err(|e| HttpClientError::RequestFailed(format!("Invalid header name: {}", e)))?;
274                request = request.header(header_name, header_value);
275            }
276
277            let response = request
278                .send()
279                .await
280                .map_err(|e| HttpClientError::RequestFailed(e.to_string()))?;
281
282            let status = response.status();
283            if !status.is_success() {
284                let error_body = response
285                    .text()
286                    .await
287                    .unwrap_or_else(|_| "Failed to read error response".to_string());
288
289                return Err(HttpClientError::InvalidStatus {
290                    status: status.as_u16(),
291                    message: error_body,
292                });
293            }
294
295            response
296                .json::<Value>()
297                .await
298                .map_err(|e| HttpClientError::ParseError(e.to_string()))
299        }
300    }
301}