add logic

This commit is contained in:
Stefan
2025-05-19 23:13:33 +02:00
parent 2a80700941
commit c030cf8fb6
5 changed files with 155 additions and 33 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ impl PolicyRuleCondition {
pub fn to_vec(&self) -> Vec<&str> {
match self {
PolicyRuleCondition::Simple { expression } => once(expression.as_str())
.chain(self.to_vec().into_iter())
// .chain(self.to_vec().into_iter())
.collect(),
PolicyRuleCondition::All { items }
| PolicyRuleCondition::Any { items }
+117 -25
View File
@@ -1,19 +1,22 @@
use crate::model::{PolicyBundle, PolicyDecisionTable, PolicyDocument, PolicyRule};
use ahash::{HashMap, HashMapExt};
use bumpalo::Bump;
use petgraph::algo::toposort;
use petgraph::graph::DiGraph;
use std::sync::Arc;
use zen_expression::intellisense::{DependencyProvider, DependencyProviderResponse, IntelliSense};
use zen_expression::intellisense::{DependencyProviderResponse, IntelliSense};
use zen_expression::lexer::Lexer;
use zen_expression::parser::Parser;
use zen_expression::ExpressionKind;
pub struct PolicyCompiler {}
// Ultimately, backed by an array of execution steps needed to be performed
#[derive(Debug, Clone)]
pub enum PolicyExecutionStep {
DecisionTable(Arc<PolicyDecisionTable>),
Rule(Arc<PolicyRule>),
}
#[derive(Debug, Clone)]
struct PolicyCompilerStep {
execution_step: PolicyExecutionStep,
dependency_provider: DependencyProviderResponse,
@@ -22,15 +25,36 @@ struct PolicyCompilerStep {
// Computes the execution order for Decision Tables and Rules
impl PolicyCompiler {
fn compile_bundle(policy_bundle: PolicyBundle) {
let mut arena = Bump::new();
let is = IntelliSense::new();
let steps = policy_bundle
.documents
.iter()
.flat_map(|d| match d.as_ref() {
PolicyDocument::DecisionTable { content } => vec![PolicyCompilerStep {
dependency_provider: Default::default(),
execution_step: PolicyExecutionStep::DecisionTable(content.clone()),
}],
PolicyDocument::RuleSet { content } => content
.rules
.iter()
.map(|r| Self::compile_rule(r.clone()))
.collect::<Vec<_>>(),
})
.collect::<Vec<_>>();
for document in policy_bundle.documents.iter() {
match document.as_ref() {
PolicyDocument::DecisionTable { content } => {}
PolicyDocument::RuleSet { content } => {}
}
}
println!("{:#?}", steps);
let sorted_steps = Self::sort_steps_by_dependencies(steps);
println!("{:#?}", sorted_steps);
let sorted_expressions = sorted_steps
.iter()
.map(|s| match &s.execution_step {
PolicyExecutionStep::DecisionTable(_) => String::from("decisionTable"),
PolicyExecutionStep::Rule(r) => r.outcome.clone(),
})
.collect::<Vec<_>>();
println!("{:#?}", sorted_expressions);
}
fn compile_rule(rule: Arc<PolicyRule>) -> PolicyCompilerStep {
@@ -39,25 +63,79 @@ impl PolicyCompiler {
let outcome_str = rule.outcome.as_str();
is.dependencies(outcome_str).unwrap_or_default()
};
if let Some(condition) = &rule.conditions {
}
rule.conditions.iter().fold(outcome, |acc, c| {
let rule_str =
})
let Some(condition) = &rule.conditions else {
return PolicyCompilerStep {
execution_step: PolicyExecutionStep::Rule(rule.clone()),
dependency_provider: outcome,
};
};
let complete_outcome = condition.to_vec().iter().fold(outcome, |acc, c| {
let condition_provider = is.dependencies(c);
acc.merge(condition_provider.unwrap_or_default())
});
PolicyCompilerStep {
execution_step: PolicyExecutionStep::Rule(rule),
dependency_provider: outcome_deps.unwrap_or_default(),
execution_step: PolicyExecutionStep::Rule(rule.clone()),
dependency_provider: complete_outcome,
}
}
fn compile_decision_table(
decision_table: Arc<PolicyDecisionTable>,
c: &mut InternalCompiler,
) -> PolicyCompilerStep {
// fn compile_decision_table(
// decision_table: Arc<PolicyDecisionTable>,
// c: &mut InternalCompiler,
// ) -> PolicyCompilerStep {
// }
fn sort_steps_by_dependencies(steps: Vec<PolicyCompilerStep>) -> Vec<PolicyCompilerStep> {
// Create a directed graph
let mut graph = DiGraph::<usize, ()>::new();
let mut nodes = Vec::new();
let mut provides_map: HashMap<_, _> = HashMap::new();
// Create nodes for each step
for (idx, step) in steps.iter().enumerate() {
let node_idx = graph.add_node(idx);
nodes.push(node_idx);
// Map provided values to step indices
for provided in &step.dependency_provider.provides {
provides_map.insert(provided.clone(), idx);
}
}
// Add edges based on dependencies
for (idx, step) in steps.iter().enumerate() {
for dependency in &step.dependency_provider.dependencies {
if let Some(&provider_idx) = provides_map.get(dependency) {
if provider_idx != idx {
// Avoid self-dependencies
graph.add_edge(nodes[provider_idx], nodes[idx], ());
}
}
}
}
// Perform topological sort
match toposort(&graph, None) {
Ok(sorted_indices) => {
// Reconstruct the sorted steps
sorted_indices
.into_iter()
.map(|node_idx| {
let step_idx = graph[node_idx];
steps[step_idx].clone()
})
.collect()
}
Err(_cycle) => {
// Handle cycles in the dependency graph
// For simplicity, return the original order
eprintln!("Warning: Dependency cycle detected in policy steps");
steps
}
}
}
}
@@ -65,3 +143,17 @@ struct InternalCompiler<'arena> {
arena: &'arena mut Bump,
lexer: Lexer<'arena>,
}
#[cfg(test)]
mod test {
use crate::model::PolicyBundle;
use crate::policy::compiler::PolicyCompiler;
#[test]
fn initial() {
let policy_bundle: PolicyBundle =
serde_json::from_str(include_str!("policy.json")).unwrap();
let s = PolicyCompiler::compile_bundle(policy_bundle);
}
}
+11 -3
View File
@@ -1,9 +1,9 @@
{
"files": [
"documents": [
{
"type": "policy",
"type": "ruleSet",
"content": {
"expressions": [
"rules": [
{
"name": "Eligibility Determination",
"outcome": "customer.eligible = true",
@@ -121,6 +121,14 @@
"type": "simple",
"expression": "application.type == 'LOAN'"
}
},
{
"name": "Final Term Calculation",
"outcome": "some_new_field = 123",
"conditions": {
"type": "simple",
"expression": "customer.finalTerm == 0"
}
}
]
}
@@ -8,7 +8,7 @@ use bumpalo::Bump;
use serde::Serialize;
use std::rc::Rc;
#[derive(Debug, Serialize, Default)]
#[derive(Debug, Serialize, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DependencyProviderResponse {
pub provides: HashSet<Rc<str>>,
@@ -78,6 +78,15 @@ impl<'arena> DependencyProvider<'arena> {
DependencyProviderResponse::from(provider)
}
fn duplicate(&self) -> DependencyProvider<'arena> {
Self {
provides: Default::default(),
dependencies: Default::default(),
resolution: DependencyResolution::Static,
arena: self.arena,
}
}
fn set_dynamic(&mut self) {
self.resolution = DependencyResolution::Dynamic;
}
@@ -95,8 +104,17 @@ impl<'arena> DependencyProvider<'arena> {
continue;
};
// Will contain only dependencies, which need to be filtered for existing provides
let mut local_provider = self.duplicate();
local_provider.determine(v, scope);
self.dependencies.extend(
local_provider
.dependencies
.into_iter()
.filter(|d| !self.provides.contains(d)),
);
self.provides.insert(Rc::from(*key));
self.determine(v, scope);
}
D::None
+6 -2
View File
@@ -700,7 +700,9 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> {
let (identifier_with_postfix, combination) =
self.with_postfix(identifier_node, &expression_parser);
if let Some(&TokenKind::Operator(Operator::Assign)) = self.current_kind() {
if combination != PostfixCombination::Property {
if combination != PostfixCombination::Property
&& combination != PostfixCombination::Inherit
{
return self.error_with_node(
AstNodeError::Custom {
span: identifier_with_postfix.span().unwrap_or_default(),
@@ -945,7 +947,9 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> {
});
self.next();
let (key_node, combination) = self.with_postfix(identifier_node, expression_parser);
if combination != PostfixCombination::Property {
if combination != PostfixCombination::Property
&& combination != PostfixCombination::Inherit
{
return self.error_with_node(
AstNodeError::Custom {
span: key_node.span().unwrap_or_default(),