1use std::collections::{HashMap, HashSet};
11use std::sync::{Arc, Mutex};
12
13use crate::path_resolution::{resolve_path_segments, PathError, PathResolutionInfo};
14use crate::permissions::PermissionRule;
15use crate::pseudo_value_core::{PseudoListCore, PseudoValueCore};
16use crate::{StaticNode, StaticNodegroup, StaticResourceMetadata, StaticTile};
17
18#[derive(Debug, Clone)]
24pub enum SemanticChildError {
25 TilesNotLoaded { nodegroup_id: String },
27 ChildNotFound { alias: String },
29 TilesNotInitialized,
31 ModelNotInitialized(String),
33 Other(String),
35}
36
37impl std::fmt::Display for SemanticChildError {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 SemanticChildError::TilesNotLoaded { nodegroup_id } => {
41 write!(f, "Tiles not loaded for nodegroup: {}", nodegroup_id)
42 }
43 SemanticChildError::ChildNotFound { alias } => {
44 write!(f, "Child node not found: {}", alias)
45 }
46 SemanticChildError::TilesNotInitialized => {
47 write!(f, "Tiles not initialized")
48 }
49 SemanticChildError::ModelNotInitialized(msg) => {
50 write!(f, "Model not initialized: {}", msg)
51 }
52 SemanticChildError::Other(msg) => {
53 write!(f, "{}", msg)
54 }
55 }
56 }
57}
58
59impl std::error::Error for SemanticChildError {}
60
61impl From<String> for SemanticChildError {
62 fn from(s: String) -> Self {
63 SemanticChildError::Other(s)
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum LoadState {
74 NotLoaded,
75 Loading,
76 Loaded,
77}
78
79#[derive(Clone, Debug)]
85pub struct ValuesFromNodegroupResult {
86 pub values: HashMap<String, PseudoListCore>,
88 pub implied_nodegroups: Vec<String>,
90}
91
92#[derive(Clone, Debug)]
94pub struct EnsureNodegroupResult {
95 pub values: HashMap<String, PseudoListCore>,
97 pub implied_nodegroups: Vec<String>,
99 pub all_nodegroups_map: HashMap<String, bool>,
101}
102
103#[derive(Clone, Debug)]
105pub struct PopulateResult {
106 pub values: HashMap<String, PseudoListCore>,
108 pub all_values_map: HashMap<String, Option<bool>>,
110 pub all_nodegroups_map: HashMap<String, bool>,
112}
113
114#[derive(Debug)]
116pub enum SemanticChildResult {
117 List(PseudoListCore),
119 Single(PseudoValueCore),
121 Empty,
123}
124
125pub trait ModelAccess {
136 fn get_nodes(&self) -> Option<&HashMap<String, Arc<StaticNode>>>;
138
139 fn get_edges(&self) -> Option<&HashMap<String, Vec<String>>>;
141
142 fn get_reverse_edges(&self) -> Option<&HashMap<String, Vec<String>>>;
144
145 fn get_nodes_by_nodegroup(&self) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>>;
147
148 fn get_nodegroups(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>>;
150
151 fn get_root_node(&self) -> Result<Arc<StaticNode>, String> {
155 let nodes = self
156 .get_nodes()
157 .ok_or_else(|| "Nodes not initialized".to_string())?;
158 for node in nodes.values() {
160 if node.istopnode {
161 return Ok(Arc::clone(node));
162 }
163 }
164 for node in nodes.values() {
166 if node.nodegroup_id.is_none()
167 || node
168 .nodegroup_id
169 .as_ref()
170 .map(|s| s.is_empty())
171 .unwrap_or(true)
172 {
173 return Ok(Arc::clone(node));
174 }
175 }
176 Err("Could not find root node".to_string())
177 }
178
179 fn get_child_nodes(&self, node_id: &str) -> Result<HashMap<String, Arc<StaticNode>>, String> {
182 let edges = self.get_edges().ok_or("Edges not initialized")?;
183 let nodes = self.get_nodes().ok_or("Nodes not initialized")?;
184 let child_ids = edges.get(node_id).cloned().unwrap_or_default();
185 let mut children = HashMap::new();
186 for child_id in child_ids {
187 if let Some(node) = nodes.get(&child_id) {
188 if let Some(ref alias) = node.alias {
189 if !alias.is_empty() {
190 children.insert(alias.clone(), Arc::clone(node));
191 }
192 }
193 }
194 }
195 Ok(children)
196 }
197
198 fn get_permitted_nodegroups(&self) -> HashMap<String, PermissionRule>;
201}
202
203pub fn is_node_single_cardinality_with<F>(node: &StaticNode, get_cardinality: F) -> bool
223where
224 F: Fn(&str) -> Option<String>,
225{
226 if let Some(ref ng_id) = node.nodegroup_id {
227 if &node.nodeid == ng_id {
229 if let Some(cardinality) = get_cardinality(ng_id) {
230 return cardinality != "n";
231 }
232 }
233 }
234 !node.is_collector
236}
237
238pub fn is_node_single_cardinality(
242 node: &StaticNode,
243 nodegroups: Option<&HashMap<String, Arc<StaticNodegroup>>>,
244) -> bool {
245 is_node_single_cardinality_with(node, |ng_id| {
246 nodegroups
247 .and_then(|ngs| ngs.get(ng_id))
248 .and_then(|ng| ng.cardinality.clone())
249 })
250}
251
252pub fn matches_semantic_child(
257 parent_tile_id: Option<&String>,
258 parent_nodegroup_id: Option<&String>,
259 child_node: &StaticNode,
260 tile: &StaticTile,
261) -> bool {
262 if tile.nodegroup_id != *child_node.nodegroup_id.as_ref().unwrap_or(&"".into()) {
264 return false;
265 }
266
267 let is_semantic = child_node.datatype == "semantic";
270 if !(Some(&child_node.nodeid) == child_node.nodegroup_id.as_ref()
271 || tile.data.contains_key(&child_node.nodeid)
272 || is_semantic)
273 {
274 return false;
275 }
276
277 let parent_ng = parent_nodegroup_id.map(|s| s.as_str()).unwrap_or("");
279
280 if tile.nodegroup_id != parent_ng {
283 if let Some(parent_tid) = parent_tile_id {
284 let parent_matches =
286 tile.parenttile_id.is_none() || tile.parenttile_id.as_ref() == Some(parent_tid);
287
288 if parent_matches {
289 return true;
290 }
291 } else {
292 if tile.parenttile_id.is_none() {
294 return true;
295 }
296 }
297 }
298
299 if tile.nodegroup_id == parent_ng {
302 if let Some(parent_tid) = parent_tile_id {
303 let has_data_or_is_semantic =
307 tile.data.contains_key(&child_node.nodeid) || child_node.datatype == "semantic";
308 if tile.tileid.as_ref() == Some(parent_tid)
309 && !child_node.is_collector
310 && has_data_or_is_semantic
311 {
312 return true;
313 }
314 }
315 }
316
317 if tile.nodegroup_id != parent_ng && child_node.is_collector {
320 if let Some(parent_tid) = parent_tile_id {
321 return tile.parenttile_id.is_none() || tile.parenttile_id.as_ref() == Some(parent_tid);
322 }
323 return true;
324 }
325
326 false
327}
328
329pub fn resolve_and_filter_tiles(
338 path: &str,
339 model: &dyn ModelAccess,
340 tiles_store: &HashMap<String, StaticTile>,
341 nodegroup_index: &HashMap<String, Vec<String>>,
342 filter_tile_id: Option<&str>,
343) -> Result<(PathResolutionInfo, Vec<StaticTile>), PathError> {
344 let root_node = model
345 .get_root_node()
346 .map_err(PathError::ModelNotInitialized)?;
347 let nodes = model
348 .get_nodes()
349 .ok_or_else(|| PathError::ModelNotInitialized("Nodes not initialized".into()))?;
350 let edges = model
351 .get_edges()
352 .ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
353 let nodegroups = model.get_nodegroups();
354
355 let info = resolve_path_segments(path, &root_node, nodes, edges, nodegroups)?;
356
357 let tile_ids = nodegroup_index
358 .get(&info.nodegroup_id)
359 .cloned()
360 .unwrap_or_default();
361
362 let tiles: Vec<StaticTile> = if let Some(filter_id) = filter_tile_id {
363 let filter_id_string = filter_id.to_string();
364 let parent_ng = info.parent_nodegroup_id.as_ref();
365 tile_ids
366 .iter()
367 .filter_map(|tid| {
368 tiles_store.get(tid).and_then(|t| {
369 if matches_semantic_child(
370 Some(&filter_id_string),
371 parent_ng,
372 &info.target_node,
373 t,
374 ) {
375 Some(t.clone())
376 } else {
377 None
378 }
379 })
380 })
381 .collect()
382 } else {
383 tile_ids
384 .iter()
385 .filter_map(|tid| tiles_store.get(tid).cloned())
386 .collect()
387 };
388
389 Ok((info, tiles))
390}
391
392type AliasTilesMap = HashMap<String, (Arc<StaticNode>, Vec<Option<Arc<StaticTile>>>)>;
398
399pub fn create_pseudo_list_from_tiles(
401 node: Arc<StaticNode>,
402 tiles: Vec<Option<Arc<StaticTile>>>,
403 edges: &HashMap<String, Vec<String>>,
404 is_single: bool,
405) -> PseudoListCore {
406 let alias = node.alias.clone().unwrap_or_default();
407 let child_node_ids = edges.get(&node.nodeid).cloned().unwrap_or_default();
408
409 let values: Vec<PseudoValueCore> = tiles
410 .into_iter()
411 .map(|tile| {
412 let tile_data = tile
413 .as_ref()
414 .and_then(|t| t.data.get(&node.nodeid).cloned());
415
416 PseudoValueCore::from_node_and_tile(
417 Arc::clone(&node),
418 tile,
419 tile_data,
420 child_node_ids.clone(),
421 )
422 })
423 .collect();
424
425 PseudoListCore::from_values_with_cardinality(alias, values, is_single)
426}
427
428pub fn values_from_resource_nodegroup(
434 existing_values: &HashMap<String, Option<bool>>,
435 nodegroup_tile_ids: &[String],
436 nodegroup_id: &str,
437 model: &dyn ModelAccess,
438 tiles_store: &HashMap<String, StaticTile>,
439) -> Result<ValuesFromNodegroupResult, SemanticChildError> {
440 let node_objs = model.get_nodes().ok_or_else(|| {
441 SemanticChildError::ModelNotInitialized("Model nodes not initialized".to_string())
442 })?;
443 let edges = model.get_edges().ok_or_else(|| {
444 SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
445 })?;
446 let reverse_edges = model.get_reverse_edges().ok_or_else(|| {
447 SemanticChildError::ModelNotInitialized("Model reverse edges not initialized".to_string())
448 })?;
449 let nodes_by_nodegroup = model.get_nodes_by_nodegroup().ok_or_else(|| {
450 SemanticChildError::ModelNotInitialized(
451 "Model nodes-by-nodegroup not initialized".to_string(),
452 )
453 })?;
454 let nodegroups = model.get_nodegroups();
455
456 let mut values: HashMap<String, PseudoListCore> = HashMap::new();
457 let mut implied_nodegroups: HashSet<String> = HashSet::new();
458 let mut implied_nodes: HashMap<(String, String), (Arc<StaticNode>, Arc<StaticTile>)> =
459 HashMap::new();
460 let mut tile_nodes_seen: HashSet<(String, String)> = HashSet::new();
461
462 let mut alias_tiles: AliasTilesMap = HashMap::new();
463 let nodegroup_nodes = nodes_by_nodegroup.get(nodegroup_id);
464
465 for tile_id in nodegroup_tile_ids {
466 let tile = if tile_id.is_empty() {
467 None
468 } else {
469 tiles_store.get(tile_id).map(|t| Arc::new(t.clone()))
470 };
471 let tile_nodegroup_id = tile.as_ref().map(|t| t.nodegroup_id.clone());
472
473 if let Some(nodes_in_ng) = nodegroup_nodes {
474 for node in nodes_in_ng.iter() {
475 let alias = match &node.alias {
476 Some(a) if !a.is_empty() => a.clone(),
477 _ => continue,
478 };
479
480 if !tile_id.is_empty() {
481 tile_nodes_seen.insert((node.nodeid.clone(), tile_id.clone()));
482 }
483
484 if let Some(Some(true)) = existing_values.get(&alias) {
485 continue;
486 }
487
488 let entry = alias_tiles
489 .entry(alias.clone())
490 .or_insert_with(|| (Arc::clone(node), Vec::new()));
491 entry.1.push(tile.clone());
492
493 if let Some(parent_ids) = reverse_edges.get(&node.nodeid) {
494 if let Some(parent_id) = parent_ids.first() {
495 if let Some(domain_node) = node_objs.get(parent_id) {
496 if let Some(ref domain_ng_id) = domain_node.nodegroup_id {
497 if !domain_ng_id.is_empty() && domain_ng_id != nodegroup_id {
498 implied_nodegroups.insert(domain_ng_id.clone());
499 }
500 if let Some(ref tile_ng_id) = tile_nodegroup_id {
501 if domain_ng_id == tile_ng_id
502 && domain_ng_id != &domain_node.nodeid
503 && !tile_id.is_empty()
504 {
505 let key = (domain_node.nodeid.clone(), tile_id.clone());
506 if let Some(t) = tile.as_ref() {
507 implied_nodes.entry(key).or_insert_with(|| {
508 (Arc::clone(domain_node), Arc::clone(t))
509 });
510 }
511 }
512 }
513 }
514 }
515 }
516 }
517 }
518 }
519 }
520
521 for (_key, (node, tile)) in implied_nodes.iter() {
523 if let Some(tid) = tile.tileid.as_ref() {
524 let key = (node.nodeid.clone(), tid.clone());
525 if !tile_nodes_seen.contains(&key) {
526 let alias = match &node.alias {
527 Some(a) if !a.is_empty() => a.clone(),
528 _ => continue,
529 };
530 tile_nodes_seen.insert(key);
531 if existing_values.get(&alias) != Some(&Some(true)) {
532 let entry = alias_tiles
533 .entry(alias.clone())
534 .or_insert_with(|| (Arc::clone(node), Vec::new()));
535 entry.1.push(Some(Arc::clone(tile)));
536 }
537 }
538 }
539 }
540
541 for (alias, (node, tiles)) in alias_tiles {
543 let is_single = is_node_single_cardinality(&node, nodegroups);
544 let pseudo_list = create_pseudo_list_from_tiles(node, tiles, edges, is_single);
545 values.insert(alias, pseudo_list);
546 }
547
548 Ok(ValuesFromNodegroupResult {
549 values,
550 implied_nodegroups: implied_nodegroups.into_iter().collect(),
551 })
552}
553
554#[allow(clippy::too_many_arguments)]
559pub fn ensure_nodegroup(
560 all_values_map: &HashMap<String, Option<bool>>,
561 all_nodegroups: &mut HashMap<String, bool>,
562 nodegroup_id: &str,
563 add_if_missing: bool,
564 nodegroup_permissions: &HashMap<String, PermissionRule>,
565 do_implied_nodegroups: bool,
566 model: &dyn ModelAccess,
567 tiles_store: &HashMap<String, StaticTile>,
568) -> Result<EnsureNodegroupResult, SemanticChildError> {
569 let sentinel = all_nodegroups.get(nodegroup_id);
570 let should_process = match sentinel {
571 Some(&false) => true,
572 Some(&true) => false,
573 None => add_if_missing,
574 };
575
576 let mut all_values: HashMap<String, PseudoListCore> = HashMap::new();
577 let mut implied_nodegroups_set: HashSet<String> = HashSet::new();
578
579 if should_process {
580 let mut nodegroup_tiles: Vec<String> = Vec::new();
581
582 for (tile_id, tile) in tiles_store.iter() {
583 if tile.nodegroup_id == nodegroup_id {
584 let permitted = nodegroup_permissions
585 .get(&tile.nodegroup_id)
586 .map(|rule| rule.permits_tile(tile))
587 .unwrap_or(true);
588 if permitted {
589 nodegroup_tiles.push(tile_id.clone());
590 }
591 }
592 }
593
594 if nodegroup_tiles.is_empty() && add_if_missing {
595 nodegroup_tiles.push(String::new());
596 }
597
598 let values_result = values_from_resource_nodegroup(
599 all_values_map,
600 &nodegroup_tiles,
601 nodegroup_id,
602 model,
603 tiles_store,
604 )?;
605
606 for (alias, pseudo_list) in values_result.values {
607 all_values.insert(alias, pseudo_list);
608 }
609 for ng in values_result.implied_nodegroups.iter() {
610 implied_nodegroups_set.insert(ng.clone());
611 }
612
613 all_nodegroups.insert(nodegroup_id.to_string(), true);
614
615 if do_implied_nodegroups && !implied_nodegroups_set.is_empty() {
616 let implied_list: Vec<String> = implied_nodegroups_set.iter().cloned().collect();
617 for implied_ng in implied_list.iter() {
618 let implied_result = ensure_nodegroup(
619 all_values_map,
620 all_nodegroups,
621 implied_ng,
622 true,
623 nodegroup_permissions,
624 true,
625 model,
626 tiles_store,
627 )?;
628 for (alias, pseudo_list) in implied_result.values {
629 all_values.insert(alias, pseudo_list);
630 }
631 }
632 implied_nodegroups_set.clear();
633 }
634 }
635
636 Ok(EnsureNodegroupResult {
637 values: all_values,
638 implied_nodegroups: implied_nodegroups_set.into_iter().collect(),
639 all_nodegroups_map: all_nodegroups.clone(),
640 })
641}
642
643pub struct ResourceInstanceWrapperCore {
653 pub graph_id: String,
655
656 pub resource_instance: Option<StaticResourceMetadata>,
658
659 pub tiles: Option<HashMap<String, StaticTile>>,
661
662 pub nodegroup_index: HashMap<String, Vec<String>>,
664
665 pub loaded_nodegroups: Arc<Mutex<HashMap<String, LoadState>>>,
667
668 pub pseudo_cache: Arc<Mutex<HashMap<String, PseudoListCore>>>,
670
671 pub cached_nodes: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
673 pub cached_edges: Option<Arc<HashMap<String, Vec<String>>>>,
674 pub cached_reverse_edges: Option<Arc<HashMap<String, Vec<String>>>>,
675 pub cached_nodes_by_nodegroup: Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>>,
676 pub cached_nodegroups: Option<Arc<HashMap<String, Arc<StaticNodegroup>>>>,
677}
678
679impl ResourceInstanceWrapperCore {
680 pub fn new(graph_id: String) -> Self {
682 ResourceInstanceWrapperCore {
683 graph_id,
684 resource_instance: None,
685 tiles: None,
686 nodegroup_index: HashMap::new(),
687 loaded_nodegroups: Arc::new(Mutex::new(HashMap::new())),
688 pseudo_cache: Arc::new(Mutex::new(HashMap::new())),
689 cached_nodes: None,
690 cached_edges: None,
691 cached_reverse_edges: None,
692 cached_nodes_by_nodegroup: None,
693 cached_nodegroups: None,
694 }
695 }
696
697 pub fn set_cached_indices(&mut self, model: &dyn ModelAccess) {
699 self.cached_nodes = model.get_nodes().map(|m| Arc::new(m.clone()));
700 self.cached_edges = model.get_edges().map(|m| Arc::new(m.clone()));
701 self.cached_reverse_edges = model.get_reverse_edges().map(|m| Arc::new(m.clone()));
702 self.cached_nodes_by_nodegroup =
703 model.get_nodes_by_nodegroup().map(|m| Arc::new(m.clone()));
704 self.cached_nodegroups = model.get_nodegroups().map(|m| Arc::new(m.clone()));
705 }
706
707 pub fn load_tiles(&mut self, tiles: Vec<StaticTile>) {
709 let mut tiles_map = HashMap::new();
710 let mut nodegroup_index: HashMap<String, Vec<String>> = HashMap::new();
711
712 for tile in tiles {
713 let tile_id = tile
714 .tileid
715 .clone()
716 .unwrap_or_else(|| format!("synthetic_{}", tiles_map.len()));
717
718 nodegroup_index
720 .entry(tile.nodegroup_id.clone())
721 .or_default()
722 .push(tile_id.clone());
723
724 tiles_map.insert(tile_id, tile);
725 }
726
727 self.tiles = Some(tiles_map);
728 self.nodegroup_index = nodegroup_index;
729 }
730
731 pub fn get_tile(&self, tile_id: &str) -> Option<&StaticTile> {
733 self.tiles.as_ref().and_then(|t| t.get(tile_id))
734 }
735
736 pub fn set_tile_data_for_node(
739 &mut self,
740 tile_id: &str,
741 node_id: &str,
742 value: serde_json::Value,
743 ) -> bool {
744 if let Some(tiles) = &mut self.tiles {
745 if let Some(tile) = tiles.get_mut(tile_id) {
746 tile.data.insert(node_id.to_string(), value);
747 return true;
748 }
749 }
750 false
751 }
752
753 pub fn get_tiles_for_nodegroup(&self, nodegroup_id: &str) -> Vec<&StaticTile> {
755 let tile_ids = self.nodegroup_index.get(nodegroup_id);
756 match (tile_ids, &self.tiles) {
757 (Some(ids), Some(tiles)) => ids.iter().filter_map(|id| tiles.get(id)).collect(),
758 _ => Vec::new(),
759 }
760 }
761
762 pub fn is_nodegroup_loaded(&self, nodegroup_id: &str) -> bool {
764 if let Ok(loaded) = self.loaded_nodegroups.lock() {
765 matches!(loaded.get(nodegroup_id), Some(LoadState::Loaded))
766 } else {
767 false
768 }
769 }
770
771 pub fn mark_nodegroup_loaded(&self, nodegroup_id: &str) {
773 if let Ok(mut loaded) = self.loaded_nodegroups.lock() {
774 loaded.insert(nodegroup_id.to_string(), LoadState::Loaded);
775 }
776 }
777
778 pub fn get_cached_pseudo(&self, alias: &str) -> Option<PseudoListCore> {
780 if let Ok(cache) = self.pseudo_cache.lock() {
781 cache.get(alias).cloned()
782 } else {
783 None
784 }
785 }
786
787 pub fn store_pseudo(&self, alias: String, pseudo_list: PseudoListCore) {
789 if let Ok(mut cache) = self.pseudo_cache.lock() {
790 cache.insert(alias, pseudo_list);
791 }
792 }
793
794 pub fn values_from_resource_nodegroup(
799 &self,
800 existing_values: &HashMap<String, Option<bool>>,
801 nodegroup_tile_ids: &[String],
802 nodegroup_id: &str,
803 model: &dyn ModelAccess,
804 ) -> Result<ValuesFromNodegroupResult, SemanticChildError> {
805 let tiles_store = match &self.tiles {
806 Some(t) => t,
807 None => {
808 return Ok(ValuesFromNodegroupResult {
809 values: HashMap::new(),
810 implied_nodegroups: Vec::new(),
811 });
812 }
813 };
814 values_from_resource_nodegroup(
815 existing_values,
816 nodegroup_tile_ids,
817 nodegroup_id,
818 model,
819 tiles_store,
820 )
821 }
822
823 #[allow(clippy::too_many_arguments)]
828 pub fn ensure_nodegroup(
829 &self,
830 all_values_map: &HashMap<String, Option<bool>>,
831 all_nodegroups: &mut HashMap<String, bool>,
832 nodegroup_id: &str,
833 add_if_missing: bool,
834 nodegroup_permissions: &HashMap<String, PermissionRule>,
835 do_implied_nodegroups: bool,
836 model: &dyn ModelAccess,
837 ) -> Result<EnsureNodegroupResult, SemanticChildError> {
838 let tiles_store = match &self.tiles {
839 Some(t) => t,
840 None => {
841 return Ok(EnsureNodegroupResult {
842 values: HashMap::new(),
843 implied_nodegroups: Vec::new(),
844 all_nodegroups_map: all_nodegroups.clone(),
845 });
846 }
847 };
848 ensure_nodegroup(
849 all_values_map,
850 all_nodegroups,
851 nodegroup_id,
852 add_if_missing,
853 nodegroup_permissions,
854 do_implied_nodegroups,
855 model,
856 tiles_store,
857 )
858 }
859
860 pub fn populate(
864 &self,
865 lazy: bool,
866 nodegroup_ids: &[String],
867 root_node_alias: &str,
868 model: &dyn ModelAccess,
869 ) -> Result<PopulateResult, SemanticChildError> {
870 let nodegroup_permissions = model.get_permitted_nodegroups();
871
872 let cache_len = if let Ok(cache) = self.pseudo_cache.lock() {
874 cache.len()
875 } else {
876 0
877 };
878 let cache_populated = cache_len > 1;
879
880 let already_loaded: HashSet<String> = if cache_populated {
882 if let Ok(loaded) = self.loaded_nodegroups.lock() {
883 loaded
884 .iter()
885 .filter(|(_, state)| **state == LoadState::Loaded)
886 .map(|(id, _)| id.clone())
887 .collect()
888 } else {
889 HashSet::new()
890 }
891 } else {
892 HashSet::new()
893 };
894
895 let nodegroups_to_process: Vec<String> = nodegroup_ids
897 .iter()
898 .filter(|id| !already_loaded.contains(*id))
899 .cloned()
900 .collect();
901
902 let mut all_values: HashMap<String, Option<bool>> = HashMap::new();
904 let mut all_nodegroups: HashMap<String, bool> = HashMap::new();
905
906 for nodegroup_id in nodegroup_ids.iter() {
908 let is_loaded = already_loaded.contains(nodegroup_id);
909 all_nodegroups.insert(nodegroup_id.clone(), is_loaded);
910 }
911
912 all_values.insert(root_node_alias.to_string(), Some(false));
914
915 let mut all_structured_values: HashMap<String, PseudoListCore> =
917 if !already_loaded.is_empty() {
918 if let Ok(cache) = self.pseudo_cache.lock() {
919 cache.clone()
920 } else {
921 HashMap::new()
922 }
923 } else {
924 HashMap::new()
925 };
926
927 if !lazy {
929 let mut implied_nodegroups_set = HashSet::new();
930
931 for nodegroup_id in nodegroups_to_process.iter() {
933 let result = self.ensure_nodegroup(
934 &all_values,
935 &mut all_nodegroups,
936 nodegroup_id,
937 true,
938 &nodegroup_permissions,
939 false,
940 model,
941 )?;
942
943 for (alias, pseudo_list) in result.values {
944 all_structured_values.insert(alias, pseudo_list);
945 }
946
947 for implied_ng in result.implied_nodegroups.iter() {
948 if implied_ng != nodegroup_id {
949 implied_nodegroups_set.insert(implied_ng.clone());
950 }
951 }
952 }
953
954 while !implied_nodegroups_set.is_empty() {
956 let current_implied: Vec<String> = implied_nodegroups_set.iter().cloned().collect();
957 implied_nodegroups_set.clear();
958
959 for nodegroup_id in current_implied.iter() {
960 let current_value = all_nodegroups.get(nodegroup_id);
961 let should_process = matches!(current_value, Some(&false) | None);
962
963 if should_process {
964 let result = self.ensure_nodegroup(
965 &all_values,
966 &mut all_nodegroups,
967 nodegroup_id,
968 true,
969 &nodegroup_permissions,
970 true,
971 model,
972 )?;
973
974 for (alias, pseudo_list) in result.values {
975 all_structured_values.insert(alias, pseudo_list);
976 }
977
978 for implied_ng in result.implied_nodegroups.iter() {
979 implied_nodegroups_set.insert(implied_ng.clone());
980 }
981 }
982 }
983 }
984 }
985
986 let root_node = model
988 .get_root_node()
989 .map_err(SemanticChildError::ModelNotInitialized)?;
990 let edges = model.get_edges().ok_or_else(|| {
991 SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
992 })?;
993
994 let child_node_ids = edges.get(&root_node.nodeid).cloned().unwrap_or_default();
995
996 let root_pseudo =
997 PseudoValueCore::from_node_and_tile(root_node, None, None, child_node_ids);
998
999 let root_list = PseudoListCore::from_values_with_cardinality(
1000 root_node_alias.to_string(),
1001 vec![root_pseudo],
1002 true,
1003 );
1004
1005 all_structured_values.insert(root_node_alias.to_string(), root_list);
1006
1007 if let Ok(mut cache) = self.pseudo_cache.lock() {
1009 for (alias, pseudo_list) in all_structured_values.iter() {
1010 cache.insert(alias.clone(), pseudo_list.clone());
1011 }
1012 }
1013
1014 Ok(PopulateResult {
1015 values: all_structured_values,
1016 all_values_map: all_values,
1017 all_nodegroups_map: all_nodegroups,
1018 })
1019 }
1020
1021 pub fn get_semantic_child_value(
1023 &self,
1024 parent_tile_id: Option<&String>,
1025 parent_node_id: &str,
1026 parent_nodegroup_id: Option<&String>,
1027 child_alias: &str,
1028 model: &dyn ModelAccess,
1029 ) -> Result<SemanticChildResult, SemanticChildError> {
1030 let child_nodes = model
1032 .get_child_nodes(parent_node_id)
1033 .map_err(SemanticChildError::ModelNotInitialized)?;
1034
1035 let child_node = child_nodes
1037 .values()
1038 .find(|n| n.alias.as_deref() == Some(child_alias))
1039 .ok_or_else(|| SemanticChildError::ChildNotFound {
1040 alias: child_alias.to_string(),
1041 })?;
1042
1043 let tiles = self
1045 .tiles
1046 .as_ref()
1047 .ok_or(SemanticChildError::TilesNotInitialized)?;
1048
1049 let matching_tiles: Vec<Arc<StaticTile>> = tiles
1051 .values()
1052 .filter(|tile| {
1053 matches_semantic_child(parent_tile_id, parent_nodegroup_id, child_node, tile)
1054 })
1055 .map(|t| Arc::new(t.clone()))
1056 .collect();
1057
1058 if matching_tiles.is_empty() {
1059 return Ok(SemanticChildResult::Empty);
1060 }
1061
1062 let edges = model.get_edges().ok_or_else(|| {
1064 SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
1065 })?;
1066 let child_node_ids = edges.get(&child_node.nodeid).cloned().unwrap_or_default();
1067
1068 let nodegroups = model.get_nodegroups();
1070 let is_single = is_node_single_cardinality(child_node, nodegroups);
1071
1072 let values: Vec<PseudoValueCore> = matching_tiles
1074 .into_iter()
1075 .map(|tile| {
1076 let tile_data = tile.data.get(&child_node.nodeid).cloned();
1077 PseudoValueCore::from_node_and_tile(
1078 Arc::clone(child_node),
1079 Some(tile),
1080 tile_data,
1081 child_node_ids.clone(),
1082 )
1083 })
1084 .collect();
1085
1086 if is_single && values.len() == 1 {
1087 Ok(SemanticChildResult::Single(
1088 values.into_iter().next().unwrap(),
1089 ))
1090 } else {
1091 let list = PseudoListCore::from_values_with_cardinality(
1092 child_alias.to_string(),
1093 values,
1094 is_single,
1095 );
1096 Ok(SemanticChildResult::List(list))
1097 }
1098 }
1099
1100 pub fn resolve_path(
1105 &self,
1106 path: &str,
1107 model: &dyn ModelAccess,
1108 ) -> Result<PathResolutionInfo, PathError> {
1109 let root_node = model
1110 .get_root_node()
1111 .map_err(PathError::ModelNotInitialized)?;
1112 let nodes = model
1113 .get_nodes()
1114 .ok_or_else(|| PathError::ModelNotInitialized("Nodes not initialized".into()))?;
1115 let edges = model
1116 .get_edges()
1117 .ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
1118 let nodegroups = model.get_nodegroups();
1119
1120 resolve_path_segments(path, &root_node, nodes, edges, nodegroups)
1121 }
1122
1123 pub fn resolve_and_filter_tiles(
1128 &self,
1129 path: &str,
1130 model: &dyn ModelAccess,
1131 filter_tile_id: Option<&str>,
1132 ) -> Result<(PathResolutionInfo, Vec<StaticTile>), PathError> {
1133 let tiles_store = self.tiles.as_ref().ok_or(PathError::TilesNotInitialized)?;
1134 resolve_and_filter_tiles(
1135 path,
1136 model,
1137 tiles_store,
1138 &self.nodegroup_index,
1139 filter_tile_id,
1140 )
1141 }
1142
1143 pub fn get_values_at_path(
1152 &self,
1153 path: &str,
1154 model: &dyn ModelAccess,
1155 filter_tile_id: Option<&str>,
1156 ) -> Result<PseudoListCore, PathError> {
1157 let (info, tiles) = self.resolve_and_filter_tiles(path, model, filter_tile_id)?;
1158
1159 let edges = model
1160 .get_edges()
1161 .ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
1162
1163 let tiles_wrapped: Vec<Option<Arc<StaticTile>>> =
1164 tiles.into_iter().map(|t| Some(Arc::new(t))).collect();
1165
1166 let pseudo_list =
1167 create_pseudo_list_from_tiles(info.target_node, tiles_wrapped, edges, info.is_single);
1168
1169 Ok(pseudo_list)
1170 }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use super::*;
1176
1177 #[test]
1178 fn test_load_state_equality() {
1179 assert_eq!(LoadState::NotLoaded, LoadState::NotLoaded);
1180 assert_eq!(LoadState::Loading, LoadState::Loading);
1181 assert_eq!(LoadState::Loaded, LoadState::Loaded);
1182 assert_ne!(LoadState::NotLoaded, LoadState::Loaded);
1183 }
1184
1185 #[test]
1186 fn test_is_node_single_cardinality_collector() {
1187 let node = Arc::new(StaticNode {
1188 nodeid: "test-node".to_string(),
1189 name: "Test Node".to_string(),
1190 alias: Some("test".to_string()),
1191 datatype: "string".to_string(),
1192 is_collector: true,
1193 nodegroup_id: Some("test-nodegroup".to_string()),
1194 graph_id: "test-graph".to_string(),
1195 isrequired: false,
1196 exportable: true,
1197 sortorder: None,
1198 config: HashMap::new(),
1199 parentproperty: None,
1200 ontologyclass: None,
1201 description: None,
1202 fieldname: None,
1203 hascustomalias: false,
1204 issearchable: false,
1205 istopnode: false,
1206 sourcebranchpublication_id: None,
1207 source_identifier_id: None,
1208 is_immutable: None,
1209 });
1210
1211 assert!(!is_node_single_cardinality(&node, None));
1213 }
1214
1215 #[test]
1216 fn test_is_node_single_cardinality_non_collector() {
1217 let node = Arc::new(StaticNode {
1218 nodeid: "test-node".to_string(),
1219 name: "Test Node".to_string(),
1220 alias: Some("test".to_string()),
1221 datatype: "string".to_string(),
1222 is_collector: false,
1223 nodegroup_id: Some("test-nodegroup".to_string()),
1224 graph_id: "test-graph".to_string(),
1225 isrequired: false,
1226 exportable: true,
1227 sortorder: None,
1228 config: HashMap::new(),
1229 parentproperty: None,
1230 ontologyclass: None,
1231 description: None,
1232 fieldname: None,
1233 hascustomalias: false,
1234 issearchable: false,
1235 istopnode: false,
1236 sourcebranchpublication_id: None,
1237 source_identifier_id: None,
1238 is_immutable: None,
1239 });
1240
1241 assert!(is_node_single_cardinality(&node, None));
1243 }
1244}