use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use crate::path_resolution::{resolve_path_segments, PathError, PathResolutionInfo};
use crate::permissions::PermissionRule;
use crate::pseudo_value_core::{PseudoListCore, PseudoValueCore};
use crate::{StaticNode, StaticNodegroup, StaticResourceMetadata, StaticTile};
#[derive(Debug, Clone)]
pub enum SemanticChildError {
TilesNotLoaded { nodegroup_id: String },
ChildNotFound { alias: String },
TilesNotInitialized,
ModelNotInitialized(String),
Other(String),
}
impl std::fmt::Display for SemanticChildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SemanticChildError::TilesNotLoaded { nodegroup_id } => {
write!(f, "Tiles not loaded for nodegroup: {}", nodegroup_id)
}
SemanticChildError::ChildNotFound { alias } => {
write!(f, "Child node not found: {}", alias)
}
SemanticChildError::TilesNotInitialized => {
write!(f, "Tiles not initialized")
}
SemanticChildError::ModelNotInitialized(msg) => {
write!(f, "Model not initialized: {}", msg)
}
SemanticChildError::Other(msg) => {
write!(f, "{}", msg)
}
}
}
}
impl std::error::Error for SemanticChildError {}
impl From<String> for SemanticChildError {
fn from(s: String) -> Self {
SemanticChildError::Other(s)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LoadState {
NotLoaded,
Loading,
Loaded,
}
#[derive(Clone, Debug)]
pub struct ValuesFromNodegroupResult {
pub values: HashMap<String, PseudoListCore>,
pub implied_nodegroups: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct EnsureNodegroupResult {
pub values: HashMap<String, PseudoListCore>,
pub implied_nodegroups: Vec<String>,
pub all_nodegroups_map: HashMap<String, bool>,
}
#[derive(Clone, Debug)]
pub struct PopulateResult {
pub values: HashMap<String, PseudoListCore>,
pub all_values_map: HashMap<String, Option<bool>>,
pub all_nodegroups_map: HashMap<String, bool>,
}
#[derive(Debug)]
pub enum SemanticChildResult {
List(PseudoListCore),
Single(PseudoValueCore),
Empty,
}
pub trait ModelAccess {
fn get_nodes(&self) -> Option<&HashMap<String, Arc<StaticNode>>>;
fn get_edges(&self) -> Option<&HashMap<String, Vec<String>>>;
fn get_reverse_edges(&self) -> Option<&HashMap<String, Vec<String>>>;
fn get_nodes_by_nodegroup(&self) -> Option<&HashMap<String, Vec<Arc<StaticNode>>>>;
fn get_nodegroups(&self) -> Option<&HashMap<String, Arc<StaticNodegroup>>>;
fn get_root_node(&self) -> Result<Arc<StaticNode>, String> {
let nodes = self
.get_nodes()
.ok_or_else(|| "Nodes not initialized".to_string())?;
for node in nodes.values() {
if node.istopnode {
return Ok(Arc::clone(node));
}
}
for node in nodes.values() {
if node.nodegroup_id.is_none()
|| node
.nodegroup_id
.as_ref()
.map(|s| s.is_empty())
.unwrap_or(true)
{
return Ok(Arc::clone(node));
}
}
Err("Could not find root node".to_string())
}
fn get_child_nodes(&self, node_id: &str) -> Result<HashMap<String, Arc<StaticNode>>, String> {
let edges = self.get_edges().ok_or("Edges not initialized")?;
let nodes = self.get_nodes().ok_or("Nodes not initialized")?;
let child_ids = edges.get(node_id).cloned().unwrap_or_default();
let mut children = HashMap::new();
for child_id in child_ids {
if let Some(node) = nodes.get(&child_id) {
if let Some(ref alias) = node.alias {
if !alias.is_empty() {
children.insert(alias.clone(), Arc::clone(node));
}
}
}
}
Ok(children)
}
fn get_permitted_nodegroups(&self) -> HashMap<String, PermissionRule>;
}
pub fn is_node_single_cardinality_with<F>(node: &StaticNode, get_cardinality: F) -> bool
where
F: Fn(&str) -> Option<String>,
{
if let Some(ref ng_id) = node.nodegroup_id {
if &node.nodeid == ng_id {
if let Some(cardinality) = get_cardinality(ng_id) {
return cardinality != "n";
}
}
}
!node.is_collector
}
pub fn is_node_single_cardinality(
node: &StaticNode,
nodegroups: Option<&HashMap<String, Arc<StaticNodegroup>>>,
) -> bool {
is_node_single_cardinality_with(node, |ng_id| {
nodegroups
.and_then(|ngs| ngs.get(ng_id))
.and_then(|ng| ng.cardinality.clone())
})
}
pub fn matches_semantic_child(
parent_tile_id: Option<&String>,
parent_nodegroup_id: Option<&String>,
child_node: &StaticNode,
tile: &StaticTile,
) -> bool {
if tile.nodegroup_id != *child_node.nodegroup_id.as_ref().unwrap_or(&"".into()) {
return false;
}
let is_semantic = child_node.datatype == "semantic";
if !(Some(&child_node.nodeid) == child_node.nodegroup_id.as_ref()
|| tile.data.contains_key(&child_node.nodeid)
|| is_semantic)
{
return false;
}
let parent_ng = parent_nodegroup_id.map(|s| s.as_str()).unwrap_or("");
if tile.nodegroup_id != parent_ng {
if let Some(parent_tid) = parent_tile_id {
let parent_matches =
tile.parenttile_id.is_none() || tile.parenttile_id.as_ref() == Some(parent_tid);
if parent_matches {
return true;
}
} else {
if tile.parenttile_id.is_none() {
return true;
}
}
}
if tile.nodegroup_id == parent_ng {
if let Some(parent_tid) = parent_tile_id {
let has_data_or_is_semantic =
tile.data.contains_key(&child_node.nodeid) || child_node.datatype == "semantic";
if tile.tileid.as_ref() == Some(parent_tid)
&& !child_node.is_collector
&& has_data_or_is_semantic
{
return true;
}
}
}
if tile.nodegroup_id != parent_ng && child_node.is_collector {
if let Some(parent_tid) = parent_tile_id {
return tile.parenttile_id.is_none() || tile.parenttile_id.as_ref() == Some(parent_tid);
}
return true;
}
false
}
pub fn resolve_and_filter_tiles(
path: &str,
model: &dyn ModelAccess,
tiles_store: &HashMap<String, StaticTile>,
nodegroup_index: &HashMap<String, Vec<String>>,
filter_tile_id: Option<&str>,
) -> Result<(PathResolutionInfo, Vec<StaticTile>), PathError> {
let root_node = model
.get_root_node()
.map_err(PathError::ModelNotInitialized)?;
let nodes = model
.get_nodes()
.ok_or_else(|| PathError::ModelNotInitialized("Nodes not initialized".into()))?;
let edges = model
.get_edges()
.ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
let nodegroups = model.get_nodegroups();
let info = resolve_path_segments(path, &root_node, nodes, edges, nodegroups)?;
let tile_ids = nodegroup_index
.get(&info.nodegroup_id)
.cloned()
.unwrap_or_default();
let tiles: Vec<StaticTile> = if let Some(filter_id) = filter_tile_id {
let filter_id_string = filter_id.to_string();
let parent_ng = info.parent_nodegroup_id.as_ref();
tile_ids
.iter()
.filter_map(|tid| {
tiles_store.get(tid).and_then(|t| {
if matches_semantic_child(
Some(&filter_id_string),
parent_ng,
&info.target_node,
t,
) {
Some(t.clone())
} else {
None
}
})
})
.collect()
} else {
tile_ids
.iter()
.filter_map(|tid| tiles_store.get(tid).cloned())
.collect()
};
Ok((info, tiles))
}
type AliasTilesMap = HashMap<String, (Arc<StaticNode>, Vec<Option<Arc<StaticTile>>>)>;
pub fn create_pseudo_list_from_tiles(
node: Arc<StaticNode>,
tiles: Vec<Option<Arc<StaticTile>>>,
edges: &HashMap<String, Vec<String>>,
is_single: bool,
) -> PseudoListCore {
let alias = node.alias.clone().unwrap_or_default();
let child_node_ids = edges.get(&node.nodeid).cloned().unwrap_or_default();
let values: Vec<PseudoValueCore> = tiles
.into_iter()
.map(|tile| {
let tile_data = tile
.as_ref()
.and_then(|t| t.data.get(&node.nodeid).cloned());
PseudoValueCore::from_node_and_tile(
Arc::clone(&node),
tile,
tile_data,
child_node_ids.clone(),
)
})
.collect();
PseudoListCore::from_values_with_cardinality(alias, values, is_single)
}
pub fn values_from_resource_nodegroup(
existing_values: &HashMap<String, Option<bool>>,
nodegroup_tile_ids: &[String],
nodegroup_id: &str,
model: &dyn ModelAccess,
tiles_store: &HashMap<String, StaticTile>,
) -> Result<ValuesFromNodegroupResult, SemanticChildError> {
let node_objs = model.get_nodes().ok_or_else(|| {
SemanticChildError::ModelNotInitialized("Model nodes not initialized".to_string())
})?;
let edges = model.get_edges().ok_or_else(|| {
SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
})?;
let reverse_edges = model.get_reverse_edges().ok_or_else(|| {
SemanticChildError::ModelNotInitialized("Model reverse edges not initialized".to_string())
})?;
let nodes_by_nodegroup = model.get_nodes_by_nodegroup().ok_or_else(|| {
SemanticChildError::ModelNotInitialized(
"Model nodes-by-nodegroup not initialized".to_string(),
)
})?;
let nodegroups = model.get_nodegroups();
let mut values: HashMap<String, PseudoListCore> = HashMap::new();
let mut implied_nodegroups: HashSet<String> = HashSet::new();
let mut implied_nodes: HashMap<(String, String), (Arc<StaticNode>, Arc<StaticTile>)> =
HashMap::new();
let mut tile_nodes_seen: HashSet<(String, String)> = HashSet::new();
let mut alias_tiles: AliasTilesMap = HashMap::new();
let nodegroup_nodes = nodes_by_nodegroup.get(nodegroup_id);
for tile_id in nodegroup_tile_ids {
let tile = if tile_id.is_empty() {
None
} else {
tiles_store.get(tile_id).map(|t| Arc::new(t.clone()))
};
let tile_nodegroup_id = tile.as_ref().map(|t| t.nodegroup_id.clone());
if let Some(nodes_in_ng) = nodegroup_nodes {
for node in nodes_in_ng.iter() {
let alias = match &node.alias {
Some(a) if !a.is_empty() => a.clone(),
_ => continue,
};
if !tile_id.is_empty() {
tile_nodes_seen.insert((node.nodeid.clone(), tile_id.clone()));
}
if let Some(Some(true)) = existing_values.get(&alias) {
continue;
}
let entry = alias_tiles
.entry(alias.clone())
.or_insert_with(|| (Arc::clone(node), Vec::new()));
entry.1.push(tile.clone());
if let Some(parent_ids) = reverse_edges.get(&node.nodeid) {
if let Some(parent_id) = parent_ids.first() {
if let Some(domain_node) = node_objs.get(parent_id) {
if let Some(ref domain_ng_id) = domain_node.nodegroup_id {
if !domain_ng_id.is_empty() && domain_ng_id != nodegroup_id {
implied_nodegroups.insert(domain_ng_id.clone());
}
if let Some(ref tile_ng_id) = tile_nodegroup_id {
if domain_ng_id == tile_ng_id
&& domain_ng_id != &domain_node.nodeid
&& !tile_id.is_empty()
{
let key = (domain_node.nodeid.clone(), tile_id.clone());
if let Some(t) = tile.as_ref() {
implied_nodes.entry(key).or_insert_with(|| {
(Arc::clone(domain_node), Arc::clone(t))
});
}
}
}
}
}
}
}
}
}
}
for (_key, (node, tile)) in implied_nodes.iter() {
if let Some(tid) = tile.tileid.as_ref() {
let key = (node.nodeid.clone(), tid.clone());
if !tile_nodes_seen.contains(&key) {
let alias = match &node.alias {
Some(a) if !a.is_empty() => a.clone(),
_ => continue,
};
tile_nodes_seen.insert(key);
if existing_values.get(&alias) != Some(&Some(true)) {
let entry = alias_tiles
.entry(alias.clone())
.or_insert_with(|| (Arc::clone(node), Vec::new()));
entry.1.push(Some(Arc::clone(tile)));
}
}
}
}
for (alias, (node, tiles)) in alias_tiles {
let is_single = is_node_single_cardinality(&node, nodegroups);
let pseudo_list = create_pseudo_list_from_tiles(node, tiles, edges, is_single);
values.insert(alias, pseudo_list);
}
Ok(ValuesFromNodegroupResult {
values,
implied_nodegroups: implied_nodegroups.into_iter().collect(),
})
}
#[allow(clippy::too_many_arguments)]
pub fn ensure_nodegroup(
all_values_map: &HashMap<String, Option<bool>>,
all_nodegroups: &mut HashMap<String, bool>,
nodegroup_id: &str,
add_if_missing: bool,
nodegroup_permissions: &HashMap<String, PermissionRule>,
do_implied_nodegroups: bool,
model: &dyn ModelAccess,
tiles_store: &HashMap<String, StaticTile>,
) -> Result<EnsureNodegroupResult, SemanticChildError> {
let sentinel = all_nodegroups.get(nodegroup_id);
let should_process = match sentinel {
Some(&false) => true,
Some(&true) => false,
None => add_if_missing,
};
let mut all_values: HashMap<String, PseudoListCore> = HashMap::new();
let mut implied_nodegroups_set: HashSet<String> = HashSet::new();
if should_process {
let mut nodegroup_tiles: Vec<String> = Vec::new();
for (tile_id, tile) in tiles_store.iter() {
if tile.nodegroup_id == nodegroup_id {
let permitted = nodegroup_permissions
.get(&tile.nodegroup_id)
.map(|rule| rule.permits_tile(tile))
.unwrap_or(true);
if permitted {
nodegroup_tiles.push(tile_id.clone());
}
}
}
if nodegroup_tiles.is_empty() && add_if_missing {
nodegroup_tiles.push(String::new());
}
let values_result = values_from_resource_nodegroup(
all_values_map,
&nodegroup_tiles,
nodegroup_id,
model,
tiles_store,
)?;
for (alias, pseudo_list) in values_result.values {
all_values.insert(alias, pseudo_list);
}
for ng in values_result.implied_nodegroups.iter() {
implied_nodegroups_set.insert(ng.clone());
}
all_nodegroups.insert(nodegroup_id.to_string(), true);
if do_implied_nodegroups && !implied_nodegroups_set.is_empty() {
let implied_list: Vec<String> = implied_nodegroups_set.iter().cloned().collect();
for implied_ng in implied_list.iter() {
let implied_result = ensure_nodegroup(
all_values_map,
all_nodegroups,
implied_ng,
true,
nodegroup_permissions,
true,
model,
tiles_store,
)?;
for (alias, pseudo_list) in implied_result.values {
all_values.insert(alias, pseudo_list);
}
}
implied_nodegroups_set.clear();
}
}
Ok(EnsureNodegroupResult {
values: all_values,
implied_nodegroups: implied_nodegroups_set.into_iter().collect(),
all_nodegroups_map: all_nodegroups.clone(),
})
}
pub struct ResourceInstanceWrapperCore {
pub graph_id: String,
pub resource_instance: Option<StaticResourceMetadata>,
pub tiles: Option<HashMap<String, StaticTile>>,
pub nodegroup_index: HashMap<String, Vec<String>>,
pub loaded_nodegroups: Arc<Mutex<HashMap<String, LoadState>>>,
pub pseudo_cache: Arc<Mutex<HashMap<String, PseudoListCore>>>,
pub cached_nodes: Option<Arc<HashMap<String, Arc<StaticNode>>>>,
pub cached_edges: Option<Arc<HashMap<String, Vec<String>>>>,
pub cached_reverse_edges: Option<Arc<HashMap<String, Vec<String>>>>,
pub cached_nodes_by_nodegroup: Option<Arc<HashMap<String, Vec<Arc<StaticNode>>>>>,
pub cached_nodegroups: Option<Arc<HashMap<String, Arc<StaticNodegroup>>>>,
}
impl ResourceInstanceWrapperCore {
pub fn new(graph_id: String) -> Self {
ResourceInstanceWrapperCore {
graph_id,
resource_instance: None,
tiles: None,
nodegroup_index: HashMap::new(),
loaded_nodegroups: Arc::new(Mutex::new(HashMap::new())),
pseudo_cache: Arc::new(Mutex::new(HashMap::new())),
cached_nodes: None,
cached_edges: None,
cached_reverse_edges: None,
cached_nodes_by_nodegroup: None,
cached_nodegroups: None,
}
}
pub fn set_cached_indices(&mut self, model: &dyn ModelAccess) {
self.cached_nodes = model.get_nodes().map(|m| Arc::new(m.clone()));
self.cached_edges = model.get_edges().map(|m| Arc::new(m.clone()));
self.cached_reverse_edges = model.get_reverse_edges().map(|m| Arc::new(m.clone()));
self.cached_nodes_by_nodegroup =
model.get_nodes_by_nodegroup().map(|m| Arc::new(m.clone()));
self.cached_nodegroups = model.get_nodegroups().map(|m| Arc::new(m.clone()));
}
pub fn load_tiles(&mut self, tiles: Vec<StaticTile>) {
let mut tiles_map = HashMap::new();
let mut nodegroup_index: HashMap<String, Vec<String>> = HashMap::new();
for tile in tiles {
let tile_id = tile
.tileid
.clone()
.unwrap_or_else(|| format!("synthetic_{}", tiles_map.len()));
nodegroup_index
.entry(tile.nodegroup_id.clone())
.or_default()
.push(tile_id.clone());
tiles_map.insert(tile_id, tile);
}
self.tiles = Some(tiles_map);
self.nodegroup_index = nodegroup_index;
}
pub fn get_tile(&self, tile_id: &str) -> Option<&StaticTile> {
self.tiles.as_ref().and_then(|t| t.get(tile_id))
}
pub fn set_tile_data_for_node(
&mut self,
tile_id: &str,
node_id: &str,
value: serde_json::Value,
) -> bool {
if let Some(tiles) = &mut self.tiles {
if let Some(tile) = tiles.get_mut(tile_id) {
tile.data.insert(node_id.to_string(), value);
return true;
}
}
false
}
pub fn get_tiles_for_nodegroup(&self, nodegroup_id: &str) -> Vec<&StaticTile> {
let tile_ids = self.nodegroup_index.get(nodegroup_id);
match (tile_ids, &self.tiles) {
(Some(ids), Some(tiles)) => ids.iter().filter_map(|id| tiles.get(id)).collect(),
_ => Vec::new(),
}
}
pub fn is_nodegroup_loaded(&self, nodegroup_id: &str) -> bool {
if let Ok(loaded) = self.loaded_nodegroups.lock() {
matches!(loaded.get(nodegroup_id), Some(LoadState::Loaded))
} else {
false
}
}
pub fn mark_nodegroup_loaded(&self, nodegroup_id: &str) {
if let Ok(mut loaded) = self.loaded_nodegroups.lock() {
loaded.insert(nodegroup_id.to_string(), LoadState::Loaded);
}
}
pub fn get_cached_pseudo(&self, alias: &str) -> Option<PseudoListCore> {
if let Ok(cache) = self.pseudo_cache.lock() {
cache.get(alias).cloned()
} else {
None
}
}
pub fn store_pseudo(&self, alias: String, pseudo_list: PseudoListCore) {
if let Ok(mut cache) = self.pseudo_cache.lock() {
cache.insert(alias, pseudo_list);
}
}
pub fn values_from_resource_nodegroup(
&self,
existing_values: &HashMap<String, Option<bool>>,
nodegroup_tile_ids: &[String],
nodegroup_id: &str,
model: &dyn ModelAccess,
) -> Result<ValuesFromNodegroupResult, SemanticChildError> {
let tiles_store = match &self.tiles {
Some(t) => t,
None => {
return Ok(ValuesFromNodegroupResult {
values: HashMap::new(),
implied_nodegroups: Vec::new(),
});
}
};
values_from_resource_nodegroup(
existing_values,
nodegroup_tile_ids,
nodegroup_id,
model,
tiles_store,
)
}
#[allow(clippy::too_many_arguments)]
pub fn ensure_nodegroup(
&self,
all_values_map: &HashMap<String, Option<bool>>,
all_nodegroups: &mut HashMap<String, bool>,
nodegroup_id: &str,
add_if_missing: bool,
nodegroup_permissions: &HashMap<String, PermissionRule>,
do_implied_nodegroups: bool,
model: &dyn ModelAccess,
) -> Result<EnsureNodegroupResult, SemanticChildError> {
let tiles_store = match &self.tiles {
Some(t) => t,
None => {
return Ok(EnsureNodegroupResult {
values: HashMap::new(),
implied_nodegroups: Vec::new(),
all_nodegroups_map: all_nodegroups.clone(),
});
}
};
ensure_nodegroup(
all_values_map,
all_nodegroups,
nodegroup_id,
add_if_missing,
nodegroup_permissions,
do_implied_nodegroups,
model,
tiles_store,
)
}
pub fn populate(
&self,
lazy: bool,
nodegroup_ids: &[String],
root_node_alias: &str,
model: &dyn ModelAccess,
) -> Result<PopulateResult, SemanticChildError> {
let nodegroup_permissions = model.get_permitted_nodegroups();
let cache_len = if let Ok(cache) = self.pseudo_cache.lock() {
cache.len()
} else {
0
};
let cache_populated = cache_len > 1;
let already_loaded: HashSet<String> = if cache_populated {
if let Ok(loaded) = self.loaded_nodegroups.lock() {
loaded
.iter()
.filter(|(_, state)| **state == LoadState::Loaded)
.map(|(id, _)| id.clone())
.collect()
} else {
HashSet::new()
}
} else {
HashSet::new()
};
let nodegroups_to_process: Vec<String> = nodegroup_ids
.iter()
.filter(|id| !already_loaded.contains(*id))
.cloned()
.collect();
let mut all_values: HashMap<String, Option<bool>> = HashMap::new();
let mut all_nodegroups: HashMap<String, bool> = HashMap::new();
for nodegroup_id in nodegroup_ids.iter() {
let is_loaded = already_loaded.contains(nodegroup_id);
all_nodegroups.insert(nodegroup_id.clone(), is_loaded);
}
all_values.insert(root_node_alias.to_string(), Some(false));
let mut all_structured_values: HashMap<String, PseudoListCore> =
if !already_loaded.is_empty() {
if let Ok(cache) = self.pseudo_cache.lock() {
cache.clone()
} else {
HashMap::new()
}
} else {
HashMap::new()
};
if !lazy {
let mut implied_nodegroups_set = HashSet::new();
for nodegroup_id in nodegroups_to_process.iter() {
let result = self.ensure_nodegroup(
&all_values,
&mut all_nodegroups,
nodegroup_id,
true,
&nodegroup_permissions,
false,
model,
)?;
for (alias, pseudo_list) in result.values {
all_structured_values.insert(alias, pseudo_list);
}
for implied_ng in result.implied_nodegroups.iter() {
if implied_ng != nodegroup_id {
implied_nodegroups_set.insert(implied_ng.clone());
}
}
}
while !implied_nodegroups_set.is_empty() {
let current_implied: Vec<String> = implied_nodegroups_set.iter().cloned().collect();
implied_nodegroups_set.clear();
for nodegroup_id in current_implied.iter() {
let current_value = all_nodegroups.get(nodegroup_id);
let should_process = matches!(current_value, Some(&false) | None);
if should_process {
let result = self.ensure_nodegroup(
&all_values,
&mut all_nodegroups,
nodegroup_id,
true,
&nodegroup_permissions,
true,
model,
)?;
for (alias, pseudo_list) in result.values {
all_structured_values.insert(alias, pseudo_list);
}
for implied_ng in result.implied_nodegroups.iter() {
implied_nodegroups_set.insert(implied_ng.clone());
}
}
}
}
}
let root_node = model
.get_root_node()
.map_err(SemanticChildError::ModelNotInitialized)?;
let edges = model.get_edges().ok_or_else(|| {
SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
})?;
let child_node_ids = edges.get(&root_node.nodeid).cloned().unwrap_or_default();
let root_pseudo =
PseudoValueCore::from_node_and_tile(root_node, None, None, child_node_ids);
let root_list = PseudoListCore::from_values_with_cardinality(
root_node_alias.to_string(),
vec![root_pseudo],
true,
);
all_structured_values.insert(root_node_alias.to_string(), root_list);
if let Ok(mut cache) = self.pseudo_cache.lock() {
for (alias, pseudo_list) in all_structured_values.iter() {
cache.insert(alias.clone(), pseudo_list.clone());
}
}
Ok(PopulateResult {
values: all_structured_values,
all_values_map: all_values,
all_nodegroups_map: all_nodegroups,
})
}
pub fn get_semantic_child_value(
&self,
parent_tile_id: Option<&String>,
parent_node_id: &str,
parent_nodegroup_id: Option<&String>,
child_alias: &str,
model: &dyn ModelAccess,
) -> Result<SemanticChildResult, SemanticChildError> {
let child_nodes = model
.get_child_nodes(parent_node_id)
.map_err(SemanticChildError::ModelNotInitialized)?;
let child_node = child_nodes
.values()
.find(|n| n.alias.as_deref() == Some(child_alias))
.ok_or_else(|| SemanticChildError::ChildNotFound {
alias: child_alias.to_string(),
})?;
let tiles = self
.tiles
.as_ref()
.ok_or(SemanticChildError::TilesNotInitialized)?;
let matching_tiles: Vec<Arc<StaticTile>> = tiles
.values()
.filter(|tile| {
matches_semantic_child(parent_tile_id, parent_nodegroup_id, child_node, tile)
})
.map(|t| Arc::new(t.clone()))
.collect();
if matching_tiles.is_empty() {
return Ok(SemanticChildResult::Empty);
}
let edges = model.get_edges().ok_or_else(|| {
SemanticChildError::ModelNotInitialized("Model edges not initialized".to_string())
})?;
let child_node_ids = edges.get(&child_node.nodeid).cloned().unwrap_or_default();
let nodegroups = model.get_nodegroups();
let is_single = is_node_single_cardinality(child_node, nodegroups);
let values: Vec<PseudoValueCore> = matching_tiles
.into_iter()
.map(|tile| {
let tile_data = tile.data.get(&child_node.nodeid).cloned();
PseudoValueCore::from_node_and_tile(
Arc::clone(child_node),
Some(tile),
tile_data,
child_node_ids.clone(),
)
})
.collect();
if is_single && values.len() == 1 {
Ok(SemanticChildResult::Single(
values.into_iter().next().unwrap(),
))
} else {
let list = PseudoListCore::from_values_with_cardinality(
child_alias.to_string(),
values,
is_single,
);
Ok(SemanticChildResult::List(list))
}
}
pub fn resolve_path(
&self,
path: &str,
model: &dyn ModelAccess,
) -> Result<PathResolutionInfo, PathError> {
let root_node = model
.get_root_node()
.map_err(PathError::ModelNotInitialized)?;
let nodes = model
.get_nodes()
.ok_or_else(|| PathError::ModelNotInitialized("Nodes not initialized".into()))?;
let edges = model
.get_edges()
.ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
let nodegroups = model.get_nodegroups();
resolve_path_segments(path, &root_node, nodes, edges, nodegroups)
}
pub fn resolve_and_filter_tiles(
&self,
path: &str,
model: &dyn ModelAccess,
filter_tile_id: Option<&str>,
) -> Result<(PathResolutionInfo, Vec<StaticTile>), PathError> {
let tiles_store = self.tiles.as_ref().ok_or(PathError::TilesNotInitialized)?;
resolve_and_filter_tiles(
path,
model,
tiles_store,
&self.nodegroup_index,
filter_tile_id,
)
}
pub fn get_values_at_path(
&self,
path: &str,
model: &dyn ModelAccess,
filter_tile_id: Option<&str>,
) -> Result<PseudoListCore, PathError> {
let (info, tiles) = self.resolve_and_filter_tiles(path, model, filter_tile_id)?;
let edges = model
.get_edges()
.ok_or_else(|| PathError::ModelNotInitialized("Edges not initialized".into()))?;
let tiles_wrapped: Vec<Option<Arc<StaticTile>>> =
tiles.into_iter().map(|t| Some(Arc::new(t))).collect();
let pseudo_list =
create_pseudo_list_from_tiles(info.target_node, tiles_wrapped, edges, info.is_single);
Ok(pseudo_list)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_state_equality() {
assert_eq!(LoadState::NotLoaded, LoadState::NotLoaded);
assert_eq!(LoadState::Loading, LoadState::Loading);
assert_eq!(LoadState::Loaded, LoadState::Loaded);
assert_ne!(LoadState::NotLoaded, LoadState::Loaded);
}
#[test]
fn test_is_node_single_cardinality_collector() {
let node = Arc::new(StaticNode {
nodeid: "test-node".to_string(),
name: "Test Node".to_string(),
alias: Some("test".to_string()),
datatype: "string".to_string(),
is_collector: true,
nodegroup_id: Some("test-nodegroup".to_string()),
graph_id: "test-graph".to_string(),
isrequired: false,
exportable: true,
sortorder: None,
config: HashMap::new(),
parentproperty: None,
ontologyclass: None,
description: None,
fieldname: None,
hascustomalias: false,
issearchable: false,
istopnode: false,
sourcebranchpublication_id: None,
source_identifier_id: None,
is_immutable: None,
});
assert!(!is_node_single_cardinality(&node, None));
}
#[test]
fn test_is_node_single_cardinality_non_collector() {
let node = Arc::new(StaticNode {
nodeid: "test-node".to_string(),
name: "Test Node".to_string(),
alias: Some("test".to_string()),
datatype: "string".to_string(),
is_collector: false,
nodegroup_id: Some("test-nodegroup".to_string()),
graph_id: "test-graph".to_string(),
isrequired: false,
exportable: true,
sortorder: None,
config: HashMap::new(),
parentproperty: None,
ontologyclass: None,
description: None,
fieldname: None,
hascustomalias: false,
issearchable: false,
istopnode: false,
sourcebranchpublication_id: None,
source_identifier_id: None,
is_immutable: None,
});
assert!(is_node_single_cardinality(&node, None));
}
}