Skip to main content

rustlavel_core/
error.rs

1use std::fmt;
2
3/// The framework-wide result type.
4pub type Result<T, E = Error> = std::result::Result<T, E>;
5
6/// Every failure the framework itself can produce.
7///
8/// Application errors are expected to convert into this through `From`, which
9/// is what lets a handler return `Result<T, E>` and still be a valid handler.
10#[derive(Debug)]
11pub enum Error {
12    /// A file could not be read or written.
13    Io(std::io::Error),
14    /// A `.env` or config file was malformed.
15    Config { file: String, line: usize, message: String },
16    /// A JSON document could not be parsed.
17    Json { line: usize, column: usize, message: String },
18    /// A template could not be parsed or rendered.
19    ///
20    /// Carries the position because a view is written by hand, often by
21    /// someone who is not the person reading the stack trace.
22    Template { file: String, line: usize, column: usize, message: String },
23    /// A malformed HTTP request reached the parser.
24    Protocol(String),
25    /// A dependency was not tried, because it is known to be failing.
26    ///
27    /// Distinct from a failure on purpose: nothing was sent, so nothing can
28    /// have had an effect, and a caller may safely fall back to a cache, a
29    /// default, or a degraded answer. Raised by the circuit breaker in
30    /// `rustlavel-client`.
31    Unavailable(String),
32    /// Anything raised by application code.
33    Message(String),
34}
35
36impl Error {
37    pub fn msg(message: impl Into<String>) -> Self {
38        Error::Message(message.into())
39    }
40
41    /// The HTTP status this error should surface as when it escapes a handler.
42    pub fn status(&self) -> u16 {
43        match self {
44            Error::Protocol(_) => 400,
45            // The upstream this request needed is known to be down, and the
46            // caller may reasonably try again later — which is what 503 means.
47            Error::Unavailable(_) => 503,
48            _ => 500,
49        }
50    }
51
52    /// A short, human title for the dev error page.
53    pub fn title(&self) -> &'static str {
54        match self {
55            Error::Io(_) => "I/O Error",
56            Error::Config { .. } => "Configuration Error",
57            Error::Json { .. } => "JSON Error",
58            Error::Template { .. } => "Template Error",
59            Error::Protocol(_) => "Protocol Error",
60            Error::Unavailable(_) => "Dependency Unavailable",
61            Error::Message(_) => "Application Error",
62        }
63    }
64
65    /// A suggestion shown on the dev error page — the Ignition touch.
66    pub fn hint(&self) -> Option<String> {
67        match self {
68            Error::Config { file, .. } => Some(format!(
69                "Check the syntax of `{file}`. Each line should look like `KEY=value`."
70            )),
71            Error::Json { .. } => {
72                Some("Verify the payload is valid JSON — trailing commas are not allowed.".into())
73            }
74            Error::Template { file, line, .. } => Some(format!(
75                "Look at `{file}` around line {line}. Every `@if`, `@foreach` and `@section` \
76                 needs its matching `@end...`."
77            )),
78            Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
79                Some("The file does not exist. Did you run `rustlavel new` in this directory?".into())
80            }
81            Error::Io(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
82                Some("That port is already in use. Try `rustlavel serve --port 8001`.".into())
83            }
84            _ => None,
85        }
86    }
87}
88
89impl fmt::Display for Error {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Error::Io(e) => write!(f, "{e}"),
93            Error::Config { file, line, message } => {
94                write!(f, "{file}:{line}: {message}")
95            }
96            Error::Json { line, column, message } => {
97                write!(f, "invalid JSON at line {line}, column {column}: {message}")
98            }
99            Error::Template { file, line, column, message } => {
100                write!(f, "{file}:{line}:{column}: {message}")
101            }
102            Error::Protocol(m) => write!(f, "malformed request: {m}"),
103            Error::Unavailable(m) => f.write_str(m),
104            Error::Message(m) => f.write_str(m),
105        }
106    }
107}
108
109impl std::error::Error for Error {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match self {
112            Error::Io(e) => Some(e),
113            _ => None,
114        }
115    }
116}
117
118impl From<std::io::Error> for Error {
119    fn from(e: std::io::Error) -> Self {
120        Error::Io(e)
121    }
122}
123
124impl From<String> for Error {
125    fn from(s: String) -> Self {
126        Error::Message(s)
127    }
128}
129
130impl From<&str> for Error {
131    fn from(s: &str) -> Self {
132        Error::Message(s.to_string())
133    }
134}