zen-engine 2.0.1

Business rules engine
Documentation
use std::collections::VecDeque;
use std::sync::Arc;

use ahash::{HashMap, HashMapExt, HashSet};
use zen_expression::variable::VariableType;

use crate::workspace::db::{Db, DictionaryUnitEntry};
use crate::workspace::graph::analysis::{
    GraphAnalysis, GraphAnalyzer, GraphSignature, SignatureResolution,
};
use zen_types::decision::DecisionNodeKind;

use crate::policy::queries::scope::VariableTypeScope;
use crate::workspace::types::{
    Cursor, CursorTarget, InputProperty, OutputProperty, PropertyKind, ScopeRequest,
};

impl Db {
    pub(crate) fn graph_analysis(&self, path: &Arc<str>) -> Option<Arc<GraphAnalysis>> {
        let snap = self.snapshot();
        let doc = snap.graphs.get(path)?.clone();
        if let Some(analysis) = self.cached_graph_analysis(path) {
            return Some(analysis);
        }
        let content = doc.as_graph()?;
        self.graph_stack.borrow_mut().push(path.clone());
        self.graph_dep_frame_push(path);
        let analysis = Arc::new(GraphAnalyzer::new(self, path.clone(), content).analyze());
        self.graph_stack.borrow_mut().pop();
        let (docs, functions) = self.graph_dep_frame_pop();
        self.graph_dep_record_many(docs.iter().cloned());
        for (&key, &state) in &functions {
            self.graph_fn_record(key, state);
        }
        self.store_graph_analysis(path, docs, functions, analysis.clone());
        Some(analysis)
    }

    pub(crate) fn is_graph(&self, path: &str) -> bool {
        self.snapshot().graphs.contains_key(path)
    }

    pub(crate) fn graph_imports(&self, path: &str) -> Vec<Arc<str>> {
        self.snapshot()
            .graphs
            .get(path)
            .and_then(|doc| doc.as_graph())
            .map(|content| content.imports.clone())
            .unwrap_or_default()
    }

    pub(crate) fn graph_dictionary_blocks(&self, imports: &[Arc<str>]) -> Vec<DictionaryUnitEntry> {
        let snap = self.snapshot();
        let mut seen: HashSet<Arc<str>> = HashSet::default();
        let mut visited: HashSet<Arc<str>> = HashSet::default();
        let mut queue: VecDeque<Arc<str>> = imports.iter().cloned().collect();
        let mut out: Vec<DictionaryUnitEntry> = Vec::new();
        while let Some(path) = queue.pop_front() {
            if !visited.insert(path.clone()) {
                continue;
            }
            self.graph_dep_record(&path);
            let Some(parsed) = snap.all_parsed.get(&path) else {
                continue;
            };
            for block in &parsed.policy.dictionaries {
                if !seen.insert(block.ir.name.clone()) {
                    continue;
                }
                out.push(DictionaryUnitEntry {
                    policy_path: path.clone(),
                    block_id: block.id.clone(),
                    ir: block.ir.clone(),
                });
            }
            queue.extend(parsed.policy.imports().iter().cloned());
        }
        out
    }

    pub(crate) fn graph_dictionary_types(
        &self,
        imports: &[Arc<str>],
    ) -> HashMap<Arc<str>, VariableType> {
        let mut out = HashMap::new();
        for entry in self.graph_dictionary_blocks(imports) {
            out.insert(entry.ir.name.clone(), entry.ir.enum_type());
        }
        out
    }

    pub(crate) fn decision_signature(&self, key: &str) -> SignatureResolution {
        let key_arc: Arc<str> = Arc::from(key);
        self.graph_dep_record(&key_arc);
        let snap = self.snapshot();
        if snap.graphs.contains_key(&key_arc) {
            if self.graph_stack.borrow().iter().any(|p| p.as_ref() == key) {
                return SignatureResolution::Recursive;
            }
            return match self.graph_analysis(&key_arc) {
                Some(analysis) => SignatureResolution::Found(analysis.signature.clone()),
                None => SignatureResolution::Missing,
            };
        }
        if snap.all_parsed.contains_key(&key_arc) {
            if let Some(&component) = snap.policy_to_component.get(&key_arc) {
                self.graph_dep_record_many(snap.components[component].iter().cloned());
            }
            let req = ScopeRequest::for_policy(key);
            let input = VariableType::empty_object();
            let output = VariableType::empty_object();
            for property in self.inputs(&req) {
                input.insert_at_path(&property.path, &property.resolved_type, true);
                output.insert_at_path(&property.path, &property.resolved_type, true);
            }
            for property in self.outputs(&req) {
                output.insert_at_path(&property.path, &property.resolved_type, true);
            }
            return SignatureResolution::Found(GraphSignature { input, output });
        }
        SignatureResolution::Missing
    }

