use chipp::{ChippClient, ChippClientError, ChippConfig, ChippMessage, ChippSession, MessageRole};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
println!("🔍 Chipp SDK Error Handling Examples\n");
println!("This example demonstrates various error scenarios and recovery strategies.\n");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 1: Invalid API Key");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
handle_invalid_api_key().await;
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 2: Network Timeout");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
handle_timeout().await;
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 3: Successful Request (with automatic retry on transient errors)");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
handle_successful_request().await;
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 4: Error Recovery with Fallback");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
handle_with_fallback().await;
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("Example 5: Error Propagation with ? Operator");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
if let Err(e) = propagate_errors().await {
println!("❌ Error propagated to main: {}", e);
println!(" Error type: {:?}", classify_error(&e));
}
println!("\n✅ All error handling examples completed!\n");
Ok(())
}
async fn handle_invalid_api_key() {
let config = ChippConfig {
api_key: "invalid-api-key".to_string(),
model: "test-app".to_string(),
max_retries: 2, ..Default::default()
};
let client = ChippClient::new(config).expect("Failed to create client");
let mut session = ChippSession::new();
let messages = vec![ChippMessage {
role: MessageRole::User,
content: "Hello!".to_string(),
}];
match client.chat(&mut session, &messages).await {
Ok(_) => println!("✅ Unexpected success!"),
Err(e) => {
println!("❌ Error: {}", e);
match &e {
ChippClientError::ApiError { status, message } if *status == 401 => {
println!(" → This is an authentication error (401 Unauthorized)");
println!(" → The SDK does NOT retry authentication errors");
println!(" → Action: Check your CHIPP_API_KEY environment variable");
println!(" → Message from API: {}", message);
}
_ => println!(" → Unexpected error type"),
}
}
}
}
async fn handle_timeout() {
let config = ChippConfig {
api_key: "test-key".to_string(),
model: "test-app".to_string(),
timeout: Duration::from_millis(1), max_retries: 2,
initial_retry_delay: Duration::from_millis(10),
..Default::default()
};
let client = ChippClient::new(config).expect("Failed to create client");
let mut session = ChippSession::new();
let messages = vec![ChippMessage {
role: MessageRole::User,
content: "Hello!".to_string(),
}];
println!("⏱️ Attempting request with 1ms timeout (will fail)...");
match client.chat(&mut session, &messages).await {
Ok(_) => println!("✅ Unexpected success!"),
Err(e) => {
println!("❌ Error: {}", e);
match &e {
ChippClientError::MaxRetriesExceeded(retries) => {
println!(" → Max retries ({}) exceeded", retries);
println!(" → The SDK automatically retried timeout errors");
println!(" → Action: Increase timeout or check network connectivity");
}
ChippClientError::HttpError(http_err) if http_err.is_timeout() => {
println!(" → This is a timeout error");
println!(" → The SDK will retry timeout errors automatically");
}
_ => println!(" → Error type: {:?}", classify_error(&e)),
}
}
}
}
async fn handle_successful_request() {
let api_key = std::env::var("CHIPP_API_KEY").unwrap_or_else(|_| {
println!("⚠️ CHIPP_API_KEY not set - skipping successful request example");
String::new()
});
if api_key.is_empty() {
return;
}
let app_name_id = std::env::var("CHIPP_APP_NAME_ID").unwrap_or_else(|_| {
println!("⚠️ CHIPP_APP_NAME_ID not set - skipping successful request example");
String::new()
});
if app_name_id.is_empty() {
return;
}
let config = ChippConfig {
api_key,
model: app_name_id,
max_retries: 3,
..Default::default()
};
let client = ChippClient::new(config).expect("Failed to create client");
let mut session = ChippSession::new();
let messages = vec![ChippMessage {
role: MessageRole::User,
content: "Say 'Hello!' in one word.".to_string(),
}];
println!("📤 Sending request...");
match client.chat(&mut session, &messages).await {
Ok(response) => {
println!("✅ Success!");
println!(" Response: {}", response);
println!(" Session ID: {:?}", session.chat_session_id);
}
Err(e) => {
println!("❌ Error: {}", e);
println!(" Error type: {:?}", classify_error(&e));
}
}
}
async fn handle_with_fallback() {
let config = ChippConfig {
api_key: "invalid-key".to_string(),
model: "test-app".to_string(),
max_retries: 1,
..Default::default()
};
let client = ChippClient::new(config).expect("Failed to create client");
let mut session = ChippSession::new();
let messages = vec![ChippMessage {
role: MessageRole::User,
content: "What's the weather?".to_string(),
}];
println!("📤 Attempting API request...");
let response = client
.chat(&mut session, &messages)
.await
.unwrap_or_else(|e| {
println!("⚠️ API request failed: {}", e);
println!(" → Using fallback response");
"I'm sorry, I'm currently unable to process your request. Please try again later."
.to_string()
});
println!("💬 Final response: {}", response);
}
async fn propagate_errors() -> Result<String, ChippClientError> {
let config = ChippConfig {
api_key: "invalid-key".to_string(),
model: "test-app".to_string(),
..Default::default()
};
let client = ChippClient::new(config)?;
let mut session = ChippSession::new();
let messages = vec![ChippMessage {
role: MessageRole::User,
content: "Hello!".to_string(),
}];
println!("📤 Sending request (will propagate error with ?)...");
let response = client.chat(&mut session, &messages).await?;
println!("✅ Success: {}", response);
Ok(response)
}
fn classify_error(error: &ChippClientError) -> &'static str {
match error {
ChippClientError::HttpError(e) if e.is_timeout() => "Network Timeout (retryable)",
ChippClientError::HttpError(e) if e.is_connect() => "Connection Error (retryable)",
ChippClientError::HttpError(_) => "HTTP Error (retryable)",
ChippClientError::ApiError { status, .. } if *status >= 500 => {
"Server Error 5xx (retryable)"
}
ChippClientError::ApiError { status, .. } if *status == 429 => "Rate Limit 429 (retryable)",
ChippClientError::ApiError { status, .. } if *status >= 400 => {
"Client Error 4xx (NOT retryable)"
}
ChippClientError::ApiError { .. } => "API Error",
ChippClientError::InvalidResponse(_) => "Invalid Response (NOT retryable)",
ChippClientError::StreamError(_) => "Stream Error (NOT retryable)",
ChippClientError::MaxRetriesExceeded(_) => "Max Retries Exceeded",
ChippClientError::ConfigError(_) => "Configuration Error (NOT retryable)",
}
}