Volga
Home
API Docs
GitHub
  • English
  • Русский
Home
API Docs
GitHub
  • English
  • Русский
  • Home
  • Getting Started

    • Quick Start
    • Route Parameters
    • Query Parameters
    • Route Groups
  • Requests & Responses

    • Headers
    • Handling JSON
    • Handling Form Data
    • Working with Files
    • Cookies
  • Middleware & Infrastructure

    • Basic Middleware
    • Custom Middleware
    • Response Compression
    • Request Decompression
    • CORS (Cross-Origin Resource Sharing)
    • Static Files
    • Rate Limiting
  • Security & Access

    • Authentication and Authorization
  • Reliability & Observability

    • Global Error Handling
    • Tracing & Logging
    • Request cancellation
  • Protocols & Realtime

    • HTTP/1 and HTTP/2
    • HTTPS
    • WebSockets
    • Server-Sent Events (SSE)
  • Advanced Patterns

    • Dependency Injection
    • Custom Handling of HEAD, OPTIONS, and TRACE Methods

Global Error Handling

Volga provides a global error handling mechanism that catches all Error values that may occur in request handlers and middleware. This can be easily achieved using the map_err method of the App to register a function that handles errors.

The function receives an Error object and should return a response that implements the IntoResponse trait.

Example:

use volga::{App, error::Error, status};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();
    
    app.map_get("/error", || async {
        std::io::Error::other("some error")
    });

    // Enabling global error handler
    app.map_err(|error: Error| async move {
        status!(500, "{:?}", error)
    });

    app.run().await
}

In this example, we intentionally create a request handler that produces an error and define an error handler that generates an HTTP response with a 500 status code based on the error message.

For convenience, the Error struct includes a status field that covers common cases (400, 401, 403, 404, etc.), allowing the macro usage to be updated as follows:

status!(error.status.as_u16(), "{:?}", error)

In fact, this is how the default error handler is implemented. If we remove the map_err method, the response remains unchanged. However, defining a custom error handler offers greater flexibility for advanced logging and tracing.

Problem Details

Volga fully supports the Problem Details format, which provides machine-readable error details in HTTP responses. This eliminates the need to define custom error formats for HTTP APIs.

To enable this functionality, ensure that the problem-details feature is activated in your app's Cargo.toml:

[dependencies]
volga = { version = "...", features = ["problem-details"] }

Then, you may return the Problem from request handler:

use volga::{App, error::Problem};
use serde::Serialize;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.map_get("/problem", || async {
        // Always producing the problem

        Problem::new(400)
            .with_detail("Missing Parameter")
            .with_instance("/problem")
            .with_extensions(ValidationError {
                invalid_params: vec![InvalidParam { 
                    name: "id".into(), 
                    reason: "The ID must be provided".into()
                }]
            })
    }); 

    app.run().await
}

#[derive(Default, Serialize)]
struct ValidationError {
    #[serde(rename = "invalid-params")]
    invalid_params: Vec<InvalidParam>,
}

#[derive(Default, Serialize)]
struct InvalidParam {
    name: String,
    reason: String,
}

Example Response:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "Bad Request",
    "status": 400,
    "detail": "Missing Parameter",
    "instance": "/problem",
    "invalid-params": [
        { "name": "id", "reason": "The ID must be provided" }
    ]
}

Global Error Handling With Problem Details

Moreover, you can combine the Problem with map_err by using the use_problem_details() method:

use volga::{App, error::Error};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();
    
    app.map_get("/error", || async {
        // Always producing the error 
        // that will be converted into Problem Details

        std::io::Error::other("some error")
    });

    // Enabling global error handler that produces
    // error responses in Problem details format
    app.use_problem_details();  

    app.run().await
}

Example Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "some error",
    "instance": "/error"
}

The type and title fields are inferred from the status code but can be overridden:

Problem::new(400)
    .with_type("https://tools.ietf.org/html/rfc9110#section-15.6.1")
    .with_title("Server Error");

Additionally, you can include extra details if needed:

Problem::new(400)
    .with_detail("Missing Parameter")
    .with_instance("/problem")
    .with_extensions(ValidationError {
        invalid_params: vec![InvalidParam { 
            name: "id".into(), 
            reason: "The ID must be provided".into()
        }]
    })

or

Problem::new(400)
    .with_detail("Missing Parameter")
    .with_instance("/problem")
    .add_param("reason", "The ID must be provided");

For a complete example, see the full implementation:

  • Global Error Handling.
  • Problem Details
Last Updated: 1/18/26, 6:33 PM
Next
Tracing & Logging