// // Copyright 2026 James Pace // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // This Source Code Form is "Incompatible With Secondary Licenses", as // defined by the Mozilla Public License, v. 2.0. // #![no_std] #![allow(unused_imports)] extern crate alloc; mod diagnostic_node; mod diagnostic_status; mod error; mod name_parsing; use alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use alloc::collections::VecDeque; use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; pub use crate::diagnostic_status::*; pub use crate::error::*; use crate::diagnostic_node::*; use crate::name_parsing::*; pub struct DiagnosticGraph { graph: limbo_graph::Graph, } impl DiagnosticGraph { pub fn new() -> Self { Self { graph: limbo_graph::Graph::::new(DiagnosticNode::Root), } } pub fn root(&self) -> limbo_graph::Key { return self.graph.root_key(); } pub fn value_of(&self, key: &limbo_graph::Key) -> Result { let graph_value = self.graph.value_of(key)?; let result = graph_value.value()?; Ok(result.clone()) } pub fn children_of(&self, key: &limbo_graph::Key) -> Result> { let result = self.graph.children_of(key)?; Ok(result) } pub fn child_of_which_has_name( &self, parent: &limbo_graph::Key, desired_child_name: &str, ) -> Result> { if !name_is_basic(&desired_child_name.to_owned()) { return Err(Error::from_msg("desired child name must be basic.")); } let all_children = self.children_of(parent)?; for child in all_children.iter() { let child_name = self.value_of(&child)?.name(); if child_name == desired_child_name { return Ok(Some(child.clone())); } } return Ok(None); } pub fn key_from_full_name(&self, full_name: &String) -> Result> { let name_as_vec = split_name(&full_name); let mut cur_base_key = self.graph.root_key(); for cur_name in name_as_vec.iter() { let key_of_cur_name = self.child_of_which_has_name(&cur_base_key, &cur_name)?; if key_of_cur_name.is_none() { return Ok(None); } cur_base_key = key_of_cur_name.unwrap(); } return Ok(Some(cur_base_key)); } pub fn full_name_from_key(&self, key: &limbo_graph::Key) -> Result { let path_to_key = self.graph.backtrack_from_key(key)?; let mut full_name = String::new(); for curr_key in path_to_key.iter() { if *curr_key == self.graph.root_key() { continue; } let status = self.value_of(&curr_key)?; full_name = full_name + "/" + &status.name(); } Ok(full_name) } pub fn value_from_name( &self, full_name: &String, ) -> Result> { let key = self.key_from_full_name(full_name)?; if key.is_none() { return Ok(None); } let key = key.unwrap(); let value = self.value_of(&key)?; return Ok(Some((value, key))); } pub fn add_status(&mut self, status: DiagnosticStatus) -> Result<()> { // If I'm basic I just need to be added as a child of the root. if status.name_is_basic() { self.graph.add( DiagnosticNode::DiagnosticStatus(status), self.graph.root_key(), )?; return Ok(()); } // Find this status' child name and parent's names let parent_names = status.get_parent_names(); let mut cur_key = self.graph.root_key(); 'parent_loop: for parent_name in parent_names.iter() { let children = self.graph.children_of(&cur_key)?; // There are no children of this parent, so we can add // the parent and go to the next parent. if children.len() == 0 { let holder_for_parent = DiagnosticStatus::from_name(parent_name.clone()); cur_key = self .graph .add(DiagnosticNode::DiagnosticStatus(holder_for_parent), cur_key)?; continue 'parent_loop; } // This parent does have children, look at them and // update loop if appropriate. for child in children { let child_node = self.graph.value_of(&child)?; if child_node.is_root() { // A child node can't be root. return Err(Error::from_msg("A child node can't be root!")); } if child_node.value()?.name() == *parent_name { // The child node matched the parent we were looking for. // Continue to find the next parent, with this key as the // current key. cur_key = child; continue 'parent_loop; } } // Parent wasn't any of the children. Add it and look for next parent. let holder_for_parent = DiagnosticStatus::from_name(parent_name.clone()); cur_key = self .graph .add(DiagnosticNode::DiagnosticStatus(holder_for_parent), cur_key)?; } // We've updated all the parents, so we can add ourselves now. // Do we already exist as a child of parent? let children_of_this_parent = self.graph.children_of(&cur_key)?; let mut names_of_children_of_this_parent = children_of_this_parent.iter().map(|x| { self.graph .value_of(&x) .expect("Given child not in graph.") .value() .expect("Given root node as child?") .name() }); let child_in_children_of_this_parent = names_of_children_of_this_parent.any(|x| x == status.get_child_name()); if !child_in_children_of_this_parent { // If we don't we can be added. self.graph.add( DiagnosticNode::DiagnosticStatus(status.copy_with_child_name()), cur_key, )?; } Ok(()) } pub fn add_status_vec(&mut self, statuses: &Vec) -> Result<()> { for status in statuses { self.add_status(status.clone())?; } Ok(()) } pub fn reconcile_levels(&mut self) -> Result<()> { let leaf_keys = self.graph.find_leaf_keys()?; 'leaf_key_loop: for leaf_key in leaf_keys.iter() { let mut child_key = leaf_key.clone(); while child_key != self.graph.root_key() { let parent_key_opt = self.graph.parent_of(&child_key)?; if parent_key_opt == None || parent_key_opt == Some(self.graph.root_key()) { continue 'leaf_key_loop; } let parent_key = parent_key_opt.unwrap(); // Reconcile the parent values. let parent_node = self.graph.value_of(&parent_key)?; let parent_value = parent_node.value()?; let child_node = self.graph.value_of(&child_key)?; let child_value = child_node.value()?; // if child level is worse than parent key. // reset parent level. if child_value.level() > parent_value.level() { let replacement_parent = DiagnosticNode::from_status( parent_value.copy_with_new_level(child_value.level()), ); self.graph .replace_value_of(&parent_key, replacement_parent)?; } // Push up the graph. child_key = parent_key; } } Ok(()) } pub fn export_graph(&self) -> Result> { let keys_in_order = self.graph.get_keys_by_depth()?; let mut statuses = Vec::::new(); for key in keys_in_order.iter() { let status_at_key = self.value_of(&key)?; statuses.push(status_at_key.copy_with_new_name(self.full_name_from_key(&key)?)); } Ok(statuses) } } #[cfg(test)] mod tests { use super::*; use alloc::vec; fn make_a_status_with_name(name: &str) -> DiagnosticStatus { let level = DiagnosticLevel::OK; let message = "I'm ok"; let hardware_id = ""; let values = BTreeMap::::new(); DiagnosticStatus::new( level, name.to_owned(), message.to_owned(), hardware_id.to_owned(), values, ) } fn make_a_status_with_name_and_level(name: &str, level: DiagnosticLevel) -> DiagnosticStatus { let level = level; let message = ""; let hardware_id = ""; let values = BTreeMap::::new(); DiagnosticStatus::new( level, name.to_owned(), message.to_owned(), hardware_id.to_owned(), values, ) } #[test] fn add_one_to_graph() -> Result<()> { let mut graph = DiagnosticGraph::new(); graph.add_status(make_a_status_with_name("/a/b/c"))?; let first_child_keys = graph.children_of(&graph.root())?; assert!(first_child_keys.len() == 1); let first_child_key = first_child_keys[0]; let first_child_value = graph.value_of(&first_child_key)?; assert!(first_child_value.name() == "a"); let second_child_keys = graph.children_of(&first_child_key)?; assert!(second_child_keys.len() == 1); let second_child_key = second_child_keys[0]; let second_child_value = graph.value_of(&second_child_key)?; assert!(second_child_value.name() == "b"); let third_child_keys = graph.children_of(&second_child_key)?; assert!(third_child_keys.len() == 1); let third_child_key = third_child_keys[0]; let third_child_value = graph.value_of(&third_child_key)?; assert!(third_child_value.name() == "c"); assert!(third_child_value.level() == DiagnosticLevel::OK); Ok(()) } #[test] fn add_multiple_to_graph() -> Result<()> { let statuses = vec![ make_a_status_with_name("/a"), make_a_status_with_name("/a/b"), make_a_status_with_name("/a/b/c"), make_a_status_with_name("/a/d/e"), make_a_status_with_name("/a/d/f"), make_a_status_with_name("/a/d"), ]; let mut graph = DiagnosticGraph::new(); graph.add_status_vec(&statuses)?; let root_children = graph.children_of(&graph.root())?; assert!(root_children.len() == 1); assert!(graph.value_of(&root_children[0])?.name() == "a"); let children_of_a = graph.children_of(&root_children[0])?; let mut children_of_a_names = children_of_a .iter() .map(|x| graph.value_of(&x).expect("Value of failed.").name()); assert!(children_of_a.len() == 2); assert!(children_of_a_names.any(|x| x == "b")); assert!(children_of_a_names.any(|x| x == "d")); let b_key = graph.child_of_which_has_name(&root_children[0], "b")?; assert!(b_key.is_some()); let b_children = graph.children_of(&b_key.unwrap())?; assert!(b_children.len() == 1); let d_key = graph.child_of_which_has_name(&root_children[0], "d")?; assert!(d_key.is_some()); let d_children = graph.children_of(&d_key.unwrap())?; assert!(d_children.len() == 2); Ok(()) } #[test] fn key_name_connections() -> Result<()> { let statuses = vec![ make_a_status_with_name("/a"), make_a_status_with_name("/a/b"), make_a_status_with_name("/a/d/e"), make_a_status_with_name("/a/d"), ]; let mut graph = DiagnosticGraph::new(); graph.add_status_vec(&statuses)?; let key_for_a = graph.key_from_full_name(&"/a".to_owned())?; assert!(key_for_a == Some(1)); let key_for_e = graph.key_from_full_name(&"/a/d/e".to_owned())?; assert!(key_for_e == Some(4)); let not_in_graph = graph.key_from_full_name(&"/a/b/c".to_owned())?; assert!(not_in_graph == None); let name_for_e = graph.full_name_from_key(&4)?; assert!(name_for_e == "/a/d/e".to_owned()); Ok(()) } #[test] fn reconcile_graph() -> Result<()> { let statuses = vec![ make_a_status_with_name_and_level("/a", DiagnosticLevel::UNSET), make_a_status_with_name_and_level("/a/b", DiagnosticLevel::UNSET), make_a_status_with_name_and_level("/a/b/c", DiagnosticLevel::OK), make_a_status_with_name_and_level("/a/d/e", DiagnosticLevel::WARN), make_a_status_with_name_and_level("/a/d", DiagnosticLevel::WARN), make_a_status_with_name_and_level("/a/d/f", DiagnosticLevel::OK), ]; let mut graph = DiagnosticGraph::new(); graph.add_status_vec(&statuses)?; graph.reconcile_levels()?; let (a_status, _) = graph.value_from_name(&"/a".to_owned())?.unwrap(); assert!(a_status.level() == DiagnosticLevel::WARN); let (d_status, _) = graph.value_from_name(&"/a/d".to_owned())?.unwrap(); assert!(d_status.level() == DiagnosticLevel::WARN); Ok(()) } }