Jump to…
snowinitial commitqoxwzsukwmkx1mo
Matt W1//! Request error handling.
Matt W2//!
Matt W3//! The important rule (spec §9): an unauthorised private repo and a nonexistent
Matt W4//! repo must be indistinguishable. [`AppError::NotFound`] is therefore the only
Matt W5//! way to say "you can't see this" — there is deliberately no `Forbidden`
Matt W6//! variant for repository access.
Matt W7
Matt W8use axum::http::StatusCode;
Matt W9use axum::response::{IntoResponse, Response};
Matt W10
Matt W11#[derive(Debug)]
Matt W12pub enum AppError {
Matt W13 /// Also the response for "exists but you may not see it".
Matt W14 NotFound,
Matt W15 BadRequest(String),
Matt W16 /// Signed in, but lacking a permission that is not itself a secret —
Matt W17 /// e.g. trying to change settings on a repo you can already read.
Matt W18 Forbidden,
Matt W19 /// Not signed in, on a page that requires it.
Matt W20 Unauthorized,
Matt W21 Internal(anyhow::Error),
Matt W22}
Matt W23
Matt W24impl std::fmt::Display for AppError {
Matt W25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Matt W26 match self {
Matt W27 AppError::NotFound => write!(f, "not found"),
Matt W28 AppError::BadRequest(m) => write!(f, "bad request: {m}"),
Matt W29 AppError::Forbidden => write!(f, "forbidden"),
Matt W30 AppError::Unauthorized => write!(f, "unauthorized"),
Matt W31 AppError::Internal(e) => write!(f, "internal error: {e}"),
Matt W32 }
Matt W33 }
Matt W34}
Matt W35
Matt W36impl<E: Into<anyhow::Error>> From<E> for AppError {
Matt W37 fn from(e: E) -> Self {
Matt W38 AppError::Internal(e.into())
Matt W39 }
Matt W40}
Matt W41
Matt W42impl IntoResponse for AppError {
Matt W43 fn into_response(self) -> Response {
Matt W44 let (status, message) = match self {
Matt W45 AppError::NotFound => (StatusCode::NOT_FOUND, "Not found".to_string()),
Matt W46 AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
Matt W47 AppError::Forbidden => (
Matt W48 StatusCode::FORBIDDEN,
Matt W49 "You do not have permission to do that.".to_string(),
Matt W50 ),
Matt W51 AppError::Unauthorized => {
Matt W52 // Send them to sign in rather than rendering a dead end.
Matt W53 return axum::response::Redirect::to("/login").into_response();
Matt W54 }
Matt W55 AppError::Internal(e) => {
Matt W56 // Log the detail; never show it. Internal errors routinely carry
Matt W57 // connection strings and object ids.
Matt W58 tracing::error!("internal error: {e:#}");
Matt W59 // In tests the detail goes to stderr as well. A failing security
Matt W60 // test that can only report "500" is nearly useless, and this
Matt W61 // path is compiled out of every real build.
Matt W62 #[cfg(test)]
Matt W63 eprintln!("internal error: {e:#}");
Matt W64 (
Matt W65 StatusCode::INTERNAL_SERVER_ERROR,
Matt W66 "Something went wrong.".to_string(),
Matt W67 )
Matt W68 }
Matt W69 };
Matt W70
Matt W71 (status, crate::views::layout::bare_error(status, &message)).into_response()
Matt W72 }
Matt W73}
Matt W74
Matt W75pub type AppResult<T> = Result<T, AppError>;

75 lines · Rust