Heavy refactor and extraction of most code to handler submodules
This commit is contained in:
parent
7d87046166
commit
ab6e90b6b8
9 changed files with 313 additions and 277 deletions
|
|
@ -6,7 +6,9 @@ It works by ingesting a TOML file containing your node specification and serving
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [ ] Automatic anchors
|
- [ ] Anchor rendering
|
||||||
|
- [ ] Automatic anchors
|
||||||
|
- [ ] Connection kinds
|
||||||
- [ ] Reduce O(n) calls in the formats module
|
- [ ] Reduce O(n) calls in the formats module
|
||||||
- [ ] Add tests
|
- [ ] Add tests
|
||||||
- [x] Array syntax for lightweight connections
|
- [x] Array syntax for lightweight connections
|
||||||
|
|
|
||||||
6
src/handlers.rs
Normal file
6
src/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;
|
||||||
53
src/handlers/error.rs
Normal file
53
src/handlers/error.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{Response, StatusCode, header},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::handlers;
|
||||||
|
|
||||||
|
pub 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");
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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."),
|
||||||
|
)
|
||||||
|
}
|
||||||
54
src/handlers/fixed.rs
Normal file
54
src/handlers/fixed.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{Response, StatusCode, header, HeaderValue},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::formats::{Format, populate_graph, serialize_graph};
|
||||||
|
use crate::handlers;
|
||||||
|
|
||||||
|
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!("[file_handler] Failed to read file 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) {
|
||||||
|
eprintln!(
|
||||||
|
"[file_handler] Overwrote existing header {h:?} \
|
||||||
|
because a header for the same key existed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"[file_handler] Failed to create content type \
|
||||||
|
header value from {content_type}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
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")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/handlers/graph.rs
Normal file
38
src/handlers/graph.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use axum::{body::Body, extract::Path, http::Response};
|
||||||
|
|
||||||
|
use crate::{formats::populate_graph, types::Node, handlers};
|
||||||
|
|
||||||
|
pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
||||||
|
let mut context = tera::Context::new();
|
||||||
|
|
||||||
|
let graph = populate_graph();
|
||||||
|
let nodes = graph.nodes;
|
||||||
|
let empty_node =
|
||||||
|
Node::new(Some(format!("Could not find node with ID {id}.")));
|
||||||
|
|
||||||
|
let node: &Node = nodes.get(&id).unwrap_or(&empty_node);
|
||||||
|
|
||||||
|
context.insert("id", &id);
|
||||||
|
context.insert("title", &node.title);
|
||||||
|
context.insert("text", &node.text);
|
||||||
|
context.insert("connections", &node.connections.clone());
|
||||||
|
context.insert("incoming", &graph.incoming.get(&id));
|
||||||
|
|
||||||
|
let not_found = node.clone() == empty_node;
|
||||||
|
let template_name = "node.html".to_string();
|
||||||
|
|
||||||
|
handlers::template::by_filename(
|
||||||
|
&template_name,
|
||||||
|
&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,
|
||||||
|
)
|
||||||
|
}
|
||||||
29
src/handlers/navigation.rs
Normal file
29
src/handlers/navigation.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{Response},
|
||||||
|
response::Redirect,
|
||||||
|
Form,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{formats::populate_graph, types::Node, handlers};
|
||||||
|
|
||||||
|
pub async fn nexus(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);
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
36
src/handlers/raw.rs
Normal file
36
src/handlers/raw.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{header, HeaderValue, Response, StatusCode},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub 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)
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[make_response] Overwrote header {overwritten:?} \
|
||||||
|
because another for key {} already existed",
|
||||||
|
header.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"[make_response] Failed to wrap header value {}",
|
||||||
|
header.1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
77
src/handlers/template.rs
Normal file
77
src/handlers/template.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{header, Response, StatusCode},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::handlers::raw::make_response;
|
||||||
|
|
||||||
|
pub(super) 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 async fn static_template_handler(name: &str) -> Response<Body> {
|
||||||
|
by_filename(name, &tera::Context::new(), 500, None, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) 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) => {
|
||||||
|
println!("Tera parsing error: {e:#?}");
|
||||||
|
panic!("{e}")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
293
src/main.rs
293
src/main.rs
|
|
@ -1,17 +1,17 @@
|
||||||
use axum::{
|
use axum::{routing::get, Router};
|
||||||
body::Body,
|
|
||||||
extract::Path,
|
|
||||||
http::{header, HeaderValue, Response, StatusCode},
|
|
||||||
response::{Redirect},
|
|
||||||
routing::get,
|
|
||||||
Form, Router,
|
|
||||||
};
|
|
||||||
|
|
||||||
use formats::{populate_graph, serialize_graph, Format};
|
use handlers::{
|
||||||
use types::Node;
|
graph::node,
|
||||||
|
navigation::{nexus, search},
|
||||||
|
fixed::{file, serial},
|
||||||
|
template::static_template_handler,
|
||||||
|
error::not_found,
|
||||||
|
};
|
||||||
|
use formats::Format;
|
||||||
|
|
||||||
mod formats;
|
mod formats;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod handlers;
|
||||||
|
|
||||||
static ONSET: std::sync::LazyLock<std::time::Instant> =
|
static ONSET: std::sync::LazyLock<std::time::Instant> =
|
||||||
std::sync::LazyLock::new(std::time::Instant::now);
|
std::sync::LazyLock::new(std::time::Instant::now);
|
||||||
|
|
@ -32,19 +32,19 @@ async fn main() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index).post(query))
|
.route("/", get(|| nexus("index.html")).post(search))
|
||||||
.route("/graph/toml", get(toml_graph))
|
.route("/graph/toml", get(|| serial(&Format::Toml)))
|
||||||
.route("/graph/json", get(json_graph))
|
.route("/graph/json", get(|| serial(&Format::Json)))
|
||||||
.route(
|
.route(
|
||||||
"/static/style.css",
|
"/static/style.css",
|
||||||
get(|| file_handler("./static/style.css", "text/css")),
|
get(|| file("./static/style.css", "text/css")),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/static/favicon.svg",
|
"/static/favicon.svg",
|
||||||
get(|| file_handler("./static/favicon.svg", "image/svg+xml")),
|
get(|| file("./static/favicon.svg", "image/svg+xml")),
|
||||||
)
|
)
|
||||||
.route("/node/{node_id}", get(node_view).post(node_view))
|
.route("/node/{node_id}", get(node).post(node))
|
||||||
.route("/tree", get(tree))
|
.route("/tree", get(|| nexus("tree.html")))
|
||||||
.route("/about", get(|| static_template_handler("about.html")))
|
.route("/about", get(|| static_template_handler("about.html")))
|
||||||
.route(
|
.route(
|
||||||
"/acknowledgments",
|
"/acknowledgments",
|
||||||
|
|
@ -67,262 +67,3 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_body(
|
|
||||||
name: &str,
|
|
||||||
context: &tera::Context,
|
|
||||||
error_message: Option<String>,
|
|
||||||
) -> (String, u16) {
|
|
||||||
let tera = match tera::Tera::new(concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/templates/**/*"
|
|
||||||
)) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
println!("Tera parsing error: {e:#?}");
|
|
||||||
panic!("{e}")
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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 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)
|
|
||||||
{
|
|
||||||
eprintln!(
|
|
||||||
"[make_response] Overwrote header {overwritten:?} \
|
|
||||||
because another for key {} already existed",
|
|
||||||
header.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[make_response] Failed to wrap header value {}",
|
|
||||||
header.1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
fn template_handler(
|
|
||||||
name: &str,
|
|
||||||
context: &tera::Context,
|
|
||||||
error_code: u16,
|
|
||||||
error_message: Option<String>,
|
|
||||||
is_error: bool,
|
|
||||||
) -> Response<Body> {
|
|
||||||
let (body, render_status) = make_body(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")])
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn node_view(Path(id): Path<String>) -> Response<Body> {
|
|
||||||
let mut context = tera::Context::new();
|
|
||||||
|
|
||||||
let graph = populate_graph();
|
|
||||||
let nodes = graph.nodes;
|
|
||||||
let empty_node =
|
|
||||||
Node::new(Some(format!("Could not find node with ID {id}.")));
|
|
||||||
|
|
||||||
let node: &Node = nodes.get(&id).unwrap_or(&empty_node);
|
|
||||||
|
|
||||||
context.insert("id", &id);
|
|
||||||
context.insert("title", &node.title);
|
|
||||||
context.insert("text", &node.text);
|
|
||||||
context.insert("connections", &node.connections.clone());
|
|
||||||
context.insert("incoming", &graph.incoming.get(&id));
|
|
||||||
|
|
||||||
let not_found = node.clone() == empty_node;
|
|
||||||
let template_name = "node.html".to_string();
|
|
||||||
|
|
||||||
template_handler(
|
|
||||||
&template_name,
|
|
||||||
&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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn index() -> 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);
|
|
||||||
|
|
||||||
template_handler("index.html", &context, 500, None, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn tree() -> 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);
|
|
||||||
|
|
||||||
template_handler("tree.html", &context, 500, None, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::unused_async)]
|
|
||||||
async fn static_template_handler(name: &str) -> Response<Body> {
|
|
||||||
template_handler(name, &tera::Context::new(), 500, None, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::unused_async)]
|
|
||||||
async fn file_handler(file_path: &str, content_type: &str) -> Response<Body> {
|
|
||||||
let content = match std::fs::read(file_path) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("[static_file_handler] Failed to read file 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) {
|
|
||||||
eprintln!(
|
|
||||||
"[static_file_handler] Overwrote existing header {h:?} \
|
|
||||||
because a header for the same key existed"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"[static_file_handler] Failed to create content type \
|
|
||||||
header value from {content_type}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct Query {
|
|
||||||
node: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn query(Form(query): Form<Query>) -> Redirect {
|
|
||||||
Redirect::permanent(format!("/node/{}", query.node).as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn json_graph() -> Response<Body> {
|
|
||||||
let graph = populate_graph();
|
|
||||||
let body = serialize_graph(&Format::Json, &graph);
|
|
||||||
|
|
||||||
make_response(&body, 200, &[(header::CONTENT_TYPE, "application/json")])
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn toml_graph() -> Response<Body> {
|
|
||||||
let graph = populate_graph();
|
|
||||||
let body = serialize_graph(&Format::Toml, &graph);
|
|
||||||
|
|
||||||
make_response(&body, 200, &[(header::CONTENT_TYPE, "text/plain")])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_error_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");
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
make_body(
|
|
||||||
"error.html",
|
|
||||||
&context,
|
|
||||||
Some(&format!(
|
|
||||||
"Failed to render template for Error {out_code}: {out_message}"
|
|
||||||
))
|
|
||||||
.cloned(),
|
|
||||||
)
|
|
||||||
.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_error_response(
|
|
||||||
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_error_body(Some(out_code), Some(out_message));
|
|
||||||
|
|
||||||
make_response(&body, out_code, &[(header::CONTENT_TYPE, "text/html")])
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn not_found() -> Response<Body> {
|
|
||||||
make_error_response(
|
|
||||||
Some(404),
|
|
||||||
Some("The page you tried to access could not be found."),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue