Break up 'types' module

This commit is contained in:
Juno Takano 2026-01-11 21:31:51 -03:00
commit db8c02df04
36 changed files with 382 additions and 329 deletions

View file

@ -1,324 +0,0 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use crate::syntax::content;
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
pub struct Graph {
pub nodes: HashMap<String, Node>,
pub root_node: String,
#[serde(skip_deserializing)]
pub incoming: HashMap<String, Vec<Edge>>,
#[serde(skip_deserializing)]
pub lowercase_keymap: HashMap<String, String>,
#[serde(default)]
pub meta: Meta,
}
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
pub struct Node {
#[serde(default)]
pub id: String,
#[serde(default)]
pub text: String,
#[serde(default)]
pub summary: String,
#[serde(default)]
pub title: String,
#[serde(default)]
pub links: Vec<String>,
#[serde(default)]
pub redirect: String,
#[serde(default)]
pub hidden: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub connections: Option<Vec<Edge>>,
}
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
pub struct Edge {
pub to: String,
#[serde(default)]
pub anchor: String,
#[serde(default)]
pub from: String,
#[serde(default)]
pub detached: bool,
}
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
pub struct Meta {
pub config: Config,
#[serde(default = "mkversion")]
pub version: (u8, u8, u8),
#[serde(default)]
pub messages: Vec<String>,
}
// See: https://github.com/serde-rs/serde/issues/368
fn mkversion() -> (u8, u8, u8) {
(0, 0, 0)
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Config {
#[serde(default)]
_private: bool,
#[serde(default = "mktrue")]
pub about: bool,
#[serde(default)]
pub about_text: String,
#[serde(default = "mkfalse")]
pub ascii_dom_ids: bool,
#[serde(default)]
pub content_language: String,
#[serde(default = "mkfalse")]
error_poem: bool,
#[serde(default = "mktrue")]
pub footer: bool,
#[serde(default = "mktrue")]
pub footer_credits: bool,
#[serde(default = "mktrue")]
pub footer_date: bool,
#[serde(default)]
pub footer_text: String,
#[serde(default = "mk8")]
pub index_node_count: u16,
#[serde(default = "mktrue")]
pub index_node_list: bool,
#[serde(default = "mktrue")]
pub index_root_node: bool,
#[serde(default = "mktrue")]
pub index_search: bool,
#[serde(default)]
node_selector: bool,
#[serde(default)]
navbar_search: bool,
#[serde(default = "mktrue")]
pub raw: bool,
#[serde(default = "mktrue")]
pub raw_json: bool,
#[serde(default = "mktrue")]
pub raw_toml: bool,
#[serde(default)]
pub site_description: String,
#[serde(default)]
pub site_title: String,
#[serde(default = "mktrue")]
pub tree: bool,
#[serde(default = "mkfalse")]
pub tree_node_summary: bool,
}
// See: https://github.com/serde-rs/serde/issues/368
fn mktrue() -> bool {
true
}
fn mkfalse() -> bool {
false
}
fn mk8() -> u16 {
8
}
#[derive(Clone)]
pub struct QueryResult {
pub node: Option<Node>,
pub redirect: bool,
}
impl Graph {
pub fn new(message: Option<&str>) -> Graph {
Graph {
nodes: HashMap::default(),
root_node: "VoidNode".to_string(),
incoming: HashMap::default(),
lowercase_keymap: HashMap::default(),
meta: Meta {
config: Config::default(),
version: (0, 1, 0),
messages: message.map_or(vec![], |m| vec![m.to_string()]),
},
}
}
pub fn find_node(&self, query: &str) -> QueryResult {
let collapsed_query = query.trim().replace(" ", "");
let candidate = if let Some(exact_match) = self.nodes.get(query) {
QueryResult {
node: Some(exact_match.clone()),
redirect: false,
}
} else if let Some(lower_key) =
self.lowercase_keymap.get(&collapsed_query.to_lowercase())
{
QueryResult {
node: self.nodes.get(lower_key).cloned(),
redirect: true,
}
} else {
QueryResult {
node: None,
redirect: false,
}
};
if let Some(ref candidate_node) = candidate.node
&& !candidate_node.redirect.is_empty()
{
QueryResult {
node: self.find_node(&candidate_node.redirect).node,
redirect: true,
}
} else {
candidate
}
}
pub fn get_root(&self) -> Option<Node> {
self.nodes.get(&self.root_node).cloned()
}
pub fn parse(&mut self) {
self.meta.config.footer_text =
content::parse(&self.meta.config.footer_text, self);
self.meta.config.about_text =
content::parse(&self.meta.config.about_text, self);
}
}
impl Node {
pub fn new(message: Option<String>) -> Node {
Node {
id: "404".to_string(),
title: "Not Found".to_string(),
text: match message {
Some(s) => s,
None => "Node not found.".to_string(),
},
connections: None,
links: vec![],
redirect: String::default(),
hidden: false,
summary: String::default(),
}
}
}
impl Default for Config {
fn default() -> Config {
Config {
_private: true,
about: true,
about_text: String::default(),
ascii_dom_ids: false,
content_language: String::default(),
error_poem: false,
footer: true,
footer_credits: true,
footer_date: true,
footer_text: String::default(),
index_node_count: 8,
index_node_list: true,
index_root_node: true,
index_search: true,
node_selector: true,
navbar_search: true,
raw: true,
raw_json: true,
raw_toml: true,
site_description: String::default(),
site_title: String::default(),
tree: true,
tree_node_summary: false,
}
}
}
#[cfg(test)]
mod tests {
use crate::syntax::serial::populate_graph;
use super::*;
#[test]
fn empty_graph() {
let graph = Graph::new(Some("ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj"));
assert!(graph.nodes.is_empty());
assert!(graph.incoming.is_empty());
assert_eq!(
graph.meta.messages.first().unwrap(),
"ISryQFd9peG6eYz9CFRQFWeD1GnPo0oj"
);
}
#[test]
fn empty_node_message() {
let node = Node::new(None);
assert_eq!(node.text, "Node not found.");
}
#[test]
fn empty_footer_text() {
let mut graph = populate_graph();
graph.meta.config = Config {
footer_text: String::default(),
..graph.meta.config
};
graph.parse();
println!("{:?}", graph.meta.config.footer_text);
assert!(graph.meta.config.footer_text.is_empty());
}
#[test]
fn config_footer_text() {
let payload = "0kqBrdS8NPrU4xVxh2xW0hUzAw926JCQ";
let mut graph = populate_graph();
graph.meta.config = Config {
footer_text: format!("`{payload}`"),
..graph.meta.config
};
graph.parse();
assert!(
graph
.meta
.config
.footer_text
.matches(format!("<code>{payload}</code>").as_str())
.count()
== 1
);
}
#[test]
fn config_about_text() {
let payload = "ZqPFl84JlzSS0QUo61RwTUPONIE78Lmw";
let mut graph = populate_graph();
graph.meta.config = Config {
about_text: format!("`{payload}`"),
..graph.meta.config
};
graph.parse();
assert!(
graph
.meta
.config
.about_text
.matches(format!("<code>{payload}</code>").as_str())
.count()
== 1
);
}
}