flowglad 0.1.0

Rust SDK for FlowGlad - Open source billing infrastructure
Documentation
//! Error handling example
//!
//! This example demonstrates how to handle various error scenarios when
//! working with the FlowGlad API:
//! - API errors (404, 400, etc.)
//! - Network errors
//! - Rate limiting
//! - Configuration errors
//!
//! Run this example with:
//! ```bash
//! FLOWGLAD_API_KEY=sk_test_... cargo run --example error_handling
//! ```

use flowglad::types::customer::CreateCustomer;
use flowglad::{Client, Config, Error};
use std::env;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("⚠️  FlowGlad Error Handling Example\n");

    // Example 1: Invalid API key
    println!("1️⃣  Testing invalid API key...");
    let bad_config = Config::new("invalid_key");
    match Client::new(bad_config) {
        Ok(_) => {
            println!("   ⚠️  Client created (validation happens at request time)");
        }
        Err(e) => {
            println!("   ✓ Caught configuration error: {}", e);
        }
    }
    println!();

    // Example 2: API key with invalid characters (fails at client creation)
    println!("2️⃣  Testing API key with invalid characters...");
    let invalid_config = Config::new("invalid\nkey\rwith\tcontrol\x00chars");
    match Client::new(invalid_config) {
        Ok(_) => {
            println!("   ⚠️  Client created unexpectedly");
        }
        Err(e) => {
            println!("   ✓ Caught configuration error: {}", e);
            match e {
                Error::Config(msg) => println!("     Type: Configuration error - {}", msg),
                _ => println!("     Type: {}", e),
            }
        }
    }
    println!();

    // Get valid API key for remaining examples
    let api_key = match env::var("FLOWGLAD_API_KEY") {
        Ok(key) => key,
        Err(_) => {
            println!("⚠️  FLOWGLAD_API_KEY not set. Remaining examples require a valid API key.");
            println!("   Set it with: export FLOWGLAD_API_KEY=sk_test_...");
            return Ok(());
        }
    };

    let config = Config::new(api_key);
    let client = Client::new(config)?;

    // Example 3: 404 Not Found
    println!("3️⃣  Testing 404 Not Found error...");
    match client.customers().get("nonexistent_customer_12345").await {
        Ok(_) => {
            println!("   ⚠️  Unexpectedly found customer");
        }
        Err(e) => {
            println!("   ✓ Caught API error: {}", e);
            match e {
                Error::Api {
                    status,
                    message,
                    code,
                } => {
                    println!("     Status: {}", status);
                    println!("     Message: {}", message);
                    if let Some(code) = code {
                        println!("     Code: {}", code);
                    }
                }
                _ => println!("     Type: {}", e),
            }
        }
    }
    println!();

    // Example 4: 400 Bad Request (missing required field)
    println!("4️⃣  Testing validation error...");
    // Note: Our SDK enforces required fields at compile time, so we can't
    // easily trigger a 400 error. This is a feature, not a bug!
    println!("   ✓ SDK prevents invalid requests at compile time");
    println!("     Example: CreateCustomer::new() requires external_id and name");
    println!();

    // Example 5: Custom timeout configuration
    println!("5️⃣  Testing custom timeout...");
    let short_timeout_config = Config::builder()
        .api_key(env::var("FLOWGLAD_API_KEY")?)
        .timeout(Duration::from_millis(1)) // Very short timeout
        .build()?;
    let timeout_client = Client::new(short_timeout_config)?;

    match timeout_client.customers().list().await {
        Ok(_) => {
            println!("   ⚠️  Request succeeded (network is very fast!)");
        }
        Err(e) => {
            println!("   ✓ Caught timeout/network error: {}", e);
            match e {
                Error::Network(net_err) => {
                    if net_err.is_timeout() {
                        println!("     Type: Timeout error");
                    } else if net_err.is_connect() {
                        println!("     Type: Connection error");
                    } else {
                        println!("     Type: Network error - {}", net_err);
                    }
                }
                _ => println!("     Type: {}", e),
            }
        }
    }
    println!();

    // Example 6: Retryable errors
    println!("6️⃣  Automatic retry behavior...");
    println!("   ℹ️  The SDK automatically retries on:");
    println!("     - 500 Internal Server Error");
    println!("     - 502 Bad Gateway");
    println!("     - 503 Service Unavailable");
    println!("     - 504 Gateway Timeout");
    println!("   ℹ️  Uses exponential backoff (100ms, 200ms, 400ms, ...)");
    println!("   ℹ️  Configurable with Config::max_retries()");
    println!();

    // Example 7: Rate limiting
    println!("7️⃣  Rate limiting handling...");
    println!("   ℹ️  When rate limited (429), the SDK returns:");
    println!("     Error::RateLimit {{ retry_after: Option<Duration> }}");
    println!("   ℹ️  The retry_after duration comes from the Retry-After header");
    println!();

    // Example 8: Practical error handling pattern
    println!("8️⃣  Practical error handling pattern...");
    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs();
    let external_id = format!("error_example_{}", timestamp);

    let result = client
        .customers()
        .create(
            CreateCustomer::new(&external_id, "Test User")
                .email(&format!("test+{}@example.com", timestamp)),
        )
        .await;

    match result {
        Ok(customer) => {
            println!("   ✓ Successfully created customer: {}", customer.id);
        }
        Err(Error::Api {
            status,
            message,
            code,
        }) => {
            eprintln!("   ✗ API error ({}): {}", status, message);
            if let Some(code) = code {
                eprintln!("     Error code: {}", code);
            }
            // Handle specific status codes
            match status {
                400 => eprintln!("     Fix: Check your request parameters"),
                401 => eprintln!("     Fix: Check your API key"),
                404 => eprintln!("     Fix: Resource not found"),
                429 => eprintln!("     Fix: Slow down your requests"),
                _ => eprintln!("     Fix: Check FlowGlad status or try again"),
            }
        }
        Err(Error::Network(e)) => {
            eprintln!("   ✗ Network error: {}", e);
            eprintln!("     Fix: Check your internet connection");
        }
        Err(Error::RateLimit { retry_after }) => {
            eprintln!("   ✗ Rate limited!");
            if let Some(duration) = retry_after {
                eprintln!("     Retry after: {:?}", duration);
            }
        }
        Err(Error::Serialization(e)) => {
            eprintln!("   ✗ Serialization error: {}", e);
            eprintln!("     This might indicate an API change or SDK bug");
        }
        Err(Error::Config(e)) => {
            eprintln!("   ✗ Configuration error: {}", e);
            eprintln!("     Fix: Check your Config setup");
        }
        Err(Error::Unknown(e)) => {
            eprintln!("   ✗ Unknown error: {}", e);
        }
        Err(e) => {
            // Catch-all for any future error variants (Error is non-exhaustive)
            eprintln!("   ✗ Unexpected error: {}", e);
        }
    }

    println!("\n✨ Error handling example completed!");
    println!("\n💡 Best Practices:");
    println!("   - Always match on Error variants for proper handling");
    println!("   - Use the Display trait (e.g., `{{}}`) for user-friendly messages");
    println!("   - Use the Debug trait (e.g., `{{:?}}`) for detailed error info");
    println!("   - Configure retries and timeouts based on your use case");
    println!("   - Log errors for debugging but show friendly messages to users");

    Ok(())
}