Add handlers for smart-case, spaced node queries

This commit is contained in:
Juno Takano 2026-01-06 04:10:49 -03:00
commit be6578bee2
5 changed files with 53 additions and 22 deletions

View file

@ -2,7 +2,14 @@ use axum::{routing::get, Router};
use crate::{syntax::serial::Format, types::Graph};
mod handlers;
mod handlers {
pub mod graph;
pub mod template;
pub mod raw;
pub mod navigation;
pub mod fixed;
pub mod error;
}
pub fn new(graph: &Graph) -> Router {
let mut router = Router::default()
@ -11,6 +18,7 @@ pub fn new(graph: &Graph) -> Router {
get(|| handlers::navigation::page("index.html"))
.post(handlers::navigation::search),
)
.route("/redirect", get(handlers::navigation::redirect))
.route(
"/static/style.css",
get(|| handlers::fixed::file("./static/style.css", "text/css")),
@ -38,6 +46,8 @@ pub fn new(graph: &Graph) -> Router {
}
if graph.meta.config.raw {
router = router
.route("/data", get(|| handlers::navigation::page("data.html")));
if graph.meta.config.raw_json {
router = router.route(
"/graph/json",

View file

@ -1,6 +0,0 @@
pub mod graph;
pub mod template;
pub mod raw;
pub mod navigation;
pub mod fixed;
pub mod error;

View file

@ -1,4 +1,3 @@
use crate::prelude::*;
use axum::response::IntoResponse as _;
use axum::{body::Body, extract::Path, http::Response, response::Redirect};
@ -8,9 +7,12 @@ 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_match, exact) = graph.find_node(&id);
let node = node_match.unwrap_or(empty_node.clone());
let result = graph.find_node(&id);
let nodes: Vec<Node> = graph.nodes.into_values().collect();
let not_found = result.node.is_none();
let node = result
.node
.unwrap_or(Node::new(Some(format!("Could not find node ID {id}."))));
if !node.redirect.is_empty() {
return Redirect::permanent(
@ -19,20 +21,18 @@ pub async fn node(Path(id): Path<String>) -> Response<Body> {
.into_response();
}
if !exact {
log!("Redirecting non-exact match to {}", node.id);
if result.redirect {
return Redirect::permanent(format!("/node/{}", node.id).as_str())
.into_response();
}
let mut context = tera::Context::default();
context.insert("node", &node);
context.insert("nodes", &nodes);
context.insert("text", &content::parse(&node.text, &graph.meta.config));
context.insert("incoming", &graph.incoming.get(&id));
context.insert("config", &graph.meta.config);
let not_found = node == empty_node;
handlers::template::by_filename(
"node.html",
&context,

View file

@ -25,6 +25,10 @@ pub async fn search(Form(query): Form<Query>) -> Redirect {
Redirect::permanent(format!("/node/{}", query.node).as_str())
}
pub async fn redirect(Form(query): Form<Query>) -> Redirect {
Redirect::permanent(format!("/node/{}", query.node).as_str())
}
#[derive(serde::Deserialize)]
pub struct Query {
node: String,
@ -63,4 +67,13 @@ mod tests {
let response = page("HBvcwqT8wLk6hxk1GdvNcEzJ6IiZ2Fod").await;
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn id_redirect() {
let query = Form(Query {
node: String::from("ancHOr syntaX"),
});
let response = search(query).await;
assert!(response.status_code() == StatusCode::PERMANENT_REDIRECT);
}
}

View file

@ -121,6 +121,11 @@ fn mk8() -> u16 {
8
}
pub struct QueryResult {
pub node: Option<Node>,
pub redirect: bool,
}
impl Graph {
pub fn new(message: Option<&str>) -> Graph {
Graph {
@ -136,17 +141,26 @@ impl Graph {
}
}
pub fn find_node(&self, query: &str) -> (Option<Node>, bool) {
pub fn find_node(&self, query: &str) -> QueryResult {
let collapsed_query = query.trim().replace(" ", "");
if let Some(exact_match) = self.nodes.get(query) {
(Some(exact_match.clone()), true)
QueryResult {
node: Some(exact_match.clone()),
redirect: false,
}
} else if let Some(lower_key) =
self.lowercase_keymap.get(&collapsed_query)
self.lowercase_keymap.get(&collapsed_query.to_lowercase())
{
(self.nodes.get(lower_key).cloned(), false)
QueryResult {
node: self.nodes.get(lower_key).cloned(),
redirect: true,
}
} else {
(None, false)
QueryResult {
node: None,
redirect: false,
}
}
}
@ -162,7 +176,7 @@ impl Node {
title: "Not Found".to_string(),
text: match message {
Some(s) => s,
None => "Node is empty, missing or wasn't found.".to_string(),
None => "Node not found.".to_string(),
},
connections: None,
links: vec![],
@ -233,7 +247,7 @@ mod tests {
#[test]
fn empty_node_message() {
let node = Node::new(None);
assert_eq!(node.text, "Node is empty, missing or wasn't found.");
assert_eq!(node.text, "Node not found.");
}
#[test]