diff options
Diffstat (limited to 'splorm/src/runtime_error.rs')
| -rw-r--r-- | splorm/src/runtime_error.rs | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/splorm/src/runtime_error.rs b/splorm/src/runtime_error.rs new file mode 100644 index 0000000..99aca45 --- /dev/null +++ b/splorm/src/runtime_error.rs @@ -0,0 +1,190 @@ +use std::{fmt, io::ErrorKind}; + +use axum::{ + body::Body, + response::{IntoResponse, Response}, +}; +use camino::Utf8PathBuf; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum InvalidPathReason { + OutsideRoot, + NotFound, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum FileErrorReason { + PermissionDenied, + NotFound, + Unknown, +} + +impl fmt::Display for FileErrorReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NotFound => write!(f, "file not found"), + Self::PermissionDenied => write!(f, "permission denied"), + Self::Unknown => write!(f, "unknown file error"), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum TemplateErrorReason { + NotATemplate, + ParseError, +} + +#[derive(Debug)] +pub enum RuntimeError { + InvalidPath { + reason: InvalidPathReason, + request_path: String, + system_path: Utf8PathBuf, + }, + FileError { + reason: FileErrorReason, + source: std::io::Error, + system_path: Utf8PathBuf, + }, + TemplateError { + reason: TemplateErrorReason, + source: Option<bempline::ParseError>, + system_path: Utf8PathBuf, + }, + Unknown, +} + +impl RuntimeError { + pub fn outside_root(request_path: String, system_path: Utf8PathBuf) -> Self { + Self::InvalidPath { + reason: InvalidPathReason::OutsideRoot, + request_path, + system_path, + } + } + + pub fn not_found(request_path: String, system_path: Utf8PathBuf) -> Self { + Self::InvalidPath { + reason: InvalidPathReason::NotFound, + request_path, + system_path, + } + } + + pub fn file_error(err: std::io::Error, system_path: Utf8PathBuf) -> Self { + let reason = match err.kind() { + ErrorKind::PermissionDenied => FileErrorReason::PermissionDenied, + ErrorKind::NotFound => FileErrorReason::NotFound, + _ => FileErrorReason::Unknown, + }; + + Self::FileError { + reason, + source: err, + system_path, + } + } + + pub fn not_a_template(system_path: Utf8PathBuf) -> Self { + Self::TemplateError { + reason: TemplateErrorReason::NotATemplate, + source: None, + system_path, + } + } + + pub fn template_parse_error(err: bempline::ParseError, system_path: Utf8PathBuf) -> Self { + Self::TemplateError { + reason: TemplateErrorReason::ParseError, + source: Some(err), + system_path, + } + } + + pub fn unknown() -> Self { + RuntimeError::Unknown + } + + pub fn status_code(&self) -> u16 { + match self { + Self::InvalidPath { .. } => 404, + Self::FileError { reason, .. } => match reason { + FileErrorReason::NotFound => 404, + FileErrorReason::PermissionDenied => 404, + FileErrorReason::Unknown => 500, + }, + Self::TemplateError { .. } => 500, + Self::Unknown => 500, + } + } +} + +struct PresentableRuntimeError(RuntimeError); +impl fmt::Display for PresentableRuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + macro_rules! estr { + (404) => { + write!(f, "the requested file could not be found") + }; + (404 $webpath:ident) => { + write!(f, "file not found: '{}'", $webpath) + }; + (500) => { + write!(f, "an unknown error occured!") + }; + } + + match &self.0 { + RuntimeError::InvalidPath { + request_path, + reason, + system_path, + } => { + println!("invalid_path = {request_path} ({system_path}) // {reason:?}"); + estr!(404 request_path) + } + RuntimeError::FileError { + reason, + system_path, + .. + } => match reason { + FileErrorReason::NotFound | FileErrorReason::PermissionDenied => { + println!("404 = {system_path}"); + estr!(404) + } + FileErrorReason::Unknown => estr!(500), + }, + RuntimeError::TemplateError { .. } => estr!(500), + RuntimeError::Unknown => estr!(500), + } + } +} + +impl IntoResponse for RuntimeError { + fn into_response(self) -> Response { + let status = self.status_code(); + let presentable = PresentableRuntimeError(self); + make_basic_error(status, presentable) + } +} + +fn make_basic_error<S: std::fmt::Display>(status: u16, msg: S) -> Response { + let error_str = format!( + "<html>\n\ + \t<head><title>{status}</title></head>\n\ + \t<body style='width: 20rem; padding: 0px; margin: 2rem;'>\n\ + \t\t<h1>{status}</h1>\n\ + \t\t<hr/>\n\ + \t\t<p>{msg}</p>\n\ + \t</body>\n\ + </html>" + ); + + Response::builder() + .status(status) + .header("content-type", "text/html") + .body(Body::from(error_str)) + .unwrap() + .into_response() +} |
