Skip to main content

webdav_server/api/
error.rs

1use axum::response::IntoResponse;
2use hyper::{StatusCode, header::InvalidHeaderValue};
3use tokio::io;
4
5use crate::{error::internal, logger::Loggable};
6
7/// Errors that can occur while processing an HTTP API request.
8///
9/// These are split into two categories:
10///
11/// - **Client errors (4xx):** Safe to return directly to the caller. They indicate
12///   the client sent something malformed or unauthorized.
13/// - **Internal errors (5xx):** Must never expose implementation details. The `IntoResponse`
14///   implementation strips these down to a generic message before sending.
15#[derive(Debug, thiserror::Error)]
16pub enum Error {
17    /// The multipart form body was structurally invalid and could not be parsed.
18    #[error("Malformed Multipart found")]
19    MalformedMultipart,
20
21    /// The client sent a request that violates the API contract.
22    /// The inner `String` contains a human-readable explanation safe to send back.
23    #[error("Bad Request")]
24    BadRequest(String),
25
26    /// Reading the request body stream failed mid-transfer.
27    /// This is typically a network issue on the client side.
28    #[error("Stream Read Error")]
29    StreamReadError,
30
31    /// A required field was absent from the request body or headers.
32    #[error("Missing Field")]
33    MissingField,
34
35    /// An I/O error occurred while processing the request, such as writing a
36    /// temporary file to disk. This is an internal failure.
37    #[error("IO Error : {}", .0)]
38    IoError(#[from] io::Error),
39
40    /// The request could not be authenticated. The inner `String` contains
41    /// a message safe to return (e.g., "Missing Header", "Invalid token").
42    #[error("Unauthorized : {}", .0)]
43    Unauthorized(String),
44
45    /// A low-level internal error, typically from integer or time conversions.
46    #[error("Internal Server Error : {}", .0)]
47    Internal(#[from] internal::Error),
48
49    /// The requested resource does not exist or is not accessible to the caller.
50    /// The inner `String` contains a message safe to return to the client.
51    #[error("Not Found")]
52    NotFound(String),
53
54    /// A header value provided in the response could not be parsed.
55    /// This is almost always a bug in the server code, not the client.
56    #[error("Invalid header value : {}", .0)]
57    InvalidHeader(#[from] InvalidHeaderValue),
58}
59
60pub type Result<T> = core::result::Result<T, Error>;
61
62impl IntoResponse for Error {
63    fn into_response(self) -> axum::response::Response {
64        use Error::{
65            BadRequest, Internal, InvalidHeader, IoError, MalformedMultipart,
66            MissingField, NotFound, StreamReadError, Unauthorized,
67        };
68
69        match self {
70            // Client errors: safe to return the message directly.
71            BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
72            Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
73            NotFound(msg) => (StatusCode::NOT_FOUND, msg),
74            MalformedMultipart => (
75                StatusCode::BAD_REQUEST,
76                "Malformed Multipart Payload".into(),
77            ),
78            MissingField => {
79                (StatusCode::BAD_REQUEST, "Missing required field".into())
80            }
81            InvalidHeader(_) => {
82                (StatusCode::BAD_REQUEST, "Invalid Header Value".into())
83            }
84
85            // Internal errors: strip all details before sending.
86            StreamReadError | IoError(_) | Internal(_) => (
87                StatusCode::INTERNAL_SERVER_ERROR,
88                "Internal Server Error".into(),
89            ),
90        }
91        .into_response()
92    }
93}
94
95use crate::logger::{Level, Module};
96impl Loggable for Error {
97    fn log_level(&self) -> Level {
98        use Error::{BadRequest, MissingField, NotFound, Unauthorized};
99        match self {
100            BadRequest(_) | Unauthorized(_) | MissingField => Level::Warning,
101            NotFound(_) => Level::Info,
102            _ => Level::Error,
103        }
104    }
105
106    #[inline]
107    fn log_module(&self) -> Module {
108        Module::Api
109    }
110}