Skip to main content

mini_static/
error.rs

1use std::fmt;
2
3/// Errors that can occur during static file serving.
4///
5/// This error type is non-exhaustive and may gain new variants in future releases.
6///
7/// # Variants
8///
9/// - `NotFound`: The requested path does not exist.
10/// - `Traversal`: The requested path attempts to escape the server root.
11/// - `Io`: An I/O error occurred (file read, permission denied, etc.).
12/// - `PipelineSetup`: A configured CSS/JS tool's binary is missing from `PATH`,
13///   discovered at server startup (or [`crate::Server::build`]) before any build runs.
14/// - `Build`: A configured build pipeline (CSS/JS tool or asset folder) failed to
15///   produce output.
16/// - `Config`: A builder was given a value it cannot accept, reported at configuration
17///   time rather than per request.
18#[derive(Debug)]
19#[non_exhaustive]
20pub enum StaticError {
21    /// Requested path does not exist.
22    NotFound(String),
23    /// Path traversal attempt detected.
24    Traversal(String),
25    /// I/O error from the filesystem.
26    Io(std::io::Error),
27    /// A configured build-pipeline tool could not be found or validated at startup.
28    PipelineSetup(String),
29    /// A configured build pipeline ran but failed to produce output.
30    Build(String),
31    /// A builder was given a value it cannot accept — reported when the server is
32    /// configured, not when a request arrives.
33    Config(String),
34}
35
36impl fmt::Display for StaticError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            StaticError::NotFound(path) => write!(f, "not found: {path}"),
40            StaticError::Traversal(path) => write!(f, "path traversal denied: {path}"),
41            StaticError::Io(e) => write!(f, "io error: {e}"),
42            StaticError::PipelineSetup(msg) => write!(f, "pipeline setup failed: {msg}"),
43            StaticError::Build(msg) => write!(f, "build failed: {msg}"),
44            StaticError::Config(msg) => write!(f, "invalid configuration: {msg}"),
45        }
46    }
47}
48
49impl std::error::Error for StaticError {
50    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51        match self {
52            StaticError::Io(e) => Some(e),
53            _ => None,
54        }
55    }
56}
57
58impl StaticError {
59    /// Returns a user-safe error message for use in HTTP responses.
60    ///
61    /// Traversal errors return "not found" to avoid leaking information about
62    /// the filesystem structure. I/O errors return "internal server error".
63    pub fn user_message(&self) -> &'static str {
64        match self {
65            StaticError::NotFound(_) | StaticError::Traversal(_) => "not found",
66            StaticError::Io(_)
67            | StaticError::PipelineSetup(_)
68            | StaticError::Build(_)
69            | StaticError::Config(_) => "internal server error",
70        }
71    }
72}
73
74impl From<std::io::Error> for StaticError {
75    fn from(e: std::io::Error) -> Self {
76        StaticError::Io(e)
77    }
78}
79
80#[cfg(test)]
81#[path = "../tests/unit/error.rs"]
82mod tests;