mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 08:02:28 +00:00
add logic
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user