Tutoriel backend — « Memo »

Erreurs & validation

Un seul type d'erreur, mappé une fois vers le bon statut HTTP et un JSON cohérent. Les erreurs DB sont loggées mais ne fuient jamais leur détail au client.

L'enum d'erreur #

// 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>;
ErreurStatut
Unauthorized401
NotFound404
Validation422
Database / Internal500

Validation des entrées #

#[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()))?;

Renvoyer une erreur — dans d'autres langages #

// 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" });
Règle

Toujours valider avant toute opération DB ; ne distinguez pas, sur les routes publiques, « introuvable » de « non autorisé ».