use std::fmt;
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub enum Error {
Io(std::io::Error),
Config { file: String, line: usize, message: String },
Json { line: usize, column: usize, message: String },
Template { file: String, line: usize, column: usize, message: String },
Protocol(String),
Unavailable(String),
Message(String),
}
impl Error {
pub fn msg(message: impl Into<String>) -> Self {
Error::Message(message.into())
}
pub fn status(&self) -> u16 {
match self {
Error::Protocol(_) => 400,
Error::Unavailable(_) => 503,
_ => 500,
}
}
pub fn title(&self) -> &'static str {
match self {
Error::Io(_) => "I/O Error",
Error::Config { .. } => "Configuration Error",
Error::Json { .. } => "JSON Error",
Error::Template { .. } => "Template Error",
Error::Protocol(_) => "Protocol Error",
Error::Unavailable(_) => "Dependency Unavailable",
Error::Message(_) => "Application Error",
}
}
pub fn hint(&self) -> Option<String> {
match self {
Error::Config { file, .. } => Some(format!(
"Check the syntax of `{file}`. Each line should look like `KEY=value`."
)),
Error::Json { .. } => {
Some("Verify the payload is valid JSON — trailing commas are not allowed.".into())
}
Error::Template { file, line, .. } => Some(format!(
"Look at `{file}` around line {line}. Every `@if`, `@foreach` and `@section` \
needs its matching `@end...`."
)),
Error::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
Some("The file does not exist. Did you run `rustlavel new` in this directory?".into())
}
Error::Io(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
Some("That port is already in use. Try `rustlavel serve --port 8001`.".into())
}
_ => None,
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "{e}"),
Error::Config { file, line, message } => {
write!(f, "{file}:{line}: {message}")
}
Error::Json { line, column, message } => {
write!(f, "invalid JSON at line {line}, column {column}: {message}")
}
Error::Template { file, line, column, message } => {
write!(f, "{file}:{line}:{column}: {message}")
}
Error::Protocol(m) => write!(f, "malformed request: {m}"),
Error::Unavailable(m) => f.write_str(m),
Error::Message(m) => f.write_str(m),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::Message(s)
}
}
impl From<&str> for Error {
fn from(s: &str) -> Self {
Error::Message(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_debug_form_is_the_sentence_not_the_struct() {
let error = Error::Io(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"Address already in use",
));
let shown = format!("{error:?}");
assert_eq!(shown, format!("{error}"));
assert!(!shown.contains("Io("), "the variant is leaking: {shown}");
assert!(!shown.contains("Os {"), "the os struct is leaking: {shown}");
assert!(shown.contains("Address already in use"), "{shown}");
}
#[test]
fn a_config_error_debugs_to_its_file_and_line() {
let error = Error::Config {
file: ".env".into(),
line: 4,
message: "expected KEY=value".into(),
};
assert_eq!(format!("{error:?}"), ".env:4: expected KEY=value");
}
}