Errors & validation
A single error type, mapped once to the right HTTP status and a consistent JSON body. DB errors are logged but never leak their details to the client.
The error enum #
// errors.rs
#[derive(Debug, thiserror::Error)]
pub enum MemoError {
#[error("Non authentifié")] Unauthorized,
#[error("Introuvable: {0}")] NotFound(String),
#[error("Données invalides: {0}")] Validation(String),
#[error("Erreur base de données")] Database(#[from] sqlx::Error),
#[error("Erreur interne")] Internal(#[from] anyhow::Error),
}
impl IntoResponse for MemoError {
fn into_response(self) -> Response {
let (status, code) = match &self {
MemoError::Unauthorized => (StatusCode::UNAUTHORIZED, "UNAUTHORIZED"),
MemoError::NotFound(_) => (StatusCode::NOT_FOUND, "NOT_FOUND"),
MemoError::Validation(_) => (StatusCode::UNPROCESSABLE_ENTITY, "VALIDATION"),
MemoError::Database(e) => { tracing::error!(error=%e, "db"); (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR") }
MemoError::Internal(e) => { tracing::error!(error=%e, "internal"); (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR") }
};
(status, Json(json!({ "error": code, "message": self.to_string() }))).into_response()
}
}
pub type Result<T> = std::result::Result<T, MemoError>;| Error | Status |
|---|---|
| Unauthorized | 401 |
| NotFound | 404 |
| Validation | 422 |
| Database / Internal | 500 |
Input validation #
#[derive(Deserialize, validator::Validate)]
pub struct CreateNoteDto {
#[validate(length(min = 1, max = 500))]
pub title: String,
pub body: String,
}
// dans le handler create :
dto.validate().map_err(|e| MemoError::Validation(e.to_string()))?;Returning an error — in other languages #
// Rust — via IntoResponse (cf. l'enum ci-dessus)
return (
StatusCode::NOT_FOUND,
Json(json!({ "error": "NOT_FOUND", "message": "Note introuvable" })),
).into_response();<?php
http_response_code(404);
header('Content-Type: application/json');
echo json_encode(['error' => 'NOT_FOUND', 'message' => 'Note introuvable']);
exit;# Python — Flask
return jsonify(error="NOT_FOUND", message="Note introuvable"), 404// Go
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{
"error": "NOT_FOUND", "message": "Note introuvable",
})# Perl — Mojolicious
return $c->render(status => 404,
json => { error => "NOT_FOUND", message => "Note introuvable" });Rule
Always validate before any DB operation; on public routes, don't distinguish "not found" from "unauthorized".