Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Handling

Robust error handling patterns for handlers.

Basic Pattern

#![allow(unused)]
fn main() {
use rust_edge_gateway_sdk::prelude::*;

#[handler]
pub async fn handle(_ctx: &Context, req: Request) -> Result<Response, HandlerError> {
    let data = process_request(&req)?;
    Ok(Response::ok(data))
}

fn process_request(req: &Request) -> Result<JsonValue, HandlerError> {
    let input: CreateUser = req.json()
        .map_err(|e| HandlerError::ValidationError(e.to_string()))?;

    // Process and return result
    Ok(json!({"id": "123", "name": input.name}))
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
}
}

Error Types and Status Codes

#![allow(unused)]
fn main() {
fn process(req: &Request) -> Result<JsonValue, HandlerError> {
    // 400 Bad Request - Invalid input
    if req.body.is_none() {
        return Err(HandlerError::ValidationError("Body required".into()));
    }
    
    // 401 Unauthorized - Missing/invalid auth
    if req.header("Authorization").is_none() {
        return Err(HandlerError::Unauthorized("Token required".into()));
    }
    
    // 404 Not Found - Resource doesn't exist
    let user = find_user("123");
    if user.is_none() {
        return Err(HandlerError::NotFound("User not found".into()));
    }
    
    // 503 Service Unavailable - Backend down
    if !database_available() {
        return Err(HandlerError::ServiceUnavailable("Database down".into()));
    }
    
    // 500 Internal Error - Unexpected error
    if something_broke() {
        return Err(HandlerError::Internal("Unexpected error".into()));
    }
    
    Ok(json!({"status": "ok"}))
}
}

Input Validation

#![allow(unused)]
fn main() {
#[derive(Deserialize)]
struct RegisterUser {
    email: String,
    password: String,
    name: String,
}

fn validate_input(input: &RegisterUser) -> Result<(), HandlerError> {
    // Email validation
    if !input.email.contains('@') {
        return Err(HandlerError::ValidationError(
            "Invalid email format".into()
        ));
    }

    // Password validation
    if input.password.len() < 8 {
        return Err(HandlerError::ValidationError(
            "Password must be at least 8 characters".into()
        ));
    }

    // Name validation
    if input.name.trim().is_empty() {
        return Err(HandlerError::ValidationError(
            "Name is required".into()
        ));
    }

    Ok(())
}

#[handler]
pub async fn handle(_ctx: &Context, req: Request) -> Result<Response, HandlerError> {
    let input: RegisterUser = req.json()?;
    validate_input(&input)?;
    Ok(Response::created(json!({"email": input.email})))
}
}

Custom Error Type

#![allow(unused)]
fn main() {
enum AppError {
    UserNotFound(String),
    EmailTaken(String),
    InvalidCredentials,
    RateLimited,
    DatabaseError(String),
}

impl From<AppError> for HandlerError {
    fn from(e: AppError) -> Self {
        match e {
            AppError::UserNotFound(id) => 
                HandlerError::NotFound(format!("User {} not found", id)),
            AppError::EmailTaken(email) => 
                HandlerError::ValidationError(format!("Email {} already registered", email)),
            AppError::InvalidCredentials => 
                HandlerError::Unauthorized("Invalid email or password".into()),
            AppError::RateLimited => 
                HandlerError::Internal("Rate limit exceeded".into()),
            AppError::DatabaseError(msg) => 
                HandlerError::DatabaseError(msg),
        }
    }
}

fn process(req: &Request) -> Result<JsonValue, AppError> {
    // Business logic with custom errors
    Err(AppError::InvalidCredentials)
}

#[handler]
pub async fn handle(_ctx: &Context, req: Request) -> Result<Response, HandlerError> {
    let data = process(&req)?;
    Ok(Response::ok(data))
}
}

Logging Errors

Always log errors for debugging:

#![allow(unused)]
fn main() {
#[handler]
pub async fn handle(_ctx: &Context, req: Request) -> Result<Response, HandlerError> {
    match process(&req) {
        Ok(data) => Ok(Response::ok(data)),
        Err(e) => {
            // Log with request ID for tracing
            eprintln!("[{}] Error: {}", req.request_id, e);

            // Log stack trace for internal errors
            if matches!(e, HandlerError::Internal(_) | HandlerError::DatabaseError(_)) {
                eprintln!("[{}] Request path: {}", req.request_id, req.path);
                eprintln!("[{}] Request body: {:?}", req.request_id, req.body);
            }

            Err(e)
        }
    }
}
}

Graceful Degradation

Handle service failures gracefully:

#![allow(unused)]
fn main() {
#[handler]
pub async fn handle(ctx: &Context, _req: Request) -> Result<Response, HandlerError> {
    let cache = ctx.cache("redis").await;
    let db = ctx.database("main").await?;

    // Try cache first (non-fatal if unavailable)
    if let Ok(cache) = cache {
        match cache.get("data:key").await {
            Ok(Some(data)) => {
                return Ok(Response::ok(json!({"source": "cache", "data": data})));
            }
            Ok(None) => { /* Cache miss, continue */ }
            Err(e) => {
                // Log but don't fail - Redis being down shouldn't break the app
                eprintln!("Redis error (non-fatal): {}", e);
            }
        }
    }

    // Fallback to database
    match db.query("SELECT * FROM data", &[]).await {
        Ok(result) => Ok(Response::ok(json!({"source": "db", "data": result}))),
        Err(e) => {
            eprintln!("Database error: {}", e);
            Ok(Response::json(503, json!({
                "error": "Service temporarily unavailable",
                "retry_after": 5,
            })))
        }
    }
}
}