mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 07:03:56 +02:00
feat(intel-dcap-api): add automatic retry logic for 429 rate limiting
- Add `max_retries` field to ApiClient with default of 3 retries - Implement `execute_with_retry()` helper method in helpers.rs - Update all HTTP requests to use retry wrapper for automatic 429 handling - Add `TooManyRequests` error variant with request_id and retry_after fields - Respect Retry-After header duration before retrying requests - Add `set_max_retries()` method to configure retry behavior (0 disables) - Update documentation and add handle_rate_limit example - Enhanced error handling in check_status() for 429 responses The client now transparently handles Intel API rate limiting while remaining configurable for users who need different retry behavior or manual handling. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
205113ecfa
commit
bb9c5b195e
9 changed files with 267 additions and 15 deletions
|
@ -16,6 +16,7 @@ and enclave identity verification.
|
||||||
- Type-safe request/response structures
|
- Type-safe request/response structures
|
||||||
- Support for SGX and TDX platforms
|
- Support for SGX and TDX platforms
|
||||||
- Real data integration tests
|
- Real data integration tests
|
||||||
|
- **Automatic rate limit handling with configurable retries**
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ cargo run --example get_pck_crl # Fetch certificate revocation lists
|
||||||
cargo run --example common_usage # Common attestation verification patterns
|
cargo run --example common_usage # Common attestation verification patterns
|
||||||
cargo run --example integration_test # Comprehensive test of most API endpoints
|
cargo run --example integration_test # Comprehensive test of most API endpoints
|
||||||
cargo run --example fetch_test_data # Fetch real data from Intel API for tests
|
cargo run --example fetch_test_data # Fetch real data from Intel API for tests
|
||||||
|
cargo run --example handle_rate_limit # Demonstrate automatic rate limiting handling
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
@ -45,6 +47,8 @@ cargo run --example fetch_test_data # Fetch real data from Intel API for tests
|
||||||
- **ApiClient** (`src/client/mod.rs`): Main entry point supporting API v3/v4
|
- **ApiClient** (`src/client/mod.rs`): Main entry point supporting API v3/v4
|
||||||
- Base URL: https://api.trustedservices.intel.com
|
- Base URL: https://api.trustedservices.intel.com
|
||||||
- Manages HTTP client and API version selection
|
- Manages HTTP client and API version selection
|
||||||
|
- Automatic retry logic for 429 (Too Many Requests) responses
|
||||||
|
- Default: 3 retries, configurable via `set_max_retries()`
|
||||||
|
|
||||||
### Key Modules
|
### Key Modules
|
||||||
|
|
||||||
|
@ -69,6 +73,7 @@ cargo run --example fetch_test_data # Fetch real data from Intel API for tests
|
||||||
|
|
||||||
- **error.rs**: `IntelApiError` for comprehensive error handling
|
- **error.rs**: `IntelApiError` for comprehensive error handling
|
||||||
- Extracts error details from Error-Code and Error-Message headers
|
- Extracts error details from Error-Code and Error-Message headers
|
||||||
|
- **`TooManyRequests` variant for rate limiting (429) after retry exhaustion**
|
||||||
- **types.rs**: Enums (CaType, ApiVersion, UpdateType, etc.)
|
- **types.rs**: Enums (CaType, ApiVersion, UpdateType, etc.)
|
||||||
- **requests.rs**: Request structures
|
- **requests.rs**: Request structures
|
||||||
- **responses.rs**: Response structures with JSON and certificate data
|
- **responses.rs**: Response structures with JSON and certificate data
|
||||||
|
@ -78,10 +83,18 @@ cargo run --example fetch_test_data # Fetch real data from Intel API for tests
|
||||||
All client methods follow this pattern:
|
All client methods follow this pattern:
|
||||||
|
|
||||||
1. Build request with query parameters
|
1. Build request with query parameters
|
||||||
2. Send HTTP request with proper headers
|
2. Send HTTP request with proper headers (with automatic retry on 429)
|
||||||
3. Parse response (JSON + certificate chains)
|
3. Parse response (JSON + certificate chains)
|
||||||
4. Return typed response or error
|
4. Return typed response or error
|
||||||
|
|
||||||
|
### Rate Limiting & Retry Logic
|
||||||
|
|
||||||
|
- **Automatic Retries**: All HTTP requests automatically retry on 429 (Too Many Requests) responses
|
||||||
|
- **Retry Configuration**: Default 3 retries, configurable via `ApiClient::set_max_retries()`
|
||||||
|
- **Retry-After Handling**: Waits for duration specified in Retry-After header before retrying
|
||||||
|
- **Error Handling**: `IntelApiError::TooManyRequests` returned only after all retries exhausted
|
||||||
|
- **Implementation**: `execute_with_retry()` in `src/client/helpers.rs` handles retry logic
|
||||||
|
|
||||||
### Testing Strategy
|
### Testing Strategy
|
||||||
|
|
||||||
- **Mock Tests**: Two test suites using mockito for HTTP mocking
|
- **Mock Tests**: Two test suites using mockito for HTTP mocking
|
||||||
|
@ -114,6 +127,8 @@ All client methods follow this pattern:
|
||||||
|
|
||||||
1. **Mockito Header Encoding**: Always URL-encode headers containing newlines/special characters
|
1. **Mockito Header Encoding**: Always URL-encode headers containing newlines/special characters
|
||||||
2. **API Version Selection**: Some endpoints are V4-only and will return errors on V3
|
2. **API Version Selection**: Some endpoints are V4-only and will return errors on V3
|
||||||
|
3. **Rate Limiting**: Client automatically retries 429 responses; disable with `set_max_retries(0)` if manual handling
|
||||||
|
needed
|
||||||
4. **Platform Filters**: Only certain values are valid (All, Client, E3, E5)
|
4. **Platform Filters**: Only certain values are valid (All, Client, E3, E5)
|
||||||
5. **Test Data**: PCK certificate endpoints require valid platform data and often need subscription keys
|
5. **Test Data**: PCK certificate endpoints require valid platform data and often need subscription keys
|
||||||
6. **Issuer Chain Validation**: Always check that `issuer_chain` is non-empty - it's critical for signature verification
|
6. **Issuer Chain Validation**: Always check that `issuer_chain` is non-empty - it's critical for signature verification
|
||||||
|
|
91
crates/intel-dcap-api/examples/handle_rate_limit.rs
Normal file
91
crates/intel-dcap-api/examples/handle_rate_limit.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (c) 2025 Matter Labs
|
||||||
|
|
||||||
|
//! Example demonstrating automatic rate limit handling
|
||||||
|
//!
|
||||||
|
//! The Intel DCAP API client now automatically handles 429 Too Many Requests responses
|
||||||
|
//! by retrying up to 3 times by default. This example shows how to configure the retry
|
||||||
|
//! behavior and handle cases where all retries are exhausted.
|
||||||
|
|
||||||
|
use intel_dcap_api::{ApiClient, IntelApiError};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Create API client with default settings (3 retries)
|
||||||
|
let mut client = ApiClient::new()?;
|
||||||
|
|
||||||
|
println!("Example 1: Default behavior (automatic retries)");
|
||||||
|
println!("================================================");
|
||||||
|
|
||||||
|
// Example FMSPC value
|
||||||
|
let fmspc = "00606A000000";
|
||||||
|
|
||||||
|
// The client will automatically retry up to 3 times if rate limited
|
||||||
|
match client.get_sgx_tcb_info(fmspc, None, None).await {
|
||||||
|
Ok(tcb_info) => {
|
||||||
|
println!("✓ Successfully retrieved TCB info");
|
||||||
|
println!(
|
||||||
|
" TCB Info JSON length: {} bytes",
|
||||||
|
tcb_info.tcb_info_json.len()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" Issuer Chain length: {} bytes",
|
||||||
|
tcb_info.issuer_chain.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(IntelApiError::TooManyRequests {
|
||||||
|
request_id,
|
||||||
|
retry_after,
|
||||||
|
}) => {
|
||||||
|
println!("✗ Rate limited even after 3 automatic retries");
|
||||||
|
println!(" Request ID: {}", request_id);
|
||||||
|
println!(" Last retry-after was: {} seconds", retry_after);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("✗ Other error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nExample 2: Custom retry configuration");
|
||||||
|
println!("=====================================");
|
||||||
|
|
||||||
|
// Configure client to retry up to 5 times
|
||||||
|
client.set_max_retries(5);
|
||||||
|
println!("Set max retries to 5");
|
||||||
|
|
||||||
|
match client.get_sgx_tcb_info(fmspc, None, None).await {
|
||||||
|
Ok(_) => println!("✓ Request succeeded"),
|
||||||
|
Err(IntelApiError::TooManyRequests { .. }) => {
|
||||||
|
println!("✗ Still rate limited after 5 retries")
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("✗ Error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nExample 3: Disable automatic retries");
|
||||||
|
println!("====================================");
|
||||||
|
|
||||||
|
// Disable automatic retries
|
||||||
|
client.set_max_retries(0);
|
||||||
|
println!("Disabled automatic retries");
|
||||||
|
|
||||||
|
match client.get_sgx_tcb_info(fmspc, None, None).await {
|
||||||
|
Ok(_) => println!("✓ Request succeeded on first attempt"),
|
||||||
|
Err(IntelApiError::TooManyRequests {
|
||||||
|
request_id,
|
||||||
|
retry_after,
|
||||||
|
}) => {
|
||||||
|
println!("✗ Rate limited (no automatic retry)");
|
||||||
|
println!(" Request ID: {}", request_id);
|
||||||
|
println!(" Retry after: {} seconds", retry_after);
|
||||||
|
println!(" You would need to implement manual retry logic here");
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("✗ Error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nNote: The client handles rate limiting automatically!");
|
||||||
|
println!("You only need to handle TooManyRequests errors if:");
|
||||||
|
println!("- You disable automatic retries (set_max_retries(0))");
|
||||||
|
println!("- All automatic retries are exhausted");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ impl ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
let request_builder = self.client.get(url);
|
let request_builder = self.client.get(url);
|
||||||
let response = request_builder.send().await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
let fmspcs_json = response.text().await?;
|
let fmspcs_json = response.text().await?;
|
||||||
|
|
|
@ -12,6 +12,8 @@ use crate::{
|
||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
impl ApiClient {
|
impl ApiClient {
|
||||||
/// Helper to construct API paths dynamically based on version and technology (SGX/TDX).
|
/// Helper to construct API paths dynamically based on version and technology (SGX/TDX).
|
||||||
|
@ -84,7 +86,7 @@ impl ApiClient {
|
||||||
&self,
|
&self,
|
||||||
request_builder: RequestBuilder,
|
request_builder: RequestBuilder,
|
||||||
) -> Result<PckCertificateResponse, IntelApiError> {
|
) -> Result<PckCertificateResponse, IntelApiError> {
|
||||||
let response = request_builder.send().await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
let issuer_chain = self.get_required_header(
|
let issuer_chain = self.get_required_header(
|
||||||
|
@ -109,7 +111,7 @@ impl ApiClient {
|
||||||
&self,
|
&self,
|
||||||
request_builder: RequestBuilder,
|
request_builder: RequestBuilder,
|
||||||
) -> Result<PckCertificatesResponse, IntelApiError> {
|
) -> Result<PckCertificatesResponse, IntelApiError> {
|
||||||
let response = request_builder.send().await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
let issuer_chain = self.get_required_header(
|
let issuer_chain = self.get_required_header(
|
||||||
|
@ -134,7 +136,7 @@ impl ApiClient {
|
||||||
v4_issuer_chain_header: &'static str,
|
v4_issuer_chain_header: &'static str,
|
||||||
v3_issuer_chain_header: Option<&'static str>,
|
v3_issuer_chain_header: Option<&'static str>,
|
||||||
) -> Result<(String, String), IntelApiError> {
|
) -> Result<(String, String), IntelApiError> {
|
||||||
let response = request_builder.send().await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
let issuer_chain =
|
let issuer_chain =
|
||||||
|
@ -158,7 +160,7 @@ impl ApiClient {
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let response = builder_clone.send().await?;
|
let response = self.execute_with_retry(builder_clone).await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
if status == StatusCode::NOT_FOUND || status == StatusCode::GONE {
|
if status == StatusCode::NOT_FOUND || status == StatusCode::GONE {
|
||||||
|
@ -224,4 +226,69 @@ impl ApiClient {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a request with automatic retry logic for rate limiting (429 responses).
|
||||||
|
///
|
||||||
|
/// This method will automatically retry the request up to `max_retries` times
|
||||||
|
/// when receiving a 429 Too Many Requests response, waiting for the duration
|
||||||
|
/// specified in the Retry-After header.
|
||||||
|
pub(super) async fn execute_with_retry(
|
||||||
|
&self,
|
||||||
|
request_builder: RequestBuilder,
|
||||||
|
) -> Result<Response, IntelApiError> {
|
||||||
|
let mut retries = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Clone the request builder for retry attempts
|
||||||
|
let builder = request_builder.try_clone().ok_or_else(|| {
|
||||||
|
IntelApiError::Io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Failed to clone request builder for retry",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = builder.send().await?;
|
||||||
|
let status = response.status();
|
||||||
|
|
||||||
|
if status != StatusCode::TOO_MANY_REQUESTS {
|
||||||
|
// Not a rate limit error, return the response
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 429 Too Many Requests
|
||||||
|
if retries >= self.max_retries {
|
||||||
|
// No more retries, return the error
|
||||||
|
let request_id = response
|
||||||
|
.headers()
|
||||||
|
.get("Request-ID")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("Unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let retry_after = response
|
||||||
|
.headers()
|
||||||
|
.get("Retry-After")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|v| v.parse::<u64>().ok())
|
||||||
|
.unwrap_or(60);
|
||||||
|
|
||||||
|
return Err(IntelApiError::TooManyRequests {
|
||||||
|
request_id,
|
||||||
|
retry_after,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Retry-After header
|
||||||
|
let retry_after_secs = response
|
||||||
|
.headers()
|
||||||
|
.get("Retry-After")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|v| v.parse::<u64>().ok())
|
||||||
|
.unwrap_or(60); // Default to 60 seconds
|
||||||
|
|
||||||
|
// Wait before retrying
|
||||||
|
sleep(Duration::from_secs(retry_after_secs)).await;
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@ pub struct ApiClient {
|
||||||
client: Client,
|
client: Client,
|
||||||
base_url: Url,
|
base_url: Url,
|
||||||
api_version: ApiVersion,
|
api_version: ApiVersion,
|
||||||
|
/// Maximum number of automatic retries for rate-limited requests (429 responses)
|
||||||
|
max_retries: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiClient {
|
impl ApiClient {
|
||||||
|
@ -114,6 +116,20 @@ impl ApiClient {
|
||||||
.build()?,
|
.build()?,
|
||||||
base_url: base_url.into_url()?,
|
base_url: base_url.into_url()?,
|
||||||
api_version,
|
api_version,
|
||||||
|
max_retries: 3, // Default to 3 retries
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum number of automatic retries for rate-limited requests.
|
||||||
|
///
|
||||||
|
/// When the API returns a 429 (Too Many Requests) response, the client will
|
||||||
|
/// automatically wait for the duration specified in the Retry-After header
|
||||||
|
/// and retry the request up to this many times.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `max_retries` - Maximum number of retries (0 disables automatic retries)
|
||||||
|
pub fn set_max_retries(&mut self, max_retries: u32) {
|
||||||
|
self.max_retries = max_retries;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
let request_builder = self.client.get(url);
|
let request_builder = self.client.get(url);
|
||||||
let response = request_builder.send().await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
let issuer_chain = self.get_required_header(
|
let issuer_chain = self.get_required_header(
|
||||||
|
|
|
@ -36,13 +36,13 @@ impl ApiClient {
|
||||||
let path = self.build_api_path("sgx", "registration", "platform")?;
|
let path = self.build_api_path("sgx", "registration", "platform")?;
|
||||||
let url = self.base_url.join(&path)?;
|
let url = self.base_url.join(&path)?;
|
||||||
|
|
||||||
let response = self
|
let request_builder = self
|
||||||
.client
|
.client
|
||||||
.post(url)
|
.post(url)
|
||||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||||
.body(platform_manifest)
|
.body(platform_manifest);
|
||||||
.send()
|
|
||||||
.await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
|
|
||||||
let response = check_status(response, &[StatusCode::CREATED]).await?;
|
let response = check_status(response, &[StatusCode::CREATED]).await?;
|
||||||
|
|
||||||
|
@ -81,14 +81,14 @@ impl ApiClient {
|
||||||
let path = self.build_api_path("sgx", "registration", "package")?;
|
let path = self.build_api_path("sgx", "registration", "package")?;
|
||||||
let url = self.base_url.join(&path)?;
|
let url = self.base_url.join(&path)?;
|
||||||
|
|
||||||
let response = self
|
let request_builder = self
|
||||||
.client
|
.client
|
||||||
.post(url)
|
.post(url)
|
||||||
.header("Ocp-Apim-Subscription-Key", subscription_key)
|
.header("Ocp-Apim-Subscription-Key", subscription_key)
|
||||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||||
.body(add_package_request)
|
.body(add_package_request);
|
||||||
.send()
|
|
||||||
.await?;
|
let response = self.execute_with_retry(request_builder).await?;
|
||||||
|
|
||||||
let response = check_status(response, &[StatusCode::OK]).await?;
|
let response = check_status(response, &[StatusCode::OK]).await?;
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,40 @@ pub enum IntelApiError {
|
||||||
/// Indicates an invalid parameter was provided.
|
/// Indicates an invalid parameter was provided.
|
||||||
#[error("Invalid parameter value: {0}")]
|
#[error("Invalid parameter value: {0}")]
|
||||||
InvalidParameter(&'static str),
|
InvalidParameter(&'static str),
|
||||||
|
|
||||||
|
/// Indicates that the API rate limit has been exceeded (HTTP 429).
|
||||||
|
///
|
||||||
|
/// This error is returned after the client has exhausted all automatic retry attempts
|
||||||
|
/// for a rate-limited request. The `retry_after` field contains the number of seconds
|
||||||
|
/// that was specified in the last Retry-After header. By default, the client automatically
|
||||||
|
/// retries rate-limited requests up to 3 times.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use intel_dcap_api::{ApiClient, IntelApiError};
|
||||||
|
///
|
||||||
|
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut client = ApiClient::new()?;
|
||||||
|
/// client.set_max_retries(0); // Disable automatic retries
|
||||||
|
///
|
||||||
|
/// match client.get_sgx_tcb_info("00606A000000", None, None).await {
|
||||||
|
/// Ok(tcb_info) => println!("Success"),
|
||||||
|
/// Err(IntelApiError::TooManyRequests { request_id, retry_after }) => {
|
||||||
|
/// println!("Rate limited after all retries. Last retry-after was {} seconds.", retry_after);
|
||||||
|
/// }
|
||||||
|
/// Err(e) => eprintln!("Other error: {}", e),
|
||||||
|
/// }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[error("Too many requests. Retry after {retry_after} seconds")]
|
||||||
|
TooManyRequests {
|
||||||
|
/// The unique request identifier for tracing.
|
||||||
|
request_id: String,
|
||||||
|
/// Number of seconds to wait before retrying, from Retry-After header.
|
||||||
|
retry_after: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts common API error details from response headers.
|
/// Extracts common API error details from response headers.
|
||||||
|
@ -92,6 +126,27 @@ pub(crate) async fn check_status(
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
if expected_statuses.contains(&status) {
|
if expected_statuses.contains(&status) {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
} else if status == StatusCode::TOO_MANY_REQUESTS {
|
||||||
|
// Handle 429 Too Many Requests with Retry-After header
|
||||||
|
let request_id = response
|
||||||
|
.headers()
|
||||||
|
.get("Request-ID")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("Unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Parse Retry-After header (can be in seconds or HTTP date format)
|
||||||
|
let retry_after = response
|
||||||
|
.headers()
|
||||||
|
.get("Retry-After")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|v| v.parse::<u64>().ok())
|
||||||
|
.unwrap_or(60); // Default to 60 seconds if header is missing or invalid
|
||||||
|
|
||||||
|
Err(IntelApiError::TooManyRequests {
|
||||||
|
request_id,
|
||||||
|
retry_after,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
let (request_id, error_code, error_message) = extract_api_error_details(&response);
|
let (request_id, error_code, error_message) = extract_api_error_details(&response);
|
||||||
Err(IntelApiError::ApiError {
|
Err(IntelApiError::ApiError {
|
||||||
|
|
|
@ -8,6 +8,14 @@
|
||||||
//!
|
//!
|
||||||
//! Create an [`ApiClient`] to interface with the Intel API.
|
//! Create an [`ApiClient`] to interface with the Intel API.
|
||||||
//!
|
//!
|
||||||
|
//! # Rate Limiting
|
||||||
|
//!
|
||||||
|
//! The Intel API implements rate limiting and may return HTTP 429 (Too Many Requests) responses.
|
||||||
|
//! This client automatically handles rate limiting by retrying requests up to 3 times by default,
|
||||||
|
//! waiting for the duration specified in the `Retry-After` header. You can configure the retry
|
||||||
|
//! behavior using [`ApiClient::set_max_retries`]. If all retries are exhausted, the client
|
||||||
|
//! returns an [`IntelApiError::TooManyRequests`] error.
|
||||||
|
//!
|
||||||
//! Example
|
//! Example
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use intel_dcap_api::{ApiClient, IntelApiError, TcbInfoResponse};
|
//! use intel_dcap_api::{ApiClient, IntelApiError, TcbInfoResponse};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue