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::*;

fn handle(req: Request) -> Response {
    match process_request(&req) {
        Ok(data) => Response::ok(data),
        Err(e) => e.to_response(),
    }
}

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,
}

handler_loop!(handle);
}

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(())
}

fn handle(req: Request) -> Response {
    let input: RegisterUser = match req.json() {
        Ok(i) => i,
        Err(e) => return Response::bad_request(format!("Invalid JSON: {}", e)),
    };
    
    if let Err(e) = validate_input(&input) {
        return e.to_response();
    }
    
    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)
}

fn handle(req: Request) -> Response {
    match process(&req) {
        Ok(data) => Response::ok(data),
        Err(e) => HandlerError::from(e).to_response(),
    }
}
}

Logging Errors

Always log errors for debugging:

#![allow(unused)]
fn main() {
fn handle(req: Request) -> Response {
    match process(&req) {
        Ok(data) => 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);
            }
            
            e.to_response()
        }
    }
}
}

Graceful Degradation

Handle service failures gracefully:

#![allow(unused)]
fn main() {
fn handle(req: Request) -> Response {
    let redis = RedisPool { pool_id: "cache".to_string() };
    let db = DbPool { pool_id: "main".to_string() };
    
    // Try cache first
    let cached = redis.get("data:key");
    
    match cached {
        Ok(Some(data)) => {
            return 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", &[]) {
        Ok(result) => Response::ok(json!({"source": "db", "data": result.rows})),
        Err(e) => {
            eprintln!("Database error: {}", e);
            Response::json(503, json!({
                "error": "Service temporarily unavailable",
                "retry_after": 5,
            }))
        }
    }
}
}