Skip to main content

webdav_server/error/
mod.rs

1use std::num::TryFromIntError;
2use std::time::SystemTimeError;
3
4use axum::response::IntoResponse;
5use hyper::StatusCode;
6
7use crate::api;
8use crate::logger::{Entry, Level, Loggable, logging_enabled};
9use crate::storage;
10use crate::{model, wrap_internal_err};
11
12pub mod internal;
13
14/// The top-level application error.
15///
16/// This is the single error type returned by all route handlers via `crate::Result<T>`.
17/// It acts as the central hub that aggregates errors from every domain in the application
18/// (API layer, storage layer, model layer) through `From` implementations generated by `thiserror`.
19///
20/// ## How errors flow
21///
22/// When a route handler uses the `?` operator, errors bubble up through the following chain:
23///
24/// ```text
25/// model::Error  ──┐
26/// storage::Error ──┼──► Error ──► IntoResponse (sanitized HTTP response)
27/// api::Error   ───┘
28/// ```
29///
30/// The `IntoResponse` implementation here is the **single point of truth** for what the
31/// HTTP client is allowed to see. It logs the full internal error (for the server admin)
32/// and then delegates to each domain's own `IntoResponse` to produce a sanitized response.
33#[derive(thiserror::Error, Debug)]
34pub enum Error {
35    /// A storage layer error, typically a filesystem or CAS transaction failure.
36    #[error(transparent)]
37    StorageError(#[from] storage::Error),
38
39    /// An API layer error, typically a bad request or auth failure from a route handler.
40    #[error("API Error : {}", .0)]
41    ApiError(#[from] api::Error),
42
43    /// A conflict at the application level, such as a duplicate user registration.
44    /// This is distinct from a database uniqueness error — it is raised intentionally
45    /// by business logic in route handlers.
46    #[error("User Conflict")]
47    Conflict(String),
48
49    /// A raw database error that was not caught and converted by the model layer.
50    /// In practice this should be rare, as most DB queries go through `model::Error`.
51    #[error("Database Error : {}", .0)]
52    DatabaseError(#[from] sqlx::Error),
53
54    /// A low-level internal error, typically from integer or time conversions.
55    #[error("Internal Error : {}", .0)]
56    InternalError(#[from] internal::Error),
57
58    /// A model layer error, covering database query results and domain-specific failures
59    /// (e.g., asset not found, uniqueness violations).
60    #[error("Model Error : {}", .0)]
61    ModelError(#[from] model::Error),
62}
63
64wrap_internal_err! {
65    TryFromIntError, SystemTimeError => Error::InternalError
66}
67
68impl IntoResponse for Error {
69    fn into_response(self) -> axum::response::Response {
70        let telemetry = if logging_enabled() {
71            Some(Entry {
72                module: self.log_module(),
73                level: self.log_level(),
74                message: self.to_string(),
75                timestamp_ms: chrono::Utc::now().timestamp_millis(),
76            })
77        } else {
78            None
79        };
80
81        let mut response = match self {
82            // Delegate to each domain's IntoResponse implementation.
83            // They know how to sanitize their own errors.
84            StorageError(e) => e.into_response(),
85            ApiError(e) => e.into_response(),
86            ModelError(e) => e.into_response(),
87
88            // Top-level variants that don't belong to a sub-domain.
89            Conflict(msg) => (StatusCode::CONFLICT, msg).into_response(),
90
91            // These are always sensitive — never expose details to the client.
92            DatabaseError(_) | InternalError(_) => {
93                (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
94                    .into_response()
95            }
96        };
97
98        if let Some(t) = telemetry {
99            response.extensions_mut().insert(t);
100        }
101        response
102    }
103}
104
105use Error::{
106    ApiError, Conflict, DatabaseError, InternalError, ModelError, StorageError,
107};
108
109pub type Result<T> = core::result::Result<T, Error>;
110
111impl Loggable for Error {
112    fn log_level(&self) -> crate::logger::Level {
113        match self {
114            Self::StorageError(e) => e.log_level(),
115            Self::ApiError(e) => e.log_level(),
116            Self::Conflict(_) => Level::Warning,
117            Self::ModelError(e) => e.log_level(),
118            Self::DatabaseError(_) | Self::InternalError(_) => Level::Error,
119        }
120    }
121
122    #[inline]
123    fn log_module(&self) -> crate::logger::Module {
124        match self {
125            Self::StorageError(e) => e.log_module(),
126            Self::ApiError(e) => e.log_module(),
127            Self::ModelError(e) => e.log_module(),
128            _ => crate::logger::Module::Server,
129        }
130    }
131}