zen-engine 1.0.0-beta.11

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

use zen_expression::variable::VariableType;
use zen_types::decision::{DecisionNode, DecisionNodeKind, DecisionTableContent};

use crate::policy::blocks::IntelliSenseSource;
use crate::policy::queries::scope::VariableTypeScope;
use crate::workspace::db::Db;
use crate::workspace::graph::analysis::{GraphAnalyzer, GraphNodeAnalysis};
use crate::workspace::types::{Cursor, CursorTarget, ExpressionKind};

impl Db {
    pub(crate) fn graph_resolve_cursor(
        &self,
        cursor: &Cursor,
    ) -> Option<(Arc<str>, ExpressionKind, VariableType)> {
        let snap = self.snapshot();
        let doc = snap.graphs.get(&cursor.policy_path)?.clone();
        let content = doc.as_graph()?;
        let analysis = self.graph_analysis(&cursor.policy_path)?;
        let node = content.nodes.iter().find(|n| n.id == cursor.block_id)?;
        let node_analysis = analysis.nodes.get(&cursor.block_id)?;
        self.resolve_in_node(node, node_analysis, cursor)
    }

    pub(crate) fn resolve_in_node(
        &self,
        node: &DecisionNode,
        node_analysis: &GraphNodeAnalysis,
        cursor: &Cursor,
    ) -> Option<(Arc<str>, ExpressionKind, VariableType)> {
        if matches!(cursor.target, CursorTarget::TransformInput) {
            let attributes = super::editor::NodePaths::attributes(node)?;
            let field = attributes.input_field.as_ref()?;
            let scope =
                GraphAnalyzer::scope_with_nodes(&node_analysis.input, &node_analysis.nodes_scope);
            return Some((field.clone(), ExpressionKind::Standard, scope));
        }

        match &node.kind {
            DecisionNodeKind::ExpressionNode { content } => {
                let CursorTarget::Expression { id } = &cursor.target else {
                    return None;
                };
                let row = content.expressions.iter().find(|row| row.id == *id)?;
                let dollar = node_analysis
                    .dollar
                    .clone()
                    .unwrap_or_else(VariableType::empty_object);
                let scope = GraphAnalyzer::scope_with(
                    &node_analysis.handler_input,
                    &[
                        ("$", dollar),
                        ("$nodes", node_analysis.nodes_scope.shallow_clone()),
                    ],
                );
                Some((row.value.clone(), ExpressionKind::Standard, scope))
            }
            DecisionNodeKind::SwitchNode { content } => {
                let CursorTarget::Expression { id } = &cursor.target else {
                    return None;
                };
                let statement = content.statements.iter().find(|s| s.id == *id)?;
                let scope = GraphAnalyzer::scope_with_nodes(
                    &node_analysis.input,
                    &node_analysis.nodes_scope,
                );
                Some((statement.condition.clone(), ExpressionKind::Standard, scope))
            }
            DecisionNodeKind::DecisionTableNode { content } => {
                self.resolve_in_table(content, node_analysis, cursor)
            }
            _ => None,
        }
    }

    fn resolve_in_table(
        &self,
        content: &DecisionTableContent,
        node_analysis: &GraphNodeAnalysis,
        cursor: &Cursor,
    ) -> Option<(Arc<str>, ExpressionKind, VariableType)> {
        let base_scope = GraphAnalyzer::scope_with_nodes(
            &node_analysis.handler_input,
            &node_analysis.nodes_scope,
        );
        match &cursor.target {
            CursorTarget::DecisionTableHead { col } => {
                let column = content.inputs.iter().find(|c| c.id == *col)?;
                let field = column.field.as_ref()?;
                Some((field.clone(), ExpressionKind::Standard, base_scope))
            }
            CursorTarget::DecisionTableCell { row, col } => {
                let rule = content
                    .rules
                    .iter()
                    .enumerate()
                    .find(|(idx, rule)| GraphAnalyzer::row_key(rule, *idx) == *row)
                    .map(|(_, rule)| rule)?;
                let cell = rule.get(col).cloned().unwrap_or_else(|| Arc::from(""));
                let cell = &cell;

                if let Some(column) = content.inputs.iter().find(|c| c.id == *col) {
                    return match &column.field {
                        Some(field) => {
                            let intellisense = self.graph_intellisense();
                            let field_type = IntelliSenseSource::analyze(
                                &mut intellisense.borrow_mut(),
                                field,
                                ExpressionKind::Standard,
                                &base_scope,
                            )
                            .return_type
                            .shallow_clone();
                            Some((
                                cell.clone(),
                                ExpressionKind::Unary,
                                base_scope.with_dollar(&field_type),
                            ))
                        }
                        None => Some((cell.clone(), ExpressionKind::Standard, base_scope)),
                    };
                }
                content
                    .outputs
                    .iter()
                    .any(|c| c.id == *col)
                    .then(|| (cell.clone(), ExpressionKind::Standard, base_scope))
            }
            _ => None,
        }
    }
}