Reorganize module structure
This commit is contained in:
parent
51047ad11c
commit
14dc84cc43
14 changed files with 19 additions and 23 deletions
6
src/router/handlers.rs
Normal file
6
src/router/handlers.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub mod graph;
|
||||
pub mod template;
|
||||
pub mod raw;
|
||||
pub mod navigation;
|
||||
pub mod fixed;
|
||||
pub mod error;
|
||||
58
src/router/handlers/error.rs
Normal file
58
src/router/handlers/error.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{Response, StatusCode, header},
|
||||
};
|
||||
|
||||
use crate::{syntax::serial::populate_graph, router::handlers};
|
||||
|
||||
pub(in crate::router::handlers) fn by_code(
|
||||
code: Option<u16>,
|
||||
message: Option<&str>,
|
||||
) -> Response<Body> {
|
||||
let out_code = code.unwrap_or(500);
|
||||
let out_message = &message.unwrap_or("Unknown error");
|
||||
|
||||
let body = make_body(Some(out_code), Some(out_message));
|
||||
|
||||
handlers::raw::make_response(
|
||||
&body,
|
||||
out_code,
|
||||
&[(header::CONTENT_TYPE, "text/html")],
|
||||
)
|
||||
}
|
||||
|
||||
fn make_body(code: Option<u16>, message: Option<&str>) -> String {
|
||||
let mut context = tera::Context::new();
|
||||
|
||||
let out_code = code.unwrap_or(500);
|
||||
let out_message = &message.unwrap_or("Unknown error");
|
||||
let config = populate_graph().meta.config;
|
||||
|
||||
context.insert(
|
||||
"title",
|
||||
&StatusCode::from_u16(out_code)
|
||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
context.insert("message", out_message);
|
||||
context.insert("status_code", &out_code.to_string());
|
||||
context.insert("config", &config);
|
||||
|
||||
handlers::template::render(
|
||||
"error.html",
|
||||
&context,
|
||||
Some(&format!(
|
||||
"Failed to render template for Error {out_code}: {out_message}"
|
||||
))
|
||||
.cloned(),
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
pub async fn not_found() -> Response<Body> {
|
||||
by_code(
|
||||
Some(404),
|
||||
Some("The page you tried to access could not be found."),
|
||||
)
|
||||
}
|
||||
57
src/router/handlers/fixed.rs
Normal file
57
src/router/handlers/fixed.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{Response, StatusCode, header, HeaderValue},
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
router::handlers,
|
||||
syntax::serial::{Format, populate_graph, serialize_graph},
|
||||
};
|
||||
|
||||
/// # Panics
|
||||
/// Will panic if file read fails.
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn file(file_path: &str, content_type: &str) -> Response<Body> {
|
||||
let content = match std::fs::read(file_path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
panic!("Failed to read {file_path} contents: {e}")
|
||||
},
|
||||
};
|
||||
|
||||
let mut response = Response::new(Body::from(content));
|
||||
*response.status_mut() = StatusCode::OK;
|
||||
let header = header::CONTENT_TYPE;
|
||||
|
||||
if let Ok(header_value) = HeaderValue::from_str(content_type) {
|
||||
if let Some(h) = response.headers_mut().insert(header, header_value) {
|
||||
log!(
|
||||
"Overwrote existing header {h:?} because a header for the same key existed"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log!("Failed to create content type header value from {content_type}");
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn serial(format: &Format) -> Response<Body> {
|
||||
let graph = populate_graph();
|
||||
let body = serialize_graph(format, &graph);
|
||||
|
||||
match *format {
|
||||
Format::Toml => handlers::raw::make_response(
|
||||
&body,
|
||||
200,
|
||||
&[(header::CONTENT_TYPE, "text/plain")],
|
||||
),
|
||||
Format::Json => handlers::raw::make_response(
|
||||
&body,
|
||||
200,
|
||||
&[(header::CONTENT_TYPE, "application/json")],
|
||||
),
|
||||
}
|
||||
}
|
||||
42
src/router/handlers/graph.rs
Normal file
42
src/router/handlers/graph.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use axum::response::IntoResponse as _;
|
||||
use axum::{body::Body, extract::Path, http::Response, response::Redirect};
|
||||
|
||||
use crate::syntax::content;
|
||||
|
||||
use crate::{syntax::serial::populate_graph, router::handlers, types::Node};
|
||||
|
||||
pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
||||
let graph = populate_graph();
|
||||
let empty_node = Node::new(Some(format!("Could not find node ID {id}.")));
|
||||
let node = graph.find_node(&id).unwrap_or(empty_node.clone());
|
||||
|
||||
if !graph.nodes.contains_key(&id)
|
||||
&& graph.lowercase_keymap.contains_key(&id)
|
||||
{
|
||||
return Redirect::permanent(format!("/node/{}", node.id).as_str())
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("node", &node);
|
||||
context.insert("text", &content::parse(&node.text));
|
||||
context.insert("incoming", &graph.incoming.get(&id));
|
||||
context.insert("config", &graph.meta.config.parse_text());
|
||||
|
||||
let not_found = node == empty_node;
|
||||
|
||||
handlers::template::by_filename(
|
||||
"node.html",
|
||||
&context,
|
||||
if not_found { 404 } else { 500 },
|
||||
Some(
|
||||
format!(
|
||||
"Failed to generate page for node {} (ID {}).\n\
|
||||
Node struct: <pre>{:#?}</pre>",
|
||||
node.title, id, node
|
||||
)
|
||||
.to_owned(),
|
||||
),
|
||||
not_found,
|
||||
)
|
||||
}
|
||||
31
src/router/handlers/navigation.rs
Normal file
31
src/router/handlers/navigation.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{Response},
|
||||
response::Redirect,
|
||||
Form,
|
||||
};
|
||||
|
||||
use crate::{syntax::serial::populate_graph, router::handlers, types::Node};
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn page(template: &str) -> Response<Body> {
|
||||
let mut context = tera::Context::new();
|
||||
let graph = populate_graph();
|
||||
let root_node = graph.get_root().unwrap_or_default();
|
||||
let nodes: Vec<Node> = graph.nodes.into_values().collect();
|
||||
|
||||
context.insert("nodes", &nodes);
|
||||
context.insert("root_node", &root_node);
|
||||
context.insert("config", &graph.meta.config.parse_text());
|
||||
|
||||
handlers::template::by_filename(template, &context, 500, None, false)
|
||||
}
|
||||
|
||||
pub async fn search(Form(query): Form<Query>) -> Redirect {
|
||||
Redirect::permanent(format!("/node/{}", query.node).as_str())
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct Query {
|
||||
node: String,
|
||||
}
|
||||
35
src/router/handlers/raw.rs
Normal file
35
src/router/handlers/raw.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{header, HeaderValue, Response, StatusCode},
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(in crate::router::handlers) fn make_response(
|
||||
body: &str,
|
||||
status_code: u16,
|
||||
headers: &[(header::HeaderName, &str)],
|
||||
) -> Response<Body> {
|
||||
let mut response = Response::new(Body::from(body.to_owned()));
|
||||
|
||||
*response.status_mut() = StatusCode::from_u16(status_code)
|
||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
for header in headers {
|
||||
if let Ok(wrapped) = HeaderValue::from_str(header.1) {
|
||||
if let Some(overwritten) =
|
||||
response.headers_mut().insert(header.0.clone(), wrapped)
|
||||
{
|
||||
log!(
|
||||
"Overwrote header {overwritten:?} \
|
||||
because another for key {} already existed",
|
||||
header.0
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log!("Failed to wrap header value {}", header.1);
|
||||
}
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
106
src/router/handlers/template.rs
Normal file
106
src/router/handlers/template.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{header, Response, StatusCode},
|
||||
};
|
||||
|
||||
use crate::{prelude::*, router::handlers::raw::make_response};
|
||||
|
||||
pub(in crate::router::handlers) fn by_filename(
|
||||
name: &str,
|
||||
context: &tera::Context,
|
||||
error_code: u16,
|
||||
error_message: Option<String>,
|
||||
is_error: bool,
|
||||
) -> Response<Body> {
|
||||
let (body, render_status) = render(name, context, error_message);
|
||||
|
||||
let status_code = if is_error { error_code } else { render_status };
|
||||
|
||||
make_response(&body, status_code, &[(header::CONTENT_TYPE, "text/html")])
|
||||
}
|
||||
|
||||
pub(in crate::router::handlers) fn render(
|
||||
name: &str,
|
||||
// TODO take Option, skip context if None,
|
||||
// then template_handler can replace static_template_handler
|
||||
context: &tera::Context,
|
||||
error_message: Option<String>,
|
||||
) -> (String, u16) {
|
||||
// TODO just return an Option/String> here
|
||||
let tera = match tera::Tera::new(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/templates/**/*"
|
||||
)) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
let early_error_message = format!("{e:#?}");
|
||||
log!("{}", early_error_message);
|
||||
return (emergency_wrap(&e), 500);
|
||||
},
|
||||
};
|
||||
|
||||
match tera.render(name, context) {
|
||||
Ok(t) => (t, 200),
|
||||
Err(e) => {
|
||||
let mut error_context = tera::Context::new();
|
||||
|
||||
let out_error_message = match error_message {
|
||||
Some(s) => &format!(
|
||||
"Template render failed.\n\
|
||||
User message: {s},
|
||||
Engine message:\n<pre>{e:#?}</pre>\n\
|
||||
Context:\n<pre>{context:#?}</pre>"
|
||||
),
|
||||
None => &format!(
|
||||
"Template render failed.\n\
|
||||
Engine message:\n<pre>{e:#?}</pre>\n\
|
||||
Context:\n<pre>{context:#?}</pre>"
|
||||
),
|
||||
};
|
||||
|
||||
error_context.insert("message", out_error_message);
|
||||
error_context.insert(
|
||||
"title",
|
||||
&StatusCode::INTERNAL_SERVER_ERROR.to_string(),
|
||||
);
|
||||
|
||||
(
|
||||
tera.render("error.html", &error_context)
|
||||
.unwrap_or(out_error_message.clone()),
|
||||
500,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn emergency_wrap(message: &tera::Error) -> String {
|
||||
format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pre-Templating Error</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {{
|
||||
* {{ background-color: #222222;
|
||||
color: #f1e9e5; }} }}
|
||||
* {{ line-height: 1.6em; }}
|
||||
pre {{ overflow: auto; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2><strong>Early Pre-Templating Error</strong></h2>
|
||||
<p>This normally indicates a malformed template.</p>
|
||||
<pre>
|
||||
{message}
|
||||
</pre>
|
||||
<p>
|
||||
If you haven't modified templates, plese consider
|
||||
<a href="https://codeberg.org/jutty/en/issues">reporting it</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue