1use std::fmt;
2
3pub type Result<T, E = Error> = std::result::Result<T, E>;
5
6pub enum Error {
11 Io(std::io::Error),
13 Config { file: String, line: usize, message: String },
15 Json { line: usize, column: usize, message: String },
17 Template { file: String, line: usize, column: usize, message: String },
22 Protocol(String),
24 Unavailable(String),
31 Message(String),
33}
34
35impl Error {
36 pub fn msg(message: impl Into<String>) -> Self {
37 Error::Message(message.into())
38 }
39
40 pub fn status(&self) -> u16 {
42 match self {
43 Error::Protocol(_) => 400,
44 Error::Unavailable(_) => 503,
47 _ => 500,
48 }
49 }
50
51 pub fn title(&self) -> &'static str {
53 match self {
54 Error::Io(_) => "I/O Error",
55 Error::Config { .. } => "Configuration Error",
56 Error::Json { .. } => "JSON Error",
57 Error::Template { .. } => "Template Error",
58 Error::Protocol(_) => "Protocol Error",
59 Error::Unavailable(_) => "Dependency Unavailable",
60 Error::Message(_) => "Application Error",
61 }
62 }
63
64 pub fn hint(&self) -> Option<String> {
66 match self {
67 Error::Config { file, .. } => Some(format!(
68 "Check the syntax of `{file}`. Each line should look like `KEY=value`."
69 )),
70 Error::Json { .. } => {
71 Some("Verify the payload is valid JSON — trailing commas are not allowed.".into())
72 }
73 Error::Template { file, line, .. } => Some(format!(
74 "Look at `{file}` around line {line}. Every `@if`, `@foreach` and `@section` \
75 needs its matching `@end...`."
76 )),
77 Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
78 Some("The file does not exist. Did you run `rustlavel new` in this directory?".into())
79 }
80 Error::Io(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
81 Some("That port is already in use. Try `rustlavel serve --port 8001`.".into())
82 }
83 _ => None,
84 }
85 }
86}
87
88impl fmt::Debug for Error {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 fmt::Display::fmt(self, f)
106 }
107}
108
109impl fmt::Display for Error {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Error::Io(e) => write!(f, "{e}"),
113 Error::Config { file, line, message } => {
114 write!(f, "{file}:{line}: {message}")
115 }
116 Error::Json { line, column, message } => {
117 write!(f, "invalid JSON at line {line}, column {column}: {message}")
118 }
119 Error::Template { file, line, column, message } => {
120 write!(f, "{file}:{line}:{column}: {message}")
121 }
122 Error::Protocol(m) => write!(f, "malformed request: {m}"),
123 Error::Unavailable(m) => f.write_str(m),
124 Error::Message(m) => f.write_str(m),
125 }
126 }
127}
128
129impl std::error::Error for Error {
130 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
131 match self {
132 Error::Io(e) => Some(e),
133 _ => None,
134 }
135 }
136}
137
138impl From<std::io::Error> for Error {
139 fn from(e: std::io::Error) -> Self {
140 Error::Io(e)
141 }
142}
143
144impl From<String> for Error {
145 fn from(s: String) -> Self {
146 Error::Message(s)
147 }
148}
149
150impl From<&str> for Error {
151 fn from(s: &str) -> Self {
152 Error::Message(s.to_string())
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
164 fn the_debug_form_is_the_sentence_not_the_struct() {
165 let error = Error::Io(std::io::Error::new(
166 std::io::ErrorKind::AddrInUse,
167 "Address already in use",
168 ));
169 let shown = format!("{error:?}");
170 assert_eq!(shown, format!("{error}"));
171 assert!(!shown.contains("Io("), "the variant is leaking: {shown}");
172 assert!(!shown.contains("Os {"), "the os struct is leaking: {shown}");
173 assert!(shown.contains("Address already in use"), "{shown}");
174 }
175
176 #[test]
177 fn a_config_error_debugs_to_its_file_and_line() {
178 let error = Error::Config {
179 file: ".env".into(),
180 line: 4,
181 message: "expected KEY=value".into(),
182 };
183 assert_eq!(format!("{error:?}"), ".env:4: expected KEY=value");
184 }
185}