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    /// Anything raised by application code.
26    Message(String),
27}
28
29impl Error {
30    pub fn msg(message: impl Into<String>) -> Self {
31        Error::Message(message.into())
32    }
33
34    /// The HTTP status this error should surface as when it escapes a handler.
35    pub fn status(&self) -> u16 {
36        match self {
37            Error::Protocol(_) => 400,
38            _ => 500,
39        }
40    }
41
42    /// A short, human title for the dev error page.
43    pub fn title(&self) -> &'static str {
44        match self {
45            Error::Io(_) => "I/O Error",
46            Error::Config { .. } => "Configuration Error",
47            Error::Json { .. } => "JSON Error",
48            Error::Template { .. } => "Template Error",
49            Error::Protocol(_) => "Protocol Error",
50            Error::Message(_) => "Application Error",
51        }
52    }
53
54    /// A suggestion shown on the dev error page — the Ignition touch.
55    pub fn hint(&self) -> Option<String> {
56        match self {
57            Error::Config { file, .. } => Some(format!(
58                "Check the syntax of `{file}`. Each line should look like `KEY=value`."
59            )),
60            Error::Json { .. } => {
61                Some("Verify the payload is valid JSON — trailing commas are not allowed.".into())
62            }
63            Error::Template { file, line, .. } => Some(format!(
64                "Look at `{file}` around line {line}. Every `@if`, `@foreach` and `@section` \
65                 needs its matching `@end...`."
66            )),
67            Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
68                Some("The file does not exist. Did you run `rustlavel new` in this directory?".into())
69            }
70            Error::Io(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
71                Some("That port is already in use. Try `rustlavel serve --port 8001`.".into())
72            }
73            _ => None,
74        }
75    }
76}
77
78impl fmt::Display for Error {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Error::Io(e) => write!(f, "{e}"),
82            Error::Config { file, line, message } => {
83                write!(f, "{file}:{line}: {message}")
84            }
85            Error::Json { line, column, message } => {
86                write!(f, "invalid JSON at line {line}, column {column}: {message}")
87            }
88            Error::Template { file, line, column, message } => {
89                write!(f, "{file}:{line}:{column}: {message}")
90            }
91            Error::Protocol(m) => write!(f, "malformed request: {m}"),
92            Error::Message(m) => f.write_str(m),
93        }
94    }
95}
96
97impl std::error::Error for Error {
98    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99        match self {
100            Error::Io(e) => Some(e),
101            _ => None,
102        }
103    }
104}
105
106impl From<std::io::Error> for Error {
107    fn from(e: std::io::Error) -> Self {
108        Error::Io(e)
109    }
110}
111
112impl From<String> for Error {
113    fn from(s: String) -> Self {
114        Error::Message(s)
115    }
116}
117
118impl From<&str> for Error {
119    fn from(s: &str) -> Self {
120        Error::Message(s.to_string())
121    }
122}