Error Handling
Error Handling
Rusta provides flexible error handling through Http response helpers, ErrorResponse variants, and custom IntoResponse implementations.
Built-in Error Responses
Simple String Errors
use rusta::Http;
#[get("/users/:id")]pub async fn get_user(&self, Path(id): Path<String>) -> Response { match self.svc.find(&id).await { Some(user) => Http::json(user), None => Http::not_found("User not found"), }}| Method | Status | Use Case |
|---|---|---|
Http::not_found(msg) | 404 | Resource doesn’t exist |
Http::unauthorized(msg) | 401 | Missing/invalid auth |
Http::forbidden(msg) | 403 | Authenticated but not allowed |
Http::bad_request(msg) | 400 | Invalid input |
Http::internal_error(msg) | 500 | Server error |
Structured Errors with ErrorObject
use rusta::{Http, ErrorObject};use serde_json::json;
Http::bad_request(ErrorObject(json!({ "code": "VALIDATION_ERROR", "fields": ["email", "password"],})))Response:
{ "code": "VALIDATION_ERROR", "fields": ["email", "password"]}Flexible Errors with Http::error
Http::error(422, ErrorObject(UnprocessableEntity { reason: "Email already registered", field: "email",}))Custom Error Types
Implement IntoResponse for domain-specific errors:
use axum::{response::{IntoResponse, Json}, http::StatusCode};use rusta::ErrorResponse;use serde_json::json;use thiserror::Error;
#[derive(Debug, thiserror::Error)]pub enum AppError { #[error("User not found: {0}")] NotFound(String),
#[error("Unauthorized: {0}")] Unauthorized(String),
#[error("Validation failed: {0:?}")] Validation(Vec<String>),
#[error("Database error: {0}")] Database(#[from] sqlx::Error),}
impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error) = match self { AppError::NotFound(msg) => ( StatusCode::NOT_FOUND, ErrorResponse::Message(msg), ), AppError::Unauthorized(msg) => ( StatusCode::UNAUTHORIZED, ErrorResponse::Message(msg), ), AppError::Validation(fields) => ( StatusCode::UNPROCESSABLE_ENTITY, ErrorResponse::Object(json!({ "code": "VALIDATION_ERROR", "fields": fields, })), ), AppError::Database(e) => { tracing::error!(%e, "Database error"); ( StatusCode::INTERNAL_SERVER_ERROR, ErrorResponse::Message("Internal server error".into()), ) }, };
(status, Json(error.into_json())).into_response() }}Using Custom Errors in Handlers
#[get("/users/:id")]pub async fn get_user(&self, Path(id): Path<String>) -> Result<Response, AppError> { let user = self.svc.find(&id).await .ok_or_else(|| AppError::NotFound(id))?; Ok(Http::json(user))}
#[post("/users")]pub async fn create_user(&self, Json(body): Json<CreateUserDto>) -> Result<Response, AppError> { self.svc.validate(&body)?; let user = self.svc.create(body).await?; Ok(Http::created(user))}Validation Errors
Use validator crate for struct validation:
use validator::Validate;use serde::Deserialize;
#[derive(Deserialize, Validate)]pub struct CreateUserDto { #[validate(email)] pub email: String,
#[validate(length(min = 8))] pub password: String,
#[validate(length(min = 1, max = 50))] pub name: String,}
impl CreateUserDto { pub fn validate(&self) -> Result<(), AppError> { self.validate() .map_err(|e| AppError::Validation( e.field_errors() .into_iter() .flat_map(|(field, errors)| { errors.into_iter().map(move |err| { format!("{}: {}", field, err.message.unwrap_or_default()) }) }) .collect() )) }}Error Response Format Consistency
All error responses follow this structure:
// Simple error{ "error": "User not found" }
// Structured error{ "code": "VALIDATION_ERROR", "fields": ["email", "password"]}Logging Errors
Errors are automatically logged when using the logger middleware. For custom logging:
impl IntoResponse for AppError { fn into_response(self) -> Response { match &self { AppError::Database(e) => tracing::error!(%e, "Database error"), AppError::Validation(fields) => tracing::warn!(?fields, "Validation failed"), _ => tracing::info!(%self, "Handled error"), } // ... rest of implementation }}Best Practices
- Return
Result<Response, AppError>— Makes errors explicit, enables? - Use
thiserror— DeriveErrorwith minimal boilerplate - Log at the right level —
errorfor bugs,warnfor client errors,infofor expected flows - Don’t leak internals — Map database errors to generic “Internal server error”
- Validate early — Use DTO validation before business logic