use std::collections::HashMap;
use std::sync::Arc;
use crate::graph::prune_graph as core_prune_graph;
use crate::graph::{StaticEdge, StaticGraph, StaticNode, StaticNodegroup, StaticTile};
use crate::instance_wrapper_core::ModelAccess;
use crate::permissions::PermissionRule;
#[derive(Clone)]
pub struct GraphModelAccess {
graph: Arc<StaticGraph>,
nodes: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
nodes_by_alias: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
edges: Option<Arc<HashMap<String, Vec<String>>>>,
reverse_edges: Option<Arc<HashMap<String, Vec<String>>>>,
nodes_by_nodegroup: Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>>,
nodegroups: Option<Arc<HashMap<String, Arc<StaticNodegroup>>>>,
root_node_id: Option<String>,
permitted_nodegroups: HashMap<String, PermissionRule>,
default_allow: bool,
}
impl GraphModelAccess {
pub fn new(graph: Arc<StaticGraph>, default_allow: bool) -> Self {
GraphModelAccess {
graph,
nodes: None,
nodes_by_alias: None,
edges: None,
reverse_edges: None,
nodes_by_nodegroup: None,
nodegroups: None,
root_node_id: None,
permitted_nodegroups: HashMap::new(),
default_allow,
}
}
pub fn new_eager(graph: Arc<StaticGraph>, default_allow: bool) -> Self {
let mut access = Self::new(graph, default_allow);
access
.build_indices()
.expect("Failed to build graph indices");
access
}
pub fn from_graph(graph: &StaticGraph) -> Self {
Self::new_eager(Arc::new(graph.clone()), true)
}
pub fn ensure_built(&mut self) -> Result<(), String> {
if self.nodes.is_none() {
self.build_indices()?;
}
Ok(())
}
pub fn is_built(&self) -> bool {
self.nodes.is_some()
}
pub fn invalidate_caches(&mut self) {
self.nodes = None;
self.nodes_by_alias = None;
self.edges = None;
self.reverse_edges = None;
self.nodes_by_nodegroup = None;
self.nodegroups = None;
self.root_node_id = None;
}
fn build_indices(&mut self) -> Result<(), String> {
let graph = &self.graph;
let mut nodes: HashMap<String, Arc<StaticNode>> = HashMap::new();
let mut nodes_by_alias: HashMap<String, Arc<StaticNode>> = HashMap::new();
let mut edges_map: HashMap<String, Vec<String>> = HashMap::new();
let mut reverse_edges_map: HashMap<String, Vec<String>> = HashMap::new();
let mut nodes_by_nodegroup: HashMap<String, Vec<Arc<StaticNode>>> = HashMap::new();
let mut nodegroups: HashMap<String, Arc<StaticNodegroup>> = HashMap::new();
let mut root_node_id = String::new();
for node in &graph.nodes {
let mut node_copy = node.clone();
if (node_copy.nodegroup_id.is_none()
|| node_copy
.nodegroup_id
.as_ref()
.map(|s| s.is_empty())
.unwrap_or(false))
&& node_copy.alias.is_none()
{
node_copy.alias = Some(String::new());
}
let arc_node = Arc::new(node_copy);
nodes.insert(arc_node.nodeid.clone(), Arc::clone(&arc_node));
if let Some(ref alias) = arc_node.alias {
if !alias.is_empty() {
nodes_by_alias.insert(alias.clone(), Arc::clone(&arc_node));
} else {
nodes_by_alias.insert(String::new(), Arc::clone(&arc_node));
}
} else {
nodes_by_alias.insert(String::new(), Arc::clone(&arc_node));
}
if arc_node.istopnode {
root_node_id = arc_node.nodeid.clone();
}
if let Some(ref ng_id) = arc_node.nodegroup_id {
nodes_by_nodegroup
.entry(ng_id.clone())
.or_default()
.push(Arc::clone(&arc_node));
}
}
if root_node_id.is_empty() {
for node in nodes.values() {
if node.nodegroup_id.is_none()
|| node
.nodegroup_id
.as_ref()
.map(|s| s.is_empty())
.unwrap_or(true)
{
root_node_id = node.nodeid.clone();
break;
}
}
}
for edge in &graph.edges {
let parent_id = edge.domainnode_id.clone();
let child_id = edge.rangenode_id.clone();
edges_map
.entry(parent_id.clone())
.or_default()
.push(child_id.clone());
reverse_edges_map
.entry(child_id)
.or_default()
.push(parent_id);
}
for node in &graph.nodes {
if let Some(ref ng_id) = node.nodegroup_id {
if !ng_id.is_empty() && !nodegroups.contains_key(ng_id) {
nodegroups.insert(
ng_id.clone(),
Arc::new(StaticNodegroup {
cardinality: Some("n".to_string()),
legacygroupid: None,
nodegroupid: ng_id.clone(),
parentnodegroup_id: None,
grouping_node_id: None,
}),
);
}
}
}
for ng in &graph.nodegroups {
nodegroups.insert(ng.nodegroupid.clone(), Arc::new(ng.clone()));
}
self.nodes = Some(Arc::new(nodes));
self.nodes_by_alias = Some(Arc::new(nodes_by_alias));
self.edges = Some(Arc::new(edges_map));
self.reverse_edges = Some(Arc::new(reverse_edges_map));
self.nodes_by_nodegroup = Some(Arc::new(nodes_by_nodegroup));
self.nodegroups = Some(Arc::new(nodegroups));
self.root_node_id = Some(root_node_id);
if self.permitted_nodegroups.is_empty() {
for key in self.nodegroups.as_ref().unwrap().keys() {
self.permitted_nodegroups
.insert(key.clone(), PermissionRule::Boolean(self.default_allow));
}
self.permitted_nodegroups
.insert(String::new(), PermissionRule::Boolean(true));
}
Ok(())
}
pub fn get_nodes_internal(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
self.nodes.as_ref().map(|arc| arc.as_ref())
}
pub fn get_nodes_by_alias_internal(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
self.nodes_by_alias.as_ref().map(|arc| arc.as_ref())
}
pub fn get_edges_internal(&self) -> Option<&HashMap<String, Vec<String>>> {
self.edges.as_ref().map(|arc| arc.as_ref())
}
pub fn get_reverse_edges_internal(&self) -> Option<&HashMap<String, Vec<String>>> {
self.reverse_edges.as_ref().map(|arc| arc.as_ref())
}
pub fn get_nodes_by_nodegroup_internal(
&self,
) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>> {
self.nodes_by_nodegroup.as_ref().map(|arc| arc.as_ref())
}
pub fn get_nodegroups_internal(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>> {
self.nodegroups.as_ref().map(|arc| arc.as_ref())
}
pub fn get_nodes_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNode>>>> {
self.nodes.as_ref().map(Arc::clone)
}
pub fn get_nodes_by_alias_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNode>>>> {
self.nodes_by_alias.as_ref().map(Arc::clone)
}
pub fn get_edges_arc(&self) -> Option<Arc<HashMap<String, Vec<String>>>> {
self.edges.as_ref().map(Arc::clone)
}
pub fn get_reverse_edges_arc(&self) -> Option<Arc<HashMap<String, Vec<String>>>> {
self.reverse_edges.as_ref().map(Arc::clone)
}
pub fn get_nodes_by_nodegroup_arc(&self) -> Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>> {
self.nodes_by_nodegroup.as_ref().map(Arc::clone)
}
pub fn get_nodegroups_arc(&self) -> Option<Arc<HashMap<String, Arc<StaticNodegroup>>>> {
self.nodegroups.as_ref().map(Arc::clone)
}
pub fn get_node_objects(&mut self) -> Result<&HashMap<String, Arc<StaticNode>>, String> {
self.ensure_built()?;
self.nodes
.as_ref()
.map(|arc| arc.as_ref())
.ok_or_else(|| "Could not build nodes".to_string())
}
pub fn get_node_objects_by_alias(
&mut self,
) -> Result<&HashMap<String, Arc<StaticNode>>, String> {
self.ensure_built()?;
self.nodes_by_alias
.as_ref()
.map(|arc| arc.as_ref())
.ok_or_else(|| "Could not build nodes".to_string())
}
pub fn get_edges(&mut self) -> Result<&HashMap<String, Vec<String>>, String> {
self.ensure_built()?;
self.edges
.as_ref()
.map(|arc| arc.as_ref())
.ok_or_else(|| "Could not build edges".to_string())
}
pub fn get_nodegroup_objects(
&mut self,
) -> Result<&HashMap<String, Arc<StaticNodegroup>>, String> {
self.ensure_built()?;
self.nodegroups
.as_ref()
.map(|arc| arc.as_ref())
.ok_or_else(|| "Could not build nodegroups".to_string())
}
pub fn get_root_node_mut(&mut self) -> Result<Arc<StaticNode>, String> {
self.ensure_built()?;
let root_id = self
.root_node_id
.as_ref()
.ok_or_else(|| "Root node ID not set".to_string())?;
self.nodes
.as_ref()
.and_then(|n| n.get(root_id))
.cloned()
.ok_or_else(|| "Root node not found in nodes cache".to_string())
}
pub fn get_child_nodes_mut(
&mut self,
node_id: &str,
) -> Result<HashMap<String, Arc<StaticNode>>, String> {
self.ensure_built()?;
ModelAccess::get_child_nodes(self, node_id)
}
pub fn get_child_nodes(&self, node_id: &str) -> HashMap<String, Arc<StaticNode>> {
ModelAccess::get_child_nodes(self, node_id).unwrap_or_default()
}
pub fn get_nodes_by_alias(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
self.nodes_by_alias.as_ref().map(|arc| arc.as_ref())
}
pub fn get_graph(&self) -> &StaticGraph {
&self.graph
}
pub fn get_graph_arc(&self) -> Arc<StaticGraph> {
Arc::clone(&self.graph)
}
pub fn get_default_allow(&self) -> bool {
self.default_allow
}
pub fn set_graph(&mut self, graph: Arc<StaticGraph>) {
self.graph = graph;
self.invalidate_caches();
}
pub fn set_graph_nodes(&mut self, nodes: Vec<StaticNode>) {
let mut graph = (*self.graph).clone();
graph.nodes = nodes;
self.graph = Arc::new(graph);
self.invalidate_caches();
}
pub fn set_graph_edges(&mut self, edges: Vec<StaticEdge>) {
let mut graph = (*self.graph).clone();
graph.edges = edges;
self.graph = Arc::new(graph);
self.invalidate_caches();
}
pub fn set_graph_nodegroups(&mut self, nodegroups: Vec<StaticNodegroup>) {
let mut graph = (*self.graph).clone();
graph.nodegroups = nodegroups;
self.graph = Arc::new(graph);
self.invalidate_caches();
}
pub fn rebuild_from_graph(&mut self, graph: &StaticGraph) {
self.graph = Arc::new(graph.clone());
self.invalidate_caches();
let _ = self.ensure_built();
}
pub fn set_permitted_nodegroups_rules(&mut self, permissions: HashMap<String, PermissionRule>) {
self.permitted_nodegroups = permissions;
}
pub fn set_permitted_nodegroups_bool(&mut self, permissions: HashMap<String, bool>) {
self.permitted_nodegroups = permissions
.into_iter()
.map(|(k, v)| (k, PermissionRule::Boolean(v)))
.collect();
}
pub fn set_default_allow(&mut self, default_allow: bool) {
self.default_allow = default_allow;
}
pub fn is_nodegroup_permitted(&self, nodegroup_id: &str) -> bool {
self.permitted_nodegroups
.get(nodegroup_id)
.map(|rule| rule.permits_nodegroup())
.unwrap_or(self.default_allow)
}
pub fn is_tile_permitted(&self, tile: &StaticTile) -> bool {
self.permitted_nodegroups
.get(&tile.nodegroup_id)
.map(|rule| rule.permits_tile(tile))
.unwrap_or(self.default_allow)
}
pub fn get_permission_rule(&self, nodegroup_id: &str) -> Option<&PermissionRule> {
self.permitted_nodegroups.get(nodegroup_id)
}
pub fn get_permitted_nodegroups_bool(&self) -> HashMap<String, bool> {
self.permitted_nodegroups
.iter()
.map(|(k, v)| (k.clone(), v.permits_nodegroup()))
.collect()
}
pub fn get_permitted_nodegroups_rules(&self) -> &HashMap<String, PermissionRule> {
&self.permitted_nodegroups
}
pub fn prune_graph(&mut self, keep_functions: Option<&[String]>) -> Result<(), String> {
let is_permitted = |ng_id: &str| self.is_nodegroup_permitted(ng_id);
let pruned = core_prune_graph(&self.graph, is_permitted, keep_functions)
.map_err(|e| e.to_string())?;
self.graph = Arc::new(pruned);
self.invalidate_caches();
self.ensure_built()?;
Ok(())
}
}
impl ModelAccess for GraphModelAccess {
fn get_nodes(&self) -> Option<&HashMap<String, Arc<StaticNode>>> {
self.nodes.as_ref().map(|arc| arc.as_ref())
}
fn get_edges(&self) -> Option<&HashMap<String, Vec<String>>> {
self.edges.as_ref().map(|arc| arc.as_ref())
}
fn get_reverse_edges(&self) -> Option<&HashMap<String, Vec<String>>> {
self.reverse_edges.as_ref().map(|arc| arc.as_ref())
}
fn get_nodes_by_nodegroup(&self) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>> {
self.nodes_by_nodegroup.as_ref().map(|arc| arc.as_ref())
}
fn get_nodegroups(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>> {
self.nodegroups.as_ref().map(|arc| arc.as_ref())
}
fn get_root_node(&self) -> Result<Arc<StaticNode>, String> {
let root_id = self
.root_node_id
.as_ref()
.ok_or_else(|| "Caches not built".to_string())?;
self.nodes
.as_ref()
.and_then(|n| n.get(root_id))
.cloned()
.ok_or_else(|| "Root node not found".to_string())
}
fn get_permitted_nodegroups(&self) -> HashMap<String, PermissionRule> {
self.permitted_nodegroups.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{StaticEdge, StaticNodegroup};
use crate::instance_wrapper_core::ModelAccess;
use serde_json::json;
use std::collections::HashSet;
fn create_test_graph() -> StaticGraph {
let root: StaticNode = serde_json::from_value(json!({
"nodeid": "root-id",
"name": "Root",
"alias": "root",
"datatype": "semantic",
"graph_id": "test-graph",
"is_collector": false,
"isrequired": false,
"issearchable": false,
"istopnode": true,
"sortorder": 0
}))
.unwrap();
let child_a: StaticNode = serde_json::from_value(json!({
"nodeid": "child-a-id",
"name": "Name",
"alias": "name",
"datatype": "string",
"nodegroup_id": "ng1",
"graph_id": "test-graph",
"is_collector": false,
"isrequired": false,
"issearchable": false,
"istopnode": false,
"sortorder": 0
}))
.unwrap();
let child_b: StaticNode = serde_json::from_value(json!({
"nodeid": "child-b-id",
"name": "Details",
"alias": "details",
"datatype": "semantic",
"nodegroup_id": "ng2",
"graph_id": "test-graph",
"is_collector": true,
"isrequired": false,
"issearchable": false,
"istopnode": false,
"sortorder": 1
}))
.unwrap();
let grandchild: StaticNode = serde_json::from_value(json!({
"nodeid": "grandchild-id",
"name": "Description",
"alias": "description",
"datatype": "string",
"nodegroup_id": "ng2",
"graph_id": "test-graph",
"is_collector": false,
"isrequired": false,
"issearchable": false,
"istopnode": false,
"sortorder": 0
}))
.unwrap();
let ng1 = StaticNodegroup {
cardinality: Some("n".to_string()),
legacygroupid: None,
nodegroupid: "ng1".to_string(),
parentnodegroup_id: None,
grouping_node_id: None,
};
let ng2 = StaticNodegroup {
cardinality: Some("1".to_string()),
legacygroupid: None,
nodegroupid: "ng2".to_string(),
parentnodegroup_id: None,
grouping_node_id: None,
};
let edge_root_a: StaticEdge = serde_json::from_value(json!({
"domainnode_id": "root-id",
"rangenode_id": "child-a-id",
"edgeid": "edge-1",
"graph_id": "test-graph"
}))
.unwrap();
let edge_root_b: StaticEdge = serde_json::from_value(json!({
"domainnode_id": "root-id",
"rangenode_id": "child-b-id",
"edgeid": "edge-2",
"graph_id": "test-graph"
}))
.unwrap();
let edge_b_gc: StaticEdge = serde_json::from_value(json!({
"domainnode_id": "child-b-id",
"rangenode_id": "grandchild-id",
"edgeid": "edge-3",
"graph_id": "test-graph"
}))
.unwrap();
serde_json::from_value(json!({
"graphid": "test-graph",
"name": {"en": "Test Graph"},
"root": root,
"nodes": [root.clone(), child_a, child_b, grandchild],
"edges": [edge_root_a, edge_root_b, edge_b_gc],
"nodegroups": [ng1, ng2]
}))
.unwrap()
}
#[test]
fn from_graph_builds_node_index() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let nodes = access.nodes.as_ref().unwrap();
assert_eq!(nodes.len(), 4);
assert!(nodes.contains_key("root-id"));
assert!(nodes.contains_key("child-a-id"));
assert!(nodes.contains_key("child-b-id"));
assert!(nodes.contains_key("grandchild-id"));
}
#[test]
fn from_graph_builds_alias_index() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let aliases = access.nodes_by_alias.as_ref().unwrap();
assert!(aliases.contains_key("name"));
assert!(aliases.contains_key("details"));
assert!(aliases.contains_key("description"));
assert_eq!(aliases.get("name").unwrap().nodeid, "child-a-id");
}
#[test]
fn from_graph_builds_edges() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let edges = access.edges.as_ref().unwrap();
let root_children = edges.get("root-id").unwrap();
assert_eq!(root_children.len(), 2);
assert!(root_children.contains(&"child-a-id".to_string()));
assert!(root_children.contains(&"child-b-id".to_string()));
let b_children = edges.get("child-b-id").unwrap();
assert_eq!(b_children, &vec!["grandchild-id".to_string()]);
}
#[test]
fn from_graph_builds_reverse_edges() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let rev = access.reverse_edges.as_ref().unwrap();
let gc_parents = rev.get("grandchild-id").unwrap();
assert_eq!(gc_parents, &vec!["child-b-id".to_string()]);
let a_parents = rev.get("child-a-id").unwrap();
assert_eq!(a_parents, &vec!["root-id".to_string()]);
}
#[test]
fn from_graph_builds_nodegroups() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let ngs = access.nodegroups.as_ref().unwrap();
assert!(ngs.contains_key("ng1"));
assert!(ngs.contains_key("ng2"));
assert_eq!(ngs.get("ng2").unwrap().cardinality, Some("1".to_string()));
}
#[test]
fn from_graph_identifies_root() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
assert_eq!(access.root_node_id.as_deref(), Some("root-id"));
}
#[test]
fn from_graph_sets_root_alias_when_missing() {
let mut graph = create_test_graph();
graph.nodes[0].alias = None;
graph.nodes[0].nodegroup_id = None;
let access = GraphModelAccess::from_graph(&graph);
let aliases = access.nodes_by_alias.as_ref().unwrap();
assert!(aliases.contains_key(""));
assert_eq!(aliases.get("").unwrap().nodeid, "root-id");
}
#[test]
fn get_child_nodes_returns_children_by_alias() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let children = access.get_child_nodes("root-id");
assert_eq!(children.len(), 2);
assert!(children.contains_key("name"));
assert!(children.contains_key("details"));
assert_eq!(children.get("name").unwrap().nodeid, "child-a-id");
}
#[test]
fn get_child_nodes_empty_for_leaf() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let children = access.get_child_nodes("grandchild-id");
assert!(children.is_empty());
}
#[test]
fn is_nodegroup_permitted_default_allow() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
assert!(access.is_nodegroup_permitted("ng1"));
let access2 = GraphModelAccess::new_eager(Arc::new(graph), false);
assert!(!access2.is_nodegroup_permitted("ng1"));
}
#[test]
fn is_nodegroup_permitted_explicit_deny() {
let graph = create_test_graph();
let mut access = GraphModelAccess::from_graph(&graph);
let mut perms = HashMap::new();
perms.insert("ng1".to_string(), PermissionRule::Boolean(true));
perms.insert("ng2".to_string(), PermissionRule::Boolean(false));
access.set_permitted_nodegroups_rules(perms);
assert!(access.is_nodegroup_permitted("ng1"));
assert!(!access.is_nodegroup_permitted("ng2"));
}
#[test]
fn is_nodegroup_permitted_conditional() {
let graph = create_test_graph();
let mut access = GraphModelAccess::from_graph(&graph);
let mut perms = HashMap::new();
perms.insert(
"ng1".to_string(),
PermissionRule::Conditional {
path: ".data.field".to_string(),
allowed: HashSet::from(["value1".to_string()]),
},
);
access.set_permitted_nodegroups_rules(perms);
assert!(access.is_nodegroup_permitted("ng1"));
}
#[test]
fn get_permitted_nodegroups_bool_returns_all_when_empty() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let perms = access.get_permitted_nodegroups_bool();
assert!(perms.contains_key("ng1"));
assert!(perms.contains_key("ng2"));
assert!(perms.contains_key(""));
assert!(perms.values().all(|&v| v));
}
#[test]
fn rebuild_from_graph_preserves_permissions() {
let graph = create_test_graph();
let mut access = GraphModelAccess::from_graph(&graph);
let mut perms = HashMap::new();
perms.insert("ng1".to_string(), PermissionRule::Boolean(false));
access.set_permitted_nodegroups_rules(perms);
access.rebuild_from_graph(&graph);
assert!(!access.is_nodegroup_permitted("ng1"));
}
#[test]
fn trait_get_root_node_returns_correct_node() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let root = access.get_root_node().unwrap();
assert_eq!(root.nodeid, "root-id");
assert!(root.istopnode);
}
#[test]
fn nodes_by_nodegroup_indexed_correctly() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let nbn = access.nodes_by_nodegroup.as_ref().unwrap();
let ng2_nodes = nbn.get("ng2").unwrap();
assert_eq!(ng2_nodes.len(), 2);
let node_ids: Vec<&str> = ng2_nodes.iter().map(|n| n.nodeid.as_str()).collect();
assert!(node_ids.contains(&"child-b-id"));
assert!(node_ids.contains(&"grandchild-id"));
}
#[test]
fn lazy_new_does_not_build_caches() {
let graph = create_test_graph();
let access = GraphModelAccess::new(Arc::new(graph), true);
assert!(!access.is_built());
assert!(access.get_nodes_internal().is_none());
assert!(access.get_edges_internal().is_none());
}
#[test]
fn ensure_built_populates_caches() {
let graph = create_test_graph();
let mut access = GraphModelAccess::new(Arc::new(graph), true);
assert!(!access.is_built());
access.ensure_built().unwrap();
assert!(access.is_built());
assert_eq!(access.get_nodes_internal().unwrap().len(), 4);
}
#[test]
fn ensure_built_is_idempotent() {
let graph = create_test_graph();
let mut access = GraphModelAccess::new(Arc::new(graph), true);
access.ensure_built().unwrap();
let ptr1 = Arc::as_ptr(access.nodes.as_ref().unwrap());
access.ensure_built().unwrap();
let ptr2 = Arc::as_ptr(access.nodes.as_ref().unwrap());
assert_eq!(ptr1, ptr2);
}
#[test]
fn invalidate_caches_clears_indices() {
let graph = create_test_graph();
let mut access = GraphModelAccess::from_graph(&graph);
assert!(access.is_built());
access.invalidate_caches();
assert!(!access.is_built());
assert!(access.get_nodes_internal().is_none());
}
#[test]
fn set_graph_nodes_invalidates_caches() {
let graph = create_test_graph();
let mut access = GraphModelAccess::from_graph(&graph);
assert_eq!(access.get_nodes_internal().unwrap().len(), 4);
let root_only: Vec<StaticNode> = graph
.nodes
.iter()
.filter(|n| n.istopnode)
.cloned()
.collect();
access.set_graph_nodes(root_only);
assert!(!access.is_built());
access.ensure_built().unwrap();
assert_eq!(access.get_nodes_internal().unwrap().len(), 1);
}
#[test]
fn arc_sharing_returns_same_allocation() {
let graph = create_test_graph();
let access = GraphModelAccess::from_graph(&graph);
let arc1 = access.get_nodes_arc().unwrap();
let arc2 = access.get_nodes_arc().unwrap();
assert!(Arc::ptr_eq(&arc1, &arc2));
}
#[test]
fn mutable_accessor_triggers_lazy_build() {
let graph = create_test_graph();
let mut access = GraphModelAccess::new(Arc::new(graph), true);
assert!(!access.is_built());
let nodes = access.get_node_objects().unwrap();
assert_eq!(nodes.len(), 4);
assert!(access.is_built());
}
}