DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Request-Error-Handling

Request Error Handling in Hyperlane

Project Code:https://github.com/hyperlane-dev/hyperlane

Introduction

Robust error handling is a cornerstone of any production-grade HTTP server. The hyperlane framework provides a comprehensive error handling mechanism that allows developers to gracefully manage request-level errors, respond with appropriate HTTP status codes, and maintain server stability even when things go wrong. This article explores the request error handling system in hyperlane, covering the RequestError type, error middleware, and practical patterns for building resilient applications.

Understanding RequestError

In hyperlane, request errors are represented by the RequestError type. When something goes wrong during request processing—whether it's a malformed request, a timeout, or a business logic failure—you can capture and handle these errors through the framework's error handling pipeline.

The key entry points for accessing request error data are:

  • ctx.try_get_request_error_data() — Attempts to retrieve request error data from the current context.
  • #[try_get_request_error_data] — An attribute macro that automatically extracts request error data.
  • #[request_error_data] — A convenience attribute for declaring request error data parameters.

These mechanisms allow you to inspect what went wrong and respond accordingly, rather than letting errors propagate as unhandled exceptions.

The Request Error Middleware

Hyperlane provides a dedicated request_error middleware hook that fires when a request error occurs. This middleware receives a RequestError object and lets you implement custom error handling logic.

Registering an Error Middleware

You can register a request error middleware using the #[request_error] attribute macro. This middleware will be invoked whenever a request error is detected during processing.

#[request_error]
async fn handle_request_error(error: RequestError) {
    // Log the error details
    eprintln!("Request error occurred: {:?}", error);
}
Enter fullscreen mode Exit fullscreen mode

The #[request_error] macro marks a function as a request error handler. When registered with the server, this function will be called automatically whenever a request error is encountered, giving you a centralized place to handle failures.

Error Middleware with Context

You can also access the full server context within your error handling middleware to inspect request details and craft appropriate responses:

#[request_error]
async fn handle_request_error(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        eprintln!("Error data: {:?}", error_data);
    }
}
Enter fullscreen mode Exit fullscreen mode

Retrieving Error Data from Context

When processing a request, you may need to check for error data at various stages. Hyperlane provides multiple ways to access this information.

Using try_get_request_error_data

The try_get_request_error_data() method returns an Option type, making it safe to call even when no error has occurred:

async fn process_request(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        // An error was detected — handle it
        handle_error(error_data).await;
    } else {
        // No error — proceed normally
        process_valid_request(ctx).await;
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the #[try_get_request_error_data] Attribute

For a more declarative approach, you can use the #[try_get_request_error_data] attribute macro to automatically extract error data:

#[try_get_request_error_data]
async fn process_request(ctx: &mut ServerContext, request_error_data: Option<RequestError>) {
    match request_error_data {
        Some(error) => {
            // Handle the error case
            eprintln!("Request error: {:?}", error);
        }
        None => {
            // No error — normal processing
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the #[request_error_data] Attribute

The #[request_error_data] attribute provides a more concise way to declare error data as a parameter:

#[request_error_data]
async fn process_request(ctx: &mut ServerContext, request_error_data: RequestError) {
    // This function is only called when request error data exists
    eprintln!("Handling error: {:?}", request_error_data);
}
Enter fullscreen mode Exit fullscreen mode

Practical Error Handling Patterns

Pattern 1: Centralized Error Response

One common pattern is to create a centralized error handler that maps error types to appropriate HTTP responses:

#[request_error]
async fn centralized_error_handler(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        let response = ctx.get_mut_response();
        response.set_status_code(500);
        response.set_body("Internal Server Error".to_string());
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Error Logging and Monitoring

You can use request error middleware to feed errors into your logging and monitoring systems:

#[request_error]
async fn error_logging_middleware(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        let request = ctx.get_request();
        let path = request.get_path();
        let method = request.get_method();

        eprintln!(
            "Error on {} {}: {:?}",
            method, path, error_data
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Graceful Degradation

For applications that need to remain available even when certain operations fail, you can implement graceful degradation:

#[request_error]
async fn graceful_degradation_handler(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        let response = ctx.get_mut_response();
        // Return a fallback response instead of failing completely
        response.set_status_code(200);
        response.set_body("Fallback response".to_string());
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Differentiated Error Responses

Different error types may require different responses. You can inspect the error data and respond accordingly:

#[request_error]
async fn differentiated_error_handler(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        let response = ctx.get_mut_response();

        // Set appropriate status code and message based on error type
        response.set_status_code(400);
        response.set_header("Content-Type", "application/json");
        response.set_body(r#"{"error": "Bad Request", "message": "Invalid request data"}"#.to_string());
    }
}
Enter fullscreen mode Exit fullscreen mode

Combining Error Handling with Other Middleware

Request error handling in hyperlane works seamlessly alongside other middleware types. You can combine request_error middleware with request_middleware and response_middleware to build a complete request processing pipeline:

// Pre-request validation
#[request_middleware]
async fn validate_request(ctx: &mut ServerContext) {
    let request = ctx.get_request();
    if request.get_headers().is_empty() {
        // This could trigger the error handler
    }
}

// Error handling
#[request_error]
async fn handle_errors(ctx: &mut ServerContext) {
    if let Some(error_data) = ctx.try_get_request_error_data() {
        eprintln!("Caught error: {:?}", error_data);
        let response = ctx.get_mut_response();
        response.set_status_code(500);
        response.set_body("An error occurred".to_string());
    }
}

// Post-response processing
#[response_middleware]
async fn add_error_headers(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.add_header("X-Error-Handling", "enabled");
}
Enter fullscreen mode Exit fullscreen mode

Error Handling in Route Handlers

Within individual route handlers, you can also check for request error data and respond appropriately:

#[route("/api/data")]
async fn get_data(ctx: &mut ServerContext) {
    // Check for errors before processing
    if let Some(error_data) = ctx.try_get_request_error_data() {
        let response = ctx.get_mut_response();
        response.set_status_code(400);
        response.set_body("Invalid request".to_string());
        return;
    }

    // Normal processing
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("{\"data\": \"success\"}".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Always handle errors explicitly: Never let request errors go unhandled. Use #[request_error] middleware to catch and respond to errors.

  2. Log errors for debugging: Use the error middleware to log detailed error information, including request path, method, and error details.

  3. Return appropriate HTTP status codes: Map your errors to the correct HTTP status codes (400 for bad requests, 500 for internal errors, etc.).

  4. Don't expose internal details: In production, avoid exposing stack traces or internal error details in responses. Log them server-side instead.

  5. Use centralized error handling: Create a single error middleware that handles all request errors consistently, rather than scattering error handling across individual routes.

  6. Test your error paths: Ensure your error handling middleware works correctly by testing with malformed requests, timeouts, and other error scenarios.

Conclusion

Hyperlane's request error handling system provides a flexible and powerful way to manage errors in your HTTP applications. By leveraging RequestError, the #[request_error] middleware, and the various error data retrieval methods, you can build applications that gracefully handle failures and provide meaningful feedback to clients. Whether you need simple error logging or complex error recovery strategies, hyperlane gives you the tools to build resilient servers.


Project Code:https://github.com/hyperlane-dev/hyperlane

Top comments (0)