    pub(crate) fn graph_inputs(&self, path: &str) -> Vec<InputProperty> {
        let path_arc: Arc<str> = Arc::from(path);
        let Some(analysis) = self.graph_analysis(&path_arc) else {
            return Vec::new();
        };
        match &analysis.signature.input {
            VariableType::Object(fields) => {
                let mut properties: Vec<InputProperty> = fields
                    .borrow()
                    .iter()
                    .map(|(key, resolved_type)| InputProperty {
                        path: Arc::from(key.as_ref()),
                        resolved_type: resolved_type.shallow_clone(),
                    })
                    .collect();
                properties.sort_by(|a, b| a.path.cmp(&b.path));
                properties
            }
            VariableType::Any => analysis
                .inferred_inputs
                .iter()
                .map(|path| InputProperty {
                    path: path.clone(),
                    resolved_type: VariableType::Any,
                })
                .collect(),
            _ => Vec::new(),
        }
    }

    pub(crate) fn graph_outputs(&self, path: &str) -> Vec<OutputProperty> {
        let path_arc: Arc<str> = Arc::from(path);
        let Some(analysis) = self.graph_analysis(&path_arc) else {
            return Vec::new();
        };
        let (output_base, _) = analysis.signature.output.unwrap_nullable();
        let VariableType::Object(fields) = output_base else {
            return Vec::new();
        };
        let input_has = |key: &str| -> bool {
            let (input_base, _) = analysis.signature.input.unwrap_nullable();
            match input_base {
                VariableType::Object(input_fields) => input_fields.borrow().contains_key(key),
                _ => false,
            }
        };
        let mut properties: Vec<OutputProperty> = fields
            .borrow()
            .iter()
            .filter_map(|(key, resolved_type)| {
                let written_by = self.graph_written_by(&path_arc, key.as_ref());
                if written_by.is_none() && input_has(key.as_ref()) {
                    return None;
                }
                Some(OutputProperty {
                    path: Arc::from(key.as_ref()),
                    resolved_type: resolved_type.shallow_clone(),
                    kind: PropertyKind::Computed,
                    written_by,
                    instance_of: None,
                })
            })
            .collect();
        properties.sort_by(|a, b| a.path.cmp(&b.path));
        properties
    }

    pub(crate) fn graph_cell_expected(&self, cursor: &Cursor) -> Option<VariableType> {
        let snap = self.snapshot();
        let doc = snap.graphs.get(&cursor.policy_path)?.clone();
        let content = doc.as_graph()?;
        let node = content.nodes.iter().find(|n| n.id == cursor.block_id)?;
        let DecisionNodeKind::DecisionTableNode { content } = &node.kind else {
            return None;
        };
        let CursorTarget::DecisionTableCell { col, .. } = &cursor.target else {
            return None;
        };
        let dictionaries = self.graph_dictionary_types(&doc.as_graph()?.imports);
        GraphAnalyzer::output_expected(content, col, &dictionaries)
    }

    pub(crate) fn graph_unchecked_nodes(&self, path: &str) -> Vec<Arc<str>> {
        let path_arc: Arc<str> = Arc::from(path);
        let Some(analysis) = self.graph_analysis(&path_arc) else {
            return Vec::new();
        };
        let mut nodes: Vec<Arc<str>> = analysis
            .nodes
            .iter()
            .filter(|(_, node)| node.unchecked || node.opaque)
            .map(|(id, _)| id.clone())
            .collect();
        nodes.sort();
        nodes
    }
}