Setup and apply formatting
This commit is contained in:
parent
2cd702818e
commit
20bd1db1b7
5 changed files with 188 additions and 139 deletions
25
.rustfmt.toml
Normal file
25
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
max_width = 80
|
||||||
|
reorder_imports = false
|
||||||
|
reorder_modules = false
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
|
|
||||||
|
# blank_lines_lower_bound = 1
|
||||||
|
# not stabilized yet
|
||||||
|
# where_single_line = true
|
||||||
|
# overflow_delimited_expr = true
|
||||||
|
# normalize_doc_attributes = true
|
||||||
|
# normalize_comments = true
|
||||||
|
# inline_attribute_width = 40
|
||||||
|
# imports_granularity = "Crate"
|
||||||
|
# hex_literal_case = "Lower"
|
||||||
|
# group_imports = "StdExternalCrate"
|
||||||
|
# format_strings = true
|
||||||
|
# force_multiline_blocks = true
|
||||||
|
# error_on_unformatted = true
|
||||||
|
# error_on_line_overflow = true
|
||||||
|
# condense_wildcard_suffixes = true
|
||||||
|
# doc_comment_code_block_width = 70
|
||||||
|
# format_code_in_doc_comments = true
|
||||||
|
# wrap_comments = true
|
||||||
|
|
@ -9,6 +9,15 @@ repository = "https://codeberg.org/jutty/en"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version= "1.91.1"
|
rust-version= "1.91.1"
|
||||||
|
|
||||||
|
[package.metadata.bacon.jobs.fmt-check]
|
||||||
|
command = [
|
||||||
|
"cargo", "fmt",
|
||||||
|
"--check",
|
||||||
|
"--",
|
||||||
|
"--color=always",
|
||||||
|
]
|
||||||
|
need_stdout = true
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
# levels: allow, expect, warn, force-warn, deny, forbid
|
# levels: allow, expect, warn, force-warn, deny, forbid
|
||||||
unsafe_code = { level = "forbid", priority = 99 }
|
unsafe_code = { level = "forbid", priority = 99 }
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||||
use crate::types::{Graph, Node, Edge};
|
use crate::types::{Graph, Node, Edge};
|
||||||
|
|
||||||
pub fn populate_graph() -> Graph {
|
pub fn populate_graph() -> Graph {
|
||||||
|
|
||||||
let toml_source = match std::fs::read_to_string("./static/graph.toml") {
|
let toml_source = match std::fs::read_to_string("./static/graph.toml") {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => format!("Error: {e}"),
|
Err(e) => format!("Error: {e}"),
|
||||||
|
|
@ -20,16 +19,13 @@ pub fn populate_graph() -> Graph {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modulate_nodes(old_nodes: &HashMap<String, Node>) -> HashMap<String, Node> {
|
fn modulate_nodes(old_nodes: &HashMap<String, Node>) -> HashMap<String, Node> {
|
||||||
|
|
||||||
let mut nodes: HashMap<String, Node> = HashMap::new();
|
let mut nodes: HashMap<String, Node> = HashMap::new();
|
||||||
|
|
||||||
for (key, node) in old_nodes {
|
for (key, node) in old_nodes {
|
||||||
|
|
||||||
let connections = node.connections.clone().unwrap_or_default();
|
let connections = node.connections.clone().unwrap_or_default();
|
||||||
let mut new_edges = connections.clone();
|
let mut new_edges = connections.clone();
|
||||||
|
|
||||||
for (i, edge) in connections.iter().enumerate() {
|
for (i, edge) in connections.iter().enumerate() {
|
||||||
|
|
||||||
let mut new_edge = edge.clone();
|
let mut new_edge = edge.clone();
|
||||||
|
|
||||||
// Populate empty "from" IDs in edges with node's ID
|
// Populate empty "from" IDs in edges with node's ID
|
||||||
|
|
@ -38,11 +34,13 @@ fn modulate_nodes(old_nodes: &HashMap<String, Node>) -> HashMap<String, Node> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag detached edges
|
// Flag detached edges
|
||||||
if ! old_nodes.contains_key(&edge.to) {
|
if !old_nodes.contains_key(&edge.to) {
|
||||||
new_edge.detached = true;
|
new_edge.detached = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(e) = new_edges.get_mut(i) { *e = new_edge; }
|
if let Some(e) = new_edges.get_mut(i) {
|
||||||
|
*e = new_edge;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create connections for each link
|
// Create connections for each link
|
||||||
|
|
@ -77,13 +75,13 @@ fn modulate_nodes(old_nodes: &HashMap<String, Node>) -> HashMap<String, Node> {
|
||||||
|
|
||||||
// Construct a HashMap with incoming connections (reversed edges)
|
// Construct a HashMap with incoming connections (reversed edges)
|
||||||
fn make_incoming(nodes: &HashMap<String, Node>) -> HashMap<String, Vec<Edge>> {
|
fn make_incoming(nodes: &HashMap<String, Node>) -> HashMap<String, Vec<Edge>> {
|
||||||
|
|
||||||
let mut incoming: HashMap<String, Vec<Edge>> = HashMap::new();
|
let mut incoming: HashMap<String, Vec<Edge>> = HashMap::new();
|
||||||
for node in nodes.clone().into_values() {
|
|
||||||
|
|
||||||
|
for node in nodes.clone().into_values() {
|
||||||
let empty_vec: Vec<Edge> = vec![];
|
let empty_vec: Vec<Edge> = vec![];
|
||||||
for edge in &node.connections.clone().unwrap_or_default() {
|
for edge in &node.connections.clone().unwrap_or_default() {
|
||||||
let mut edges = incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone();
|
let mut edges =
|
||||||
|
incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone();
|
||||||
edges.extend_from_slice(std::slice::from_ref(edge));
|
edges.extend_from_slice(std::slice::from_ref(edge));
|
||||||
incoming.insert(edge.to.clone(), edges.clone());
|
incoming.insert(edge.to.clone(), edges.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -94,39 +92,31 @@ fn make_incoming(nodes: &HashMap<String, Node>) -> HashMap<String, Vec<Edge>> {
|
||||||
|
|
||||||
pub enum Format {
|
pub enum Format {
|
||||||
Toml,
|
Toml,
|
||||||
Json
|
Json,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_graph(out_format: &Format, graph: &Graph) -> String {
|
pub fn serialize_graph(out_format: &Format, graph: &Graph) -> String {
|
||||||
|
|
||||||
match *out_format {
|
match *out_format {
|
||||||
Format::Toml => {
|
Format::Toml => match toml::to_string(graph) {
|
||||||
match toml::to_string(graph) {
|
Ok(s) => s,
|
||||||
Ok(s) => s,
|
Err(e) => e.to_string(),
|
||||||
Err(e) => e.to_string(),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Format::Json => {
|
Format::Json => match serde_json::to_string(graph) {
|
||||||
match serde_json::to_string(graph) {
|
Ok(s) => s,
|
||||||
Ok(s) => s,
|
Err(e) => e.to_string(),
|
||||||
Err(e) => e.to_string(),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_graph(in_format: &Format, serial: &str) -> Graph {
|
pub fn deserialize_graph(in_format: &Format, serial: &str) -> Graph {
|
||||||
|
|
||||||
match *in_format {
|
match *in_format {
|
||||||
Format::Toml => { match toml::from_str(serial) {
|
Format::Toml => match toml::from_str(serial) {
|
||||||
Ok(g) => g,
|
Ok(g) => g,
|
||||||
Err(error) => Graph::new(Some(error.to_string()))
|
Err(error) => Graph::new(Some(error.to_string())),
|
||||||
}},
|
},
|
||||||
Format::Json => { match serde_json::from_str(serial) {
|
Format::Json => match serde_json::from_str(serial) {
|
||||||
Ok(g) => g,
|
Ok(g) => g,
|
||||||
Err(error) => Graph::new(Some(error.to_string()))
|
Err(error) => Graph::new(Some(error.to_string())),
|
||||||
}}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
207
src/main.rs
207
src/main.rs
|
|
@ -1,11 +1,10 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::Path,
|
extract::Path,
|
||||||
http::{ header, HeaderValue, Response, StatusCode },
|
http::{header, HeaderValue, Response, StatusCode},
|
||||||
response::{ Redirect },
|
response::{Redirect},
|
||||||
routing::get,
|
routing::get,
|
||||||
Form,
|
Form, Router,
|
||||||
Router,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use formats::{populate_graph, serialize_graph, Format};
|
use formats::{populate_graph, serialize_graph, Format};
|
||||||
|
|
@ -19,44 +18,50 @@ static ONSET: std::sync::LazyLock<std::time::Instant> =
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
||||||
std::panic::set_hook(Box::new(|info| {
|
std::panic::set_hook(Box::new(|info| {
|
||||||
|
let payload = info
|
||||||
let payload = info.payload_as_str().unwrap_or(
|
.payload_as_str()
|
||||||
"No string payload. Is edition > 2021?");
|
.unwrap_or("No string payload. Is edition > 2021?");
|
||||||
|
|
||||||
let location = info.location().map_or_else(
|
let location = info.location().map_or_else(
|
||||||
|| "location unavailable".to_string(),
|
|| "location unavailable".to_string(),
|
||||||
|s| format!("{}:{}:{}", s.file(), s.line(), s.column()));
|
|s| format!("{}:{}:{}", s.file(), s.line(), s.column()),
|
||||||
|
);
|
||||||
|
|
||||||
eprintln!(" P! [{:?}] {}: {}", ONSET.elapsed(), location, payload);
|
eprintln!(" P! [{:?}] {}: {}", ONSET.elapsed(), location, payload);
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index).post(query))
|
.route("/", get(index).post(query))
|
||||||
.route("/graph/toml", get(toml_graph))
|
.route("/graph/toml", get(toml_graph))
|
||||||
.route("/graph/json", get(json_graph))
|
.route("/graph/json", get(json_graph))
|
||||||
.route("/static/style.css", get(
|
.route(
|
||||||
|| { file_handler("./static/style.css", "text/css") }))
|
"/static/style.css",
|
||||||
.route("/static/favicon.svg", get(
|
get(|| file_handler("./static/style.css", "text/css")),
|
||||||
|| { file_handler("./static/favicon.svg", "image/svg+xml") }))
|
)
|
||||||
|
.route(
|
||||||
|
"/static/favicon.svg",
|
||||||
|
get(|| file_handler("./static/favicon.svg", "image/svg+xml")),
|
||||||
|
)
|
||||||
.route("/node/{node_id}", get(node_view).post(node_view))
|
.route("/node/{node_id}", get(node_view).post(node_view))
|
||||||
.route("/tree", get(tree))
|
.route("/tree", get(tree))
|
||||||
.route("/about", get(|| static_template_handler("about.html")))
|
.route("/about", get(|| static_template_handler("about.html")))
|
||||||
.route("/acknowledgments", get(|| {
|
.route(
|
||||||
static_template_handler("acknowledgments.html")
|
"/acknowledgments",
|
||||||
}))
|
get(|| static_template_handler("acknowledgments.html")),
|
||||||
.fallback(not_found)
|
)
|
||||||
;
|
.fallback(not_found);
|
||||||
|
|
||||||
if let Ok(listener) = tokio::net::TcpListener::bind("0.0.0.0:3000").await
|
|
||||||
.or(Err("Failed to instantiate Tokio listener")) {
|
|
||||||
|
|
||||||
|
if let Ok(listener) = tokio::net::TcpListener::bind("0.0.0.0:3000")
|
||||||
|
.await
|
||||||
|
.or(Err("Failed to instantiate Tokio listener"))
|
||||||
|
{
|
||||||
match axum::serve(listener, app).await {
|
match axum::serve(listener, app).await {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to serve application with axum::serve: {e:#?}");
|
eprintln!(
|
||||||
|
"Failed to serve application with axum::serve: {e:#?}"
|
||||||
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -68,21 +73,20 @@ fn make_body(
|
||||||
context: &tera::Context,
|
context: &tera::Context,
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
) -> (String, u16) {
|
) -> (String, u16) {
|
||||||
|
let tera = match tera::Tera::new(concat!(
|
||||||
let tera = match tera::Tera::new(
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"),
|
"/templates/**/*"
|
||||||
) {
|
)) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Tera parsing error: {e:#?}");
|
println!("Tera parsing error: {e:#?}");
|
||||||
panic!("{e}")
|
panic!("{e}")
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match tera.render(name, context) {
|
match tera.render(name, context) {
|
||||||
Ok(t) => (t, 200),
|
Ok(t) => (t, 200),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
||||||
let mut error_context = tera::Context::new();
|
let mut error_context = tera::Context::new();
|
||||||
|
|
||||||
let out_error_message = match error_message {
|
let out_error_message = match error_message {
|
||||||
|
|
@ -92,47 +96,54 @@ fn make_body(
|
||||||
Engine message:\n<pre>{e:#?}</pre>\n\
|
Engine message:\n<pre>{e:#?}</pre>\n\
|
||||||
Context:\n<pre>{context:#?}</pre>"
|
Context:\n<pre>{context:#?}</pre>"
|
||||||
),
|
),
|
||||||
None => {
|
None => &format!(
|
||||||
&format!(
|
"Template render failed.\n\
|
||||||
"Template render failed.\n\
|
Engine message:\n<pre>{e:#?}</pre>\n\
|
||||||
Engine message:\n<pre>{e:#?}</pre>\n\
|
Context:\n<pre>{context:#?}</pre>"
|
||||||
Context:\n<pre>{context:#?}</pre>"
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
error_context.insert("message", out_error_message);
|
error_context.insert("message", out_error_message);
|
||||||
error_context.insert("title",
|
error_context.insert(
|
||||||
&StatusCode::INTERNAL_SERVER_ERROR.to_string());
|
"title",
|
||||||
|
&StatusCode::INTERNAL_SERVER_ERROR.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
(tera.render("error.html", &error_context)
|
(
|
||||||
.unwrap_or(out_error_message.clone()), 500)
|
tera.render("error.html", &error_context)
|
||||||
}
|
.unwrap_or(out_error_message.clone()),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_response(
|
fn make_response(
|
||||||
body: &str,
|
body: &str,
|
||||||
status_code: u16,
|
status_code: u16,
|
||||||
headers: &[(header::HeaderName, &str)]
|
headers: &[(header::HeaderName, &str)],
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
|
|
||||||
let mut response = Response::new(Body::from(body.to_owned()));
|
let mut response = Response::new(Body::from(body.to_owned()));
|
||||||
|
|
||||||
*response.status_mut() = StatusCode::from_u16(status_code)
|
*response.status_mut() = StatusCode::from_u16(status_code)
|
||||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
for header in headers {
|
for header in headers {
|
||||||
if let Ok(wrapped) = HeaderValue::from_str(header.1) {
|
if let Ok(wrapped) = HeaderValue::from_str(header.1) {
|
||||||
if let Some(overwritten) = response.headers_mut().insert(
|
if let Some(overwritten) =
|
||||||
header.0.clone(),
|
response.headers_mut().insert(header.0.clone(), wrapped)
|
||||||
wrapped,
|
{
|
||||||
) { eprintln!("[make_response] Overwrote header {overwritten:?} \
|
eprintln!(
|
||||||
because another for key {} already existed", header.0);
|
"[make_response] Overwrote header {overwritten:?} \
|
||||||
|
because another for key {} already existed",
|
||||||
|
header.0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("[make_response] Failed to wrap header value {}",
|
eprintln!(
|
||||||
header.1);
|
"[make_response] Failed to wrap header value {}",
|
||||||
|
header.1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,25 +157,20 @@ fn template_handler(
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
is_error: bool,
|
is_error: bool,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
|
let (body, render_status) = make_body(name, context, error_message);
|
||||||
let (body, render_status) = make_body(
|
|
||||||
name, context, error_message);
|
|
||||||
|
|
||||||
let status_code = if is_error { error_code } else { render_status };
|
let status_code = if is_error { error_code } else { render_status };
|
||||||
|
|
||||||
make_response(&body, status_code,
|
make_response(&body, status_code, &[(header::CONTENT_TYPE, "text/html")])
|
||||||
&[(header::CONTENT_TYPE, "text/html")])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn node_view(Path(id): Path<String>) -> Response<Body> {
|
async fn node_view(Path(id): Path<String>) -> Response<Body> {
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
|
|
||||||
let graph = populate_graph();
|
let graph = populate_graph();
|
||||||
let nodes = graph.nodes;
|
let nodes = graph.nodes;
|
||||||
let empty_node = Node::new(
|
let empty_node =
|
||||||
Some(format!("Could not find node with ID {id}.")),
|
Node::new(Some(format!("Could not find node with ID {id}.")));
|
||||||
);
|
|
||||||
|
|
||||||
let node: &Node = nodes.get(&id).unwrap_or(&empty_node);
|
let node: &Node = nodes.get(&id).unwrap_or(&empty_node);
|
||||||
|
|
||||||
|
|
@ -181,17 +187,19 @@ async fn node_view(Path(id): Path<String>) -> Response<Body> {
|
||||||
&template_name,
|
&template_name,
|
||||||
&context,
|
&context,
|
||||||
if not_found { 404 } else { 500 },
|
if not_found { 404 } else { 500 },
|
||||||
Some(format!(
|
Some(
|
||||||
"Failed to generate page for node {} (ID {}).\n\
|
format!(
|
||||||
Node struct: <pre>{:#?}</pre>",
|
"Failed to generate page for node {} (ID {}).\n\
|
||||||
node.title, id, node
|
Node struct: <pre>{:#?}</pre>",
|
||||||
).to_owned()),
|
node.title, id, node
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
not_found,
|
not_found,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index() -> Response<Body> {
|
async fn index() -> Response<Body> {
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
let graph = populate_graph();
|
let graph = populate_graph();
|
||||||
let root_node = graph.get_root().unwrap_or_default();
|
let root_node = graph.get_root().unwrap_or_default();
|
||||||
|
|
@ -204,7 +212,6 @@ async fn index() -> Response<Body> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tree() -> Response<Body> {
|
async fn tree() -> Response<Body> {
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
let graph = populate_graph();
|
let graph = populate_graph();
|
||||||
let root_node = graph.get_root().unwrap_or_default();
|
let root_node = graph.get_root().unwrap_or_default();
|
||||||
|
|
@ -222,14 +229,12 @@ async fn static_template_handler(name: &str) -> Response<Body> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::unused_async)]
|
#[expect(clippy::unused_async)]
|
||||||
async fn file_handler(
|
async fn file_handler(file_path: &str, content_type: &str) -> Response<Body> {
|
||||||
file_path: &str,
|
|
||||||
content_type: &str,
|
|
||||||
) -> Response<Body> {
|
|
||||||
|
|
||||||
let content = match std::fs::read(file_path) {
|
let content = match std::fs::read(file_path) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => panic!("[static_file_handler] Failed to read file contents: {e}"),
|
Err(e) => {
|
||||||
|
panic!("[static_file_handler] Failed to read file contents: {e}")
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut response = Response::new(Body::from(content));
|
let mut response = Response::new(Body::from(content));
|
||||||
|
|
@ -238,17 +243,25 @@ async fn file_handler(
|
||||||
|
|
||||||
if let Ok(header_value) = HeaderValue::from_str(content_type) {
|
if let Ok(header_value) = HeaderValue::from_str(content_type) {
|
||||||
if let Some(h) = response.headers_mut().insert(header, header_value) {
|
if let Some(h) = response.headers_mut().insert(header, header_value) {
|
||||||
eprintln!("[static_file_handler] Overwrote existing header {h:?} \
|
eprintln!(
|
||||||
because a header for the same key existed");
|
"[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 \
|
} else {
|
||||||
header value from {content_type}"); }
|
eprintln!(
|
||||||
|
"[static_file_handler] Failed to create content type \
|
||||||
|
header value from {content_type}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Query { node: String }
|
struct Query {
|
||||||
|
node: String,
|
||||||
|
}
|
||||||
|
|
||||||
async fn query(Form(query): Form<Query>) -> Redirect {
|
async fn query(Form(query): Form<Query>) -> Redirect {
|
||||||
Redirect::permanent(format!("/node/{}", query.node).as_str())
|
Redirect::permanent(format!("/node/{}", query.node).as_str())
|
||||||
|
|
@ -268,41 +281,43 @@ async fn toml_graph() -> Response<Body> {
|
||||||
make_response(&body, 200, &[(header::CONTENT_TYPE, "text/plain")])
|
make_response(&body, 200, &[(header::CONTENT_TYPE, "text/plain")])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_error_body(
|
fn make_error_body(code: Option<u16>, message: Option<&str>) -> String {
|
||||||
code: Option<u16>,
|
|
||||||
message: Option<&str>,
|
|
||||||
) -> String {
|
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
|
|
||||||
let out_code = code.unwrap_or(500);
|
let out_code = code.unwrap_or(500);
|
||||||
let out_message = &message.unwrap_or("Unknown error");
|
let out_message = &message.unwrap_or("Unknown error");
|
||||||
|
|
||||||
context.insert("title", &StatusCode::from_u16(out_code)
|
context.insert(
|
||||||
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).to_string());
|
"title",
|
||||||
|
&StatusCode::from_u16(out_code)
|
||||||
|
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
context.insert("message", out_message);
|
context.insert("message", out_message);
|
||||||
context.insert("status_code", &out_code.to_string());
|
context.insert("status_code", &out_code.to_string());
|
||||||
|
|
||||||
make_body("error.html", &context, Some(&format!(
|
make_body(
|
||||||
"Failed to render template for Error {out_code}: {out_message}"
|
"error.html",
|
||||||
)).cloned()).0
|
&context,
|
||||||
|
Some(&format!(
|
||||||
|
"Failed to render template for Error {out_code}: {out_message}"
|
||||||
|
))
|
||||||
|
.cloned(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_error_response(
|
fn make_error_response(
|
||||||
code: Option<u16>,
|
code: Option<u16>,
|
||||||
message: Option<&str>,
|
message: Option<&str>,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
|
|
||||||
let out_code = code.unwrap_or(500);
|
let out_code = code.unwrap_or(500);
|
||||||
let out_message = &message.unwrap_or("Unknown error");
|
let out_message = &message.unwrap_or("Unknown error");
|
||||||
|
|
||||||
let body = make_error_body(Some(out_code), Some(out_message));
|
let body = make_error_body(Some(out_code), Some(out_message));
|
||||||
|
|
||||||
make_response(
|
make_response(&body, out_code, &[(header::CONTENT_TYPE, "text/html")])
|
||||||
&body,
|
|
||||||
out_code,
|
|
||||||
&[(header::CONTENT_TYPE, "text/html")],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn not_found() -> Response<Body> {
|
async fn not_found() -> Response<Body> {
|
||||||
|
|
|
||||||
32
src/types.rs
32
src/types.rs
|
|
@ -6,16 +6,21 @@ use serde::{Serialize, Deserialize};
|
||||||
pub struct Graph {
|
pub struct Graph {
|
||||||
pub nodes: HashMap<String, Node>,
|
pub nodes: HashMap<String, Node>,
|
||||||
pub root_node: String,
|
pub root_node: String,
|
||||||
#[serde(default)] pub messages: Vec<String>,
|
#[serde(default)]
|
||||||
#[serde(skip)] pub incoming: HashMap<String, Vec<Edge>>,
|
pub messages: Vec<String>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub incoming: HashMap<String, Vec<Edge>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
#[serde(default)] pub title: String,
|
#[serde(default)]
|
||||||
#[serde(default)] pub links: Vec<String>,
|
pub title: String,
|
||||||
#[serde(default)] pub id: String,
|
#[serde(default)]
|
||||||
|
pub links: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub connections: Option<Vec<Edge>>,
|
pub connections: Option<Vec<Edge>>,
|
||||||
|
|
@ -24,9 +29,12 @@ pub struct Node {
|
||||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||||
pub struct Edge {
|
pub struct Edge {
|
||||||
pub to: String,
|
pub to: String,
|
||||||
#[serde(default)] pub anchor: String,
|
#[serde(default)]
|
||||||
#[serde(default)] pub from: String,
|
pub anchor: String,
|
||||||
#[serde(default)] pub detached: bool,
|
#[serde(default)]
|
||||||
|
pub from: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub detached: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Graph {
|
impl Graph {
|
||||||
|
|
@ -35,8 +43,10 @@ impl Graph {
|
||||||
nodes: HashMap::new(),
|
nodes: HashMap::new(),
|
||||||
root_node: "VoidNode".to_string(),
|
root_node: "VoidNode".to_string(),
|
||||||
incoming: HashMap::new(),
|
incoming: HashMap::new(),
|
||||||
messages: vec![message
|
messages: vec![
|
||||||
.unwrap_or("This graph is empty or in error".to_string())],
|
message
|
||||||
|
.unwrap_or("This graph is empty or in error".to_string()),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +62,7 @@ impl Node {
|
||||||
title: "Pure Void".to_string(),
|
title: "Pure Void".to_string(),
|
||||||
text: match message {
|
text: match message {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => "Node is empty, missing or wasn't found.".to_string()
|
None => "Node is empty, missing or wasn't found.".to_string(),
|
||||||
},
|
},
|
||||||
connections: None,
|
connections: None,
|
||||||
links: vec![],
|
links: vec![],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue