Центральный обработчик ошибок
Волга предоставляет централизованный механизм обработки ошибок, который перехватывает всё ошибки, которые реализуют типаж Errors
, возникающие в обработчиках запросов и middleware. Для этого можно воспользоваться методом map_err
типа App
для регистрации функции, обрабатывающей ошибки.
Функция принимает объект тип Error
и должна вернуть ответ, реализующий типаж IntoResponse
.
Пример:
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::IoError::other("some error")
});
// Регистрируем централизованный обработчик ошибок
app.map_err(|error: Error| async move {
status!(500, "{:?}", error)
});
app.run().await
}
В этом примере мы намеренно создаем обработчик запросов, который выдает ошибку, и определяем обработчик ошибок, который генерирует HTTP-ответ с кодом состояния 500
на основе сообщения об ошибке.
Для удобства структура Error
уже включает поле status
, которое охватывает общие случаи (400, 401, 403, 404 и т. д.), что позволяет использовать макрос следующим образом:
status!(error.status.as_u16(), "{:?}", error)
Фактически, именно так реализован обработчик ошибок по-умолчанию. Если мы удалим метод map_err
, ответ останется неизменным. Однако переопределение пользовательского обработчика ошибок обеспечивает большую гибкость для логгирования и трассировки.
Problem Details
Волга полностью поддерживает формат Problem Details, который предоставляет машиночитаемые сведения об ошибках в ответах HTTP. Это устраняет необходимость определять пользовательские форматы ошибок для API.
Чтобы включить эту возможность, убедитесь, что функция problem-details
активирована в Cargo.toml
вашего приложения:
[dependencies]
volga = { version = "0.5.0", features = ["problem-details"] }
Затем вы можете воспользоваться макросом problem!
в связке с map_err
:
use volga::{App, error::Error, problem};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut app = App::new();
app.map_get("/error", || async {
std::io::IoError::other("some error")
});
// Регистрируем централизованный обработчик ошибок
app.map_err(|error: Error| async move {
let (status, instance, err) = error.into_parts();
problem! {
"status": status.as_u16(),
"detail": (err.to_string()),
"instance": instance
}
});
app.run().await
}
Пример ответа:
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"
}
Значения полей type
и title
вычисляются автоматически на основе статус код, однако их можно переопределить:
problem! {
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "Server Error",
"status": status.as_u16(),
"detail": (err.to_string()),
"instance": instance
}
Кроме того, при необходимости вы можете добавить в тело ответа любую дополнительную информацию:
problem! {
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"details": "Your request parameters didn't validate.",
"instance": "/some/resource/path",
"invalid-params": [
{ "name": "id", "reason": "Must be a positive integer" }
]
};
Итоговый пример вы можете найти здесь.