use composio_sdk::{ComposioClient, ComposioError};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Composio Rust SDK - Error Handling Example ===\n");
println!("Example 1: Error Type Matching\n");
println!("Demonstrating how to match on different error types...\n");
let client = match ComposioClient::builder()
.api_key(std::env::var("COMPOSIO_API_KEY").unwrap_or_else(|_| {
eprintln!("Error: COMPOSIO_API_KEY environment variable not set");
std::process::exit(1);
}))
.build()
{
Ok(client) => {
println!("✓ Client initialized successfully\n");
client
}
Err(e) => {
println!("✗ Failed to initialize client:");
demonstrate_error_matching(&e);
return Err(e.into());
}
};
println!("\nExample 2: Handling ApiError with Status Codes\n");
println!("Creating a session and attempting operations that may fail...\n");
let session = client
.create_session("error_demo_user")
.toolkits(vec!["github"])
.send()
.await?;
println!("✓ Session created: {}\n", session.session_id());
println!("2a. Attempting to execute a non-existent tool...");
match session
.execute_tool("NONEXISTENT_TOOL_SLUG", json!({}))
.await
{
Ok(_) => println!(" Unexpected success"),
Err(e) => {
println!("✓ Error caught as expected:");
demonstrate_api_error_handling(&e);
}
}
println!("\n2b. Attempting to execute a tool with invalid arguments...");
match session
.execute_tool(
"GITHUB_CREATE_ISSUE",
json!({
"body": "This will fail due to missing required fields"
}),
)
.await
{
Ok(_) => println!(" Unexpected success"),
Err(e) => {
println!("✓ Error caught as expected:");
demonstrate_api_error_handling(&e);
}
}
println!("\n\nExample 3: Handling NetworkError\n");
println!("Attempting to connect to an invalid base URL...\n");
match ComposioClient::builder()
.api_key("test_key")
.base_url("https://invalid-domain-that-does-not-exist-12345.com")
.build()
{
Ok(invalid_client) => {
match invalid_client
.create_session("test_user")
.send()
.await
{
Ok(_) => println!(" Unexpected success"),
Err(e) => {
println!("✓ Network error caught as expected:");
demonstrate_network_error_handling(&e);
}
}
}
Err(e) => {
println!("✗ Failed to create client: {}", e);
}
}
println!("\n\nExample 4: Handling SerializationError\n");
println!("Demonstrating JSON serialization error handling...\n");
let invalid_json_str = "{invalid json}";
match serde_json::from_str::<serde_json::Value>(invalid_json_str) {
Ok(_) => println!(" Unexpected success"),
Err(e) => {
println!("✓ Serialization error caught:");
let composio_error: ComposioError = e.into();
demonstrate_serialization_error_handling(&composio_error);
}
}
println!("\n\nExample 5: Understanding Retry Behavior\n");
println!("Checking which errors are retryable...\n");
demonstrate_retry_behavior();
println!("\n\nExample 6: Accessing Error Details\n");
println!("Demonstrating how to extract detailed error information...\n");
match session
.execute_tool(
"GITHUB_CREATE_ISSUE",
json!({
"owner": "", "repo": "", "title": "" }),
)
.await
{
Ok(_) => println!(" Unexpected success"),
Err(e) => {
println!("✓ Error with details caught:");
demonstrate_error_details_access(&e);
}
}
println!("\n\nExample 7: Production Error Handling Pattern\n");
println!("Demonstrating production-ready error handling...\n");
match execute_tool_with_production_error_handling(
&session,
"GITHUB_GET_REPOS",
json!({
"owner": "composio",
"type": "public"
}),
)
.await
{
Ok(result) => {
println!("✓ Tool executed successfully");
println!(" Log ID: {}", result.log_id);
}
Err(user_message) => {
println!("✗ Operation failed");
println!(" User message: {}", user_message);
}
}
println!("\n=== Example completed successfully! ===\n");
println!("Key Takeaways:");
println!("1. Always use pattern matching to handle different error types");
println!("2. Check status codes to determine appropriate action");
println!("3. Use suggested_fix for actionable guidance");
println!("4. Include request_id when contacting support");
println!("5. Trust the SDK's automatic retry logic for transient errors");
println!("6. Log errors appropriately in production");
println!("7. Provide user-friendly error messages");
Ok(())
}
fn demonstrate_error_matching(error: &ComposioError) {
println!(" Error Type: {}", match error {
ComposioError::ApiError { .. } => "ApiError",
ComposioError::NetworkError(_) => "NetworkError",
ComposioError::SerializationError(_) => "SerializationError",
ComposioError::InvalidInput(_) => "InvalidInput",
ComposioError::ConfigError(_) => "ConfigError",
});
println!(" Message: {}", error);
if error.is_retryable() {
println!(" ℹ️ This error will be automatically retried by the SDK");
} else {
println!(" ℹ️ This error will NOT be retried (fix required)");
}
}
fn demonstrate_api_error_handling(error: &ComposioError) {
match error {
ComposioError::ApiError {
status,
message,
code,
slug,
request_id,
suggested_fix,
errors,
} => {
println!(" Status Code: {}", status);
println!(" Message: {}", message);
if let Some(code) = code {
println!(" Error Code: {}", code);
}
if let Some(slug) = slug {
println!(" Error Slug: {}", slug);
}
if let Some(request_id) = request_id {
println!(" Request ID: {}", request_id);
println!(" 💡 Include this request ID when contacting support");
}
if let Some(fix) = suggested_fix {
println!(" Suggested Fix: {}", fix);
}
if let Some(errors) = errors {
if !errors.is_empty() {
println!(" Detailed Errors:");
for err in errors {
if let Some(field) = &err.field {
println!(" - Field '{}': {}", field, err.message);
} else {
println!(" - {}", err.message);
}
}
}
}
println!(" Guidance:");
match *status {
400 => println!(" - Bad Request: Check your request parameters"),
401 => println!(" - Unauthorized: Verify your API key"),
403 => println!(" - Forbidden: Check your permissions"),
404 => println!(" - Not Found: Verify the resource exists"),
429 => println!(" - Rate Limited: SDK will automatically retry with backoff"),
500 => println!(" - Internal Server Error: SDK will automatically retry"),
502 => println!(" - Bad Gateway: SDK will automatically retry"),
503 => println!(" - Service Unavailable: SDK will automatically retry"),
504 => println!(" - Gateway Timeout: SDK will automatically retry"),
_ => println!(" - HTTP {}: See error message for details", status),
}
}
_ => {
println!(" Not an ApiError");
}
}
}
fn demonstrate_network_error_handling(error: &ComposioError) {
match error {
ComposioError::NetworkError(e) => {
println!(" Network Error Details: {}", e);
println!(" Possible Causes:");
println!(" - DNS resolution failure");
println!(" - Connection timeout");
println!(" - Connection refused");
println!(" - TLS/SSL error");
println!(" - Network unreachable");
println!(" ℹ️ The SDK will automatically retry this error");
println!(" ℹ️ Check your internet connection and firewall settings");
}
_ => {
println!(" Not a NetworkError");
}
}
}
fn demonstrate_serialization_error_handling(error: &ComposioError) {
match error {
ComposioError::SerializationError(e) => {
println!(" Serialization Error Details: {}", e);
println!(" Possible Causes:");
println!(" - Invalid JSON syntax");
println!(" - Type mismatch (expected string, got number)");
println!(" - Missing required fields");
println!(" - Extra fields not allowed by schema");
println!(" ℹ️ This error will NOT be retried");
println!(" ℹ️ Fix the JSON format and try again");
}
_ => {
println!(" Not a SerializationError");
}
}
}
fn demonstrate_retry_behavior() {
println!("Retryable Errors (SDK will automatically retry):");
let rate_limit_error = ComposioError::ApiError {
status: 429,
message: "Rate limited".to_string(),
code: None,
slug: None,
request_id: None,
suggested_fix: None,
errors: None,
};
println!(" - 429 Rate Limited: {}", rate_limit_error.is_retryable());
for status in [500, 502, 503, 504] {
let error = ComposioError::ApiError {
status,
message: format!("Server error {}", status),
code: None,
slug: None,
request_id: None,
suggested_fix: None,
errors: None,
};
println!(" - {} Server Error: {}", status, error.is_retryable());
}
println!("\nNon-Retryable Errors (fix required):");
for status in [400, 401, 403, 404] {
let error = ComposioError::ApiError {
status,
message: format!("Client error {}", status),
code: None,
slug: None,
request_id: None,
suggested_fix: None,
errors: None,
};
println!(" - {} Client Error: {}", status, error.is_retryable());
}
let invalid_input = ComposioError::InvalidInput("Invalid API key".to_string());
println!(" - InvalidInput: {}", invalid_input.is_retryable());
let config_error = ComposioError::ConfigError("Invalid base URL".to_string());
println!(" - ConfigError: {}", config_error.is_retryable());
println!("\nRetry Configuration:");
println!(" - Max retries: 3 (configurable)");
println!(" - Initial delay: 1 second (configurable)");
println!(" - Max delay: 10 seconds (configurable)");
println!(" - Strategy: Exponential backoff");
}
fn demonstrate_error_details_access(error: &ComposioError) {
match error {
ComposioError::ApiError {
status,
message,
code,
slug,
request_id,
suggested_fix,
errors,
} => {
println!(" Accessing Error Details:");
println!(" status: {}", status);
println!(" message: {}", message);
println!(" code: {:?}", code);
println!(" slug: {:?}", slug);
println!(" request_id: {:?}", request_id);
println!(" suggested_fix: {:?}", suggested_fix);
if let Some(errors) = errors {
println!(" errors: {} field-level error(s)", errors.len());
for (i, err) in errors.iter().enumerate() {
println!(" Error {}:", i + 1);
println!(" field: {:?}", err.field);
println!(" message: {}", err.message);
}
} else {
println!(" errors: None");
}
println!("\n How to Use These Details:");
println!(" - Log 'message' for debugging");
println!(" - Show 'suggested_fix' to users");
println!(" - Include 'request_id' in support tickets");
println!(" - Use 'errors' array for field-level validation feedback");
println!(" - Check 'status' to determine retry strategy");
}
_ => {
println!(" Not an ApiError - limited details available");
println!(" Error: {}", error);
}
}
}
async fn execute_tool_with_production_error_handling(
session: &composio_sdk::Session,
tool_slug: &str,
arguments: serde_json::Value,
) -> Result<composio_sdk::ToolExecutionResponse, String> {
match session.execute_tool(tool_slug, arguments).await {
Ok(response) => {
if let Some(error) = &response.error {
eprintln!("[ERROR] Tool execution failed: {}", error);
eprintln!("[ERROR] Log ID: {}", response.log_id);
return Err(format!(
"The operation failed. Please try again or contact support with log ID: {}",
response.log_id
));
}
Ok(response)
}
Err(e) => {
eprintln!("[ERROR] SDK error: {}", e);
match &e {
ComposioError::ApiError {
status,
message,
request_id,
suggested_fix,
..
} => {
eprintln!("[ERROR] Status: {}", status);
eprintln!("[ERROR] Message: {}", message);
if let Some(req_id) = request_id {
eprintln!("[ERROR] Request ID: {}", req_id);
}
let user_message = match *status {
400 => "Invalid request. Please check your input and try again.".to_string(),
401 => "Authentication failed. Please check your credentials.".to_string(),
403 => "Access denied. You don't have permission for this operation.".to_string(),
404 => "Resource not found. Please verify the tool name.".to_string(),
429 => "Too many requests. Please wait a moment and try again.".to_string(),
500..=599 => "Service temporarily unavailable. Please try again in a few moments.".to_string(),
_ => suggested_fix.clone().unwrap_or_else(|| message.clone()),
};
if let Some(req_id) = request_id {
Err(format!("{} (Request ID: {})", user_message, req_id))
} else {
Err(user_message)
}
}
ComposioError::NetworkError(_) => {
eprintln!("[ERROR] Network error - will retry automatically");
Err("Connection issue. Please check your internet connection.".to_string())
}
ComposioError::SerializationError(_) => {
eprintln!("[ERROR] Serialization error - invalid data format");
Err("Invalid data format. Please contact support.".to_string())
}
ComposioError::InvalidInput(msg) => {
eprintln!("[ERROR] Invalid input: {}", msg);
Err(format!("Invalid input: {}", msg))
}
ComposioError::ConfigError(msg) => {
eprintln!("[ERROR] Configuration error: {}", msg);
Err("Configuration error. Please contact support.".to_string())
}
}
}
}
}