1use crate::error::{Result, TruthlinkedError};
2use crate::license::LicenseKey;
3use crate::logging::{LoggingConfig, RequestLogger, RequestTimer};
4use crate::retry::{RetryConfig, RetryExecutor};
5use crate::signing::RequestSigner;
6use crate::types::*;
7use reqwest::{Client as HttpClient, StatusCode};
8use std::time::Duration;
9
10pub struct Client {
44 http_client: HttpClient,
46 base_url: String,
48 license_key: LicenseKey,
50 signer: RequestSigner,
52 retry_executor: RetryExecutor,
54 logger: RequestLogger,
56}
57
58impl Client {
59 pub fn new(base_url: impl Into<String>, license_key: impl Into<String>) -> Result<Self> {
90 let base_url_string = base_url.into();
91 let license_key_string = license_key.into();
92
93 if !base_url_string.starts_with("https://") {
95 return Err(TruthlinkedError::InvalidRequest(
96 "Base URL must use HTTPS".to_string()
97 ));
98 }
99
100 let http_client = HttpClient::builder()
102 .timeout(Duration::from_secs(30))
103 .connect_timeout(Duration::from_secs(10))
104 .pool_idle_timeout(Duration::from_secs(90))
105 .pool_max_idle_per_host(10)
106 .https_only(true) .build()?;
108
109 Ok(Self {
110 http_client,
111 signer: RequestSigner::new(&license_key_string),
112 base_url: base_url_string,
113 license_key: LicenseKey::new(license_key_string),
114 retry_executor: RetryExecutor::new(RetryConfig::production()),
115 logger: RequestLogger::new(LoggingConfig::production()),
116 })
117 }
118
119 pub(crate) fn with_config(
121 http_client: HttpClient,
122 base_url: String,
123 license_key: String,
124 retry_config: RetryConfig,
125 logging_config: LoggingConfig,
126 ) -> Result<Self> {
127 Ok(Self {
128 http_client,
129 base_url,
130 signer: RequestSigner::new(&license_key),
131 license_key: LicenseKey::new(license_key),
132 retry_executor: RetryExecutor::new(retry_config),
133 logger: RequestLogger::new(logging_config),
134 })
135 }
136
137 pub async fn health(&self) -> Result<HealthResponse> {
158 let url = format!("{}/health", self.base_url);
159
160 self.retry_executor.execute(|| async {
161 let timer = RequestTimer::new();
162 let timestamp = RequestSigner::current_timestamp();
163 let signature = self.signer.sign_request("GET", "/health", b"", timestamp);
164
165 let timestamp_str = timestamp.to_string();
167 let headers = vec![
168 ("X-Timestamp", timestamp_str.as_str()),
169 ("X-Signature", signature.as_str()),
170 ];
171 self.logger.log_request("GET", &url, &headers, b"");
172
173 match self.http_client
174 .get(&url)
175 .header("X-Timestamp", timestamp.to_string())
176 .header("X-Signature", signature)
177 .send()
178 .await
179 {
180 Ok(response) => {
181 let status = response.status().as_u16();
182 let response_headers = vec![]; match response.status() {
185 StatusCode::OK => {
186 let body = response.bytes().await?;
187 self.logger.log_response(status, &response_headers, &body, timer.elapsed());
188
189 let health: HealthResponse = serde_json::from_slice(&body)?;
190 Ok(health)
191 }
192 _ => {
193 let body = response.bytes().await?;
194 self.logger.log_response(status, &response_headers, &body, timer.elapsed());
195 self.handle_error_status(StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR))
196 }
197 }
198 }
199 Err(e) => {
200 self.logger.log_error("GET", &url, &e.to_string(), timer.elapsed());
201 Err(e.into())
202 }
203 }
204 }).await
205 }
206
207 pub async fn exchange_token(
252 &self,
253 sso_token: impl Into<String>,
254 requested_scope: Vec<String>,
255 nonce: [u8; 32],
256 channel_binding: [u8; 32],
257 ) -> Result<TokenResponse> {
258 let url = format!("{}/v1/tokens", self.base_url);
259
260 let request = TokenRequest {
261 sso_token: sso_token.into(),
262 requested_scope,
263 nonce: hex::encode(nonce),
264 channel_binding: hex::encode(channel_binding),
265 };
266
267 let response = self.http_client
268 .post(&url)
269 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
270 .json(&request)
271 .send()
272 .await?;
273
274 self.handle_response(response).await
275 }
276
277 pub async fn validate_token(&self, token_id: impl Into<String>) -> Result<ValidateResponse> {
279 let url = format!("{}/v1/tokens/{}/validate", self.base_url, token_id.into());
280
281 let response = self.http_client
282 .get(&url)
283 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
284 .send()
285 .await?;
286
287 self.handle_response(response).await
288 }
289
290 pub async fn get_shadow_decisions(&self) -> Result<Vec<ShadowDecision>> {
320 let url = format!("{}/v1/shadow/decisions", self.base_url);
321
322 let response = self.http_client
323 .get(&url)
324 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
325 .send()
326 .await?;
327
328 self.handle_response(response).await
329 }
330
331 pub async fn replay_iam_logs(
333 &self,
334 logs: Vec<String>,
335 adapter: impl Into<String>,
336 ) -> Result<ReplayResponse> {
337 let url = format!("{}/v1/shadow/replay", self.base_url);
338
339 let request = ReplayRequest {
340 logs,
341 adapter: adapter.into(),
342 };
343
344 let response = self.http_client
345 .post(&url)
346 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
347 .json(&request)
348 .send()
349 .await?;
350
351 self.handle_response(response).await
352 }
353
354 pub async fn get_sox_report(&self) -> Result<SoxReport> {
356 let url = format!("{}/v1/compliance/sox", self.base_url);
357
358 let response = self.http_client
359 .get(&url)
360 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
361 .send()
362 .await?;
363
364 self.handle_response(response).await
365 }
366
367 pub async fn get_pci_report(&self) -> Result<PciReport> {
369 let url = format!("{}/v1/compliance/pci", self.base_url);
370
371 let response = self.http_client
372 .get(&url)
373 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
374 .send()
375 .await?;
376
377 self.handle_response(response).await
378 }
379
380 pub async fn get_audit_logs(&self) -> Result<Vec<AuditLog>> {
382 let url = format!("{}/v1/audit/logs", self.base_url);
383
384 let response = self.http_client
385 .get(&url)
386 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
387 .send()
388 .await?;
389
390 self.handle_response(response).await
391 }
392
393 pub async fn get_usage(&self) -> Result<UsageResponse> {
395 let url = format!("{}/v1/usage", self.base_url);
396
397 let response = self.http_client
398 .get(&url)
399 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
400 .send()
401 .await?;
402
403 self.handle_response(response).await
404 }
405
406 async fn handle_response<T: serde::de::DeserializeOwned>(
408 &self,
409 response: reqwest::Response,
410 ) -> Result<T> {
411 let status = response.status();
412
413 match status {
414 StatusCode::OK => {
415 response.json::<T>().await.map_err(|_| TruthlinkedError::InvalidResponse)
416 }
417 StatusCode::UNAUTHORIZED => Err(TruthlinkedError::Unauthorized),
418 StatusCode::FORBIDDEN => Err(TruthlinkedError::Forbidden),
419 StatusCode::TOO_MANY_REQUESTS => {
420 let body = response.text().await.unwrap_or_default();
421 Err(TruthlinkedError::RateLimitExceeded(body))
422 }
423 StatusCode::BAD_REQUEST | StatusCode::UNPROCESSABLE_ENTITY => {
424 let body = response.text().await.unwrap_or_default();
425 Err(TruthlinkedError::InvalidRequest(body))
426 }
427 _ if status.is_server_error() => Err(TruthlinkedError::ServerError),
428 _ => Err(TruthlinkedError::InvalidResponse),
429 }
430 }
431
432 pub async fn submit_witness(&self, submission: WitnessSubmission) -> Result<WitnessEvent> {
436 let url = format!("{}/witness/submit", self.base_url);
437
438 let response = self.http_client
439 .post(&url)
440 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
441 .json(&serde_json::json!({ "submission": submission }))
442 .send()
443 .await?;
444
445 self.handle_response(response).await
446 }
447
448 pub async fn get_witness_event(&self, sequence: u64, include_proof: bool) -> Result<WitnessEvent> {
450 let url = format!("{}/witness/event/{}", self.base_url, sequence);
451
452 let response = self.http_client
453 .get(&url)
454 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
455 .query(&[("include_proof", include_proof.to_string())])
456 .send()
457 .await?;
458
459 self.handle_response(response).await
460 }
461
462 pub async fn get_latest_sth(&self) -> Result<SignedTreeHead> {
464 let url = format!("{}/witness/sth/latest", self.base_url);
465
466 let response = self.http_client
467 .get(&url)
468 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
469 .send()
470 .await?;
471
472 self.handle_response(response).await
473 }
474
475 pub async fn get_sth(&self, tree_size: u64) -> Result<SignedTreeHead> {
477 let url = format!("{}/witness/sth/{}", self.base_url, tree_size);
478
479 let response = self.http_client
480 .get(&url)
481 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
482 .send()
483 .await?;
484
485 self.handle_response(response).await
486 }
487
488 pub async fn export_witness_chain(&self, start_seq: Option<u64>, end_seq: Option<u64>) -> Result<Vec<u8>> {
490 let url = format!("{}/witness/export", self.base_url);
491
492 let mut request = self.http_client
493 .get(&url)
494 .header("Authorization", format!("Bearer {}", self.license_key.as_str()));
495
496 if let Some(start) = start_seq {
497 request = request.query(&[("start_seq", start.to_string())]);
498 }
499 if let Some(end) = end_seq {
500 request = request.query(&[("end_seq", end.to_string())]);
501 }
502
503 let response = request.send().await?;
504
505 if !response.status().is_success() {
506 return self.handle_error_status(response.status());
507 }
508
509 Ok(response.bytes().await?.to_vec())
510 }
511
512 pub async fn witness_health(&self) -> Result<WitnessHealthResponse> {
514 let url = format!("{}/witness/health", self.base_url);
515
516 let response = self.http_client
517 .get(&url)
518 .header("Authorization", format!("Bearer {}", self.license_key.as_str()))
519 .send()
520 .await?;
521
522 self.handle_response(response).await
523 }
524
525 fn handle_error_status<T>(&self, status: StatusCode) -> Result<T> {
527 match status {
528 StatusCode::UNAUTHORIZED => Err(TruthlinkedError::Unauthorized),
529 StatusCode::FORBIDDEN => Err(TruthlinkedError::Forbidden),
530 StatusCode::TOO_MANY_REQUESTS => {
531 Err(TruthlinkedError::RateLimitExceeded("Rate limit exceeded".to_string()))
532 }
533 StatusCode::BAD_REQUEST | StatusCode::UNPROCESSABLE_ENTITY => {
534 Err(TruthlinkedError::InvalidRequest("Invalid request".to_string()))
535 }
536 _ if status.is_server_error() => Err(TruthlinkedError::ServerError),
537 _ => Err(TruthlinkedError::InvalidResponse),
538 }
539 }
540}
541
542impl std::fmt::Debug for Client {
543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544 f.debug_struct("Client")
545 .field("base_url", &self.base_url)
546 .field("license_key", &self.license_key.redacted())
547 .finish()
548 }
549}