about summary refs log tree commit diff
path: root/splorm/src/runtime_error.rs
diff options
context:
space:
mode:
Diffstat (limited to 'splorm/src/runtime_error.rs')
-rw-r--r--splorm/src/runtime_error.rs190
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()
+}