mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat(router): Add Smart Routing to route payments efficiently (#2665)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: shashank_attarde <shashank.attarde@juspay.in> Co-authored-by: Aprabhat19 <amishaprabhat@gmail.com> Co-authored-by: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com>
This commit is contained in:
447
crates/euclid/src/dssa/analyzer.rs
Normal file
447
crates/euclid/src/dssa/analyzer.rs
Normal file
@ -0,0 +1,447 @@
|
||||
//! Static Analysis for the Euclid Rule DSL
|
||||
//!
|
||||
//! Exposes certain functions that can be used to perform static analysis over programs
|
||||
//! in the Euclid Rule DSL. These include standard control flow analyses like testing
|
||||
//! conflicting assertions, to Domain Specific Analyses making use of the
|
||||
//! [`Knowledge Graph Framework`](crate::dssa::graph).
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::{graph::Memoization, types::EuclidAnalysable};
|
||||
use crate::{
|
||||
dssa::{graph, state_machine, truth, types},
|
||||
frontend::{
|
||||
ast,
|
||||
dir::{self, EuclidDirFilter},
|
||||
vir,
|
||||
},
|
||||
types::{DataType, Metadata},
|
||||
};
|
||||
|
||||
/// Analyses conflicting assertions on the same key in a conjunctive context.
|
||||
///
|
||||
/// For example,
|
||||
/// ```notrust
|
||||
/// payment_method = card && ... && payment_method = bank_debit
|
||||
/// ```notrust
|
||||
/// This is a condition that will never evaluate to `true` given a single
|
||||
/// payment method and needs to be caught in analysis.
|
||||
pub fn analyze_conflicting_assertions(
|
||||
keywise_assertions: &FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>>,
|
||||
assertion_metadata: &FxHashMap<&dir::DirValue, &Metadata>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
for (key, value_set) in keywise_assertions {
|
||||
if value_set.len() > 1 {
|
||||
let err_type = types::AnalysisErrorType::ConflictingAssertions {
|
||||
key: key.clone(),
|
||||
values: value_set
|
||||
.iter()
|
||||
.map(|val| types::ValueData {
|
||||
value: (*val).clone(),
|
||||
metadata: assertion_metadata
|
||||
.get(val)
|
||||
.map(|meta| (*meta).clone())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Err(types::AnalysisError {
|
||||
error_type: err_type,
|
||||
metadata: Default::default(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Analyses exhaustive negations on the same key in a conjunctive context.
|
||||
///
|
||||
/// For example,
|
||||
/// ```notrust
|
||||
/// authentication_type /= three_ds && ... && authentication_type /= no_three_ds
|
||||
/// ```notrust
|
||||
/// This is a condition that will never evaluate to `true` given any authentication_type
|
||||
/// since all the possible values authentication_type can take have been negated.
|
||||
pub fn analyze_exhaustive_negations(
|
||||
keywise_negations: &FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>>,
|
||||
keywise_negation_metadata: &FxHashMap<dir::DirKey, Vec<&Metadata>>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
for (key, negation_set) in keywise_negations {
|
||||
let mut value_set = if let Some(set) = key.kind.get_value_set() {
|
||||
set
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
value_set.retain(|val| !negation_set.contains(val));
|
||||
|
||||
if value_set.is_empty() {
|
||||
let error_type = types::AnalysisErrorType::ExhaustiveNegation {
|
||||
key: key.clone(),
|
||||
metadata: keywise_negation_metadata
|
||||
.get(key)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.cloned()
|
||||
.cloned()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Err(types::AnalysisError {
|
||||
error_type,
|
||||
metadata: Default::default(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_negated_assertions(
|
||||
keywise_assertions: &FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>>,
|
||||
assertion_metadata: &FxHashMap<&dir::DirValue, &Metadata>,
|
||||
keywise_negations: &FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>>,
|
||||
negation_metadata: &FxHashMap<&dir::DirValue, &Metadata>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
for (key, negation_set) in keywise_negations {
|
||||
let assertion_set = if let Some(set) = keywise_assertions.get(key) {
|
||||
set
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let intersection = negation_set & assertion_set;
|
||||
|
||||
intersection.iter().next().map_or(Ok(()), |val| {
|
||||
let error_type = types::AnalysisErrorType::NegatedAssertion {
|
||||
value: (*val).clone(),
|
||||
assertion_metadata: assertion_metadata
|
||||
.get(*val)
|
||||
.cloned()
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
negation_metadata: negation_metadata
|
||||
.get(*val)
|
||||
.cloned()
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
Err(types::AnalysisError {
|
||||
error_type,
|
||||
metadata: Default::default(),
|
||||
})
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn perform_condition_analyses(
|
||||
context: &types::ConjunctiveContext<'_>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
let mut assertion_metadata: FxHashMap<&dir::DirValue, &Metadata> = FxHashMap::default();
|
||||
let mut keywise_assertions: FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>> =
|
||||
FxHashMap::default();
|
||||
let mut negation_metadata: FxHashMap<&dir::DirValue, &Metadata> = FxHashMap::default();
|
||||
let mut keywise_negation_metadata: FxHashMap<dir::DirKey, Vec<&Metadata>> =
|
||||
FxHashMap::default();
|
||||
let mut keywise_negations: FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>> =
|
||||
FxHashMap::default();
|
||||
|
||||
for ctx_val in context {
|
||||
let key = if let Some(k) = ctx_val.value.get_key() {
|
||||
k
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let dir::DirKeyKind::Connector = key.kind {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !matches!(key.kind.get_type(), DataType::EnumVariant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match ctx_val.value {
|
||||
types::CtxValueKind::Assertion(val) => {
|
||||
keywise_assertions
|
||||
.entry(key.clone())
|
||||
.or_default()
|
||||
.insert(val);
|
||||
|
||||
assertion_metadata.insert(val, ctx_val.metadata);
|
||||
}
|
||||
|
||||
types::CtxValueKind::Negation(vals) => {
|
||||
let negation_set = keywise_negations.entry(key.clone()).or_default();
|
||||
|
||||
for val in vals {
|
||||
negation_set.insert(val);
|
||||
negation_metadata.insert(val, ctx_val.metadata);
|
||||
}
|
||||
|
||||
keywise_negation_metadata
|
||||
.entry(key.clone())
|
||||
.or_default()
|
||||
.push(ctx_val.metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analyze_conflicting_assertions(&keywise_assertions, &assertion_metadata)?;
|
||||
analyze_exhaustive_negations(&keywise_negations, &keywise_negation_metadata)?;
|
||||
analyze_negated_assertions(
|
||||
&keywise_assertions,
|
||||
&assertion_metadata,
|
||||
&keywise_negations,
|
||||
&negation_metadata,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn perform_context_analyses(
|
||||
context: &types::ConjunctiveContext<'_>,
|
||||
knowledge_graph: &graph::KnowledgeGraph<'_>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
perform_condition_analyses(context)?;
|
||||
let mut memo = Memoization::new();
|
||||
knowledge_graph
|
||||
.perform_context_analysis(context, &mut memo)
|
||||
.map_err(|err| types::AnalysisError {
|
||||
error_type: types::AnalysisErrorType::GraphAnalysis(err, memo),
|
||||
metadata: Default::default(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn analyze<O: EuclidAnalysable + EuclidDirFilter>(
|
||||
program: ast::Program<O>,
|
||||
knowledge_graph: Option<&graph::KnowledgeGraph<'_>>,
|
||||
) -> Result<vir::ValuedProgram<O>, types::AnalysisError> {
|
||||
let dir_program = ast::lowering::lower_program(program)?;
|
||||
|
||||
let selection_data = state_machine::make_connector_selection_data(&dir_program);
|
||||
let mut ctx_manager = state_machine::AnalysisContextManager::new(&dir_program, &selection_data);
|
||||
while let Some(ctx) = ctx_manager.advance().map_err(|err| types::AnalysisError {
|
||||
metadata: Default::default(),
|
||||
error_type: types::AnalysisErrorType::StateMachine(err),
|
||||
})? {
|
||||
perform_context_analyses(ctx, knowledge_graph.unwrap_or(&truth::ANALYSIS_GRAPH))?;
|
||||
}
|
||||
|
||||
dir::lowering::lower_program(dir_program)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "ast_parser"))]
|
||||
mod tests {
|
||||
#![allow(clippy::panic, clippy::expect_used)]
|
||||
|
||||
use std::{ops::Deref, sync::Weak};
|
||||
|
||||
use euclid_macros::knowledge;
|
||||
|
||||
use super::*;
|
||||
use crate::{dirval, types::DummyOutput};
|
||||
|
||||
#[test]
|
||||
fn test_conflicting_assertion_detection() {
|
||||
let program_str = r#"
|
||||
default: ["stripe", "adyen"]
|
||||
|
||||
stripe_first: ["stripe", "adyen"]
|
||||
{
|
||||
payment_method = wallet {
|
||||
amount > 500 & capture_method = automatic
|
||||
amount < 500 & payment_method = card
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let (_, program) = ast::parser::program::<DummyOutput>(program_str).expect("Program");
|
||||
let analysis_result = analyze(program, None);
|
||||
|
||||
if let Err(types::AnalysisError {
|
||||
error_type: types::AnalysisErrorType::ConflictingAssertions { key, values },
|
||||
..
|
||||
}) = analysis_result
|
||||
{
|
||||
assert!(
|
||||
matches!(key.kind, dir::DirKeyKind::PaymentMethod),
|
||||
"Key should be payment_method"
|
||||
);
|
||||
let values: Vec<dir::DirValue> = values.into_iter().map(|v| v.value).collect();
|
||||
assert_eq!(values.len(), 2, "There should be 2 conflicting conditions");
|
||||
assert!(
|
||||
values.contains(&dirval!(PaymentMethod = Wallet)),
|
||||
"Condition should include payment_method = wallet"
|
||||
);
|
||||
assert!(
|
||||
values.contains(&dirval!(PaymentMethod = Card)),
|
||||
"Condition should include payment_method = card"
|
||||
);
|
||||
} else {
|
||||
panic!("Did not receive conflicting assertions error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exhaustive_negation_detection() {
|
||||
let program_str = r#"
|
||||
default: ["stripe"]
|
||||
|
||||
rule_1: ["adyen"]
|
||||
{
|
||||
payment_method /= wallet {
|
||||
capture_method = manual & payment_method /= card {
|
||||
authentication_type = three_ds & payment_method /= pay_later {
|
||||
amount > 1000 & payment_method /= bank_redirect {
|
||||
payment_method /= crypto
|
||||
& payment_method /= bank_debit
|
||||
& payment_method /= bank_transfer
|
||||
& payment_method /= upi
|
||||
& payment_method /= reward
|
||||
& payment_method /= voucher
|
||||
& payment_method /= gift_card
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let (_, program) = ast::parser::program::<DummyOutput>(program_str).expect("Program");
|
||||
let analysis_result = analyze(program, None);
|
||||
|
||||
if let Err(types::AnalysisError {
|
||||
error_type: types::AnalysisErrorType::ExhaustiveNegation { key, .. },
|
||||
..
|
||||
}) = analysis_result
|
||||
{
|
||||
assert!(
|
||||
matches!(key.kind, dir::DirKeyKind::PaymentMethod),
|
||||
"Expected key to be payment_method"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected exhaustive negation error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negated_assertions_detection() {
|
||||
let program_str = r#"
|
||||
default: ["stripe"]
|
||||
|
||||
rule_1: ["adyen"]
|
||||
{
|
||||
payment_method = wallet {
|
||||
amount > 500 {
|
||||
capture_method = automatic
|
||||
}
|
||||
|
||||
amount < 501 {
|
||||
payment_method /= wallet
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let (_, program) = ast::parser::program::<DummyOutput>(program_str).expect("Program");
|
||||
let analysis_result = analyze(program, None);
|
||||
|
||||
if let Err(types::AnalysisError {
|
||||
error_type: types::AnalysisErrorType::NegatedAssertion { value, .. },
|
||||
..
|
||||
}) = analysis_result
|
||||
{
|
||||
assert_eq!(
|
||||
value,
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
"Expected to catch payment_method = wallet as conflict"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected negated assertion error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negation_graph_analysis() {
|
||||
let graph = knowledge! {crate
|
||||
CaptureMethod(Automatic) ->> PaymentMethod(Card);
|
||||
};
|
||||
|
||||
let program_str = r#"
|
||||
default: ["stripe"]
|
||||
|
||||
rule_1: ["adyen"]
|
||||
{
|
||||
amount > 500 {
|
||||
payment_method = pay_later
|
||||
}
|
||||
|
||||
amount < 500 {
|
||||
payment_method /= wallet & payment_method /= pay_later
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let (_, program) = ast::parser::program::<DummyOutput>(program_str).expect("Graph");
|
||||
let analysis_result = analyze(program, Some(&graph));
|
||||
|
||||
let error_type = match analysis_result {
|
||||
Err(types::AnalysisError { error_type, .. }) => error_type,
|
||||
_ => panic!("Error_type not found"),
|
||||
};
|
||||
|
||||
let a_err = match error_type {
|
||||
types::AnalysisErrorType::GraphAnalysis(trace, memo) => (trace, memo),
|
||||
_ => panic!("Graph Analysis not found"),
|
||||
};
|
||||
|
||||
let (trace, metadata) = match a_err.0 {
|
||||
graph::AnalysisError::NegationTrace { trace, metadata } => (trace, metadata),
|
||||
_ => panic!("Negation Trace not found"),
|
||||
};
|
||||
|
||||
let predecessor = match Weak::upgrade(&trace)
|
||||
.expect("Expected Arc not found")
|
||||
.deref()
|
||||
.clone()
|
||||
{
|
||||
graph::AnalysisTrace::Value { predecessors, .. } => {
|
||||
let _value = graph::NodeValue::Value(dir::DirValue::PaymentMethod(
|
||||
dir::enums::PaymentMethod::Card,
|
||||
));
|
||||
let _relation = graph::Relation::Positive;
|
||||
predecessors
|
||||
}
|
||||
_ => panic!("Expected Negation Trace for payment method = card"),
|
||||
};
|
||||
|
||||
let pred = match predecessor {
|
||||
Some(graph::ValueTracePredecessor::Mandatory(predecessor)) => predecessor,
|
||||
_ => panic!("No predecessor found"),
|
||||
};
|
||||
assert_eq!(
|
||||
metadata.len(),
|
||||
2,
|
||||
"Expected two metadats for wallet and pay_later"
|
||||
);
|
||||
assert!(matches!(
|
||||
*Weak::upgrade(&pred)
|
||||
.expect("Expected Arc not found")
|
||||
.deref(),
|
||||
graph::AnalysisTrace::Value {
|
||||
value: graph::NodeValue::Value(dir::DirValue::CaptureMethod(
|
||||
dir::enums::CaptureMethod::Automatic
|
||||
)),
|
||||
relation: graph::Relation::Positive,
|
||||
info: None,
|
||||
metadata: None,
|
||||
predecessors: None,
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
1478
crates/euclid/src/dssa/graph.rs
Normal file
1478
crates/euclid/src/dssa/graph.rs
Normal file
File diff suppressed because it is too large
Load Diff
714
crates/euclid/src/dssa/state_machine.rs
Normal file
714
crates/euclid/src/dssa/state_machine.rs
Normal file
@ -0,0 +1,714 @@
|
||||
use super::types::EuclidAnalysable;
|
||||
use crate::{dssa::types, frontend::dir, types::Metadata};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, thiserror::Error)]
|
||||
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
|
||||
pub enum StateMachineError {
|
||||
#[error("Index out of bounds: {0}")]
|
||||
IndexOutOfBounds(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ComparisonStateMachine<'a> {
|
||||
values: &'a [dir::DirValue],
|
||||
logic: &'a dir::DirComparisonLogic,
|
||||
metadata: &'a Metadata,
|
||||
count: usize,
|
||||
ctx_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> ComparisonStateMachine<'a> {
|
||||
#[inline]
|
||||
fn is_finished(&self) -> bool {
|
||||
self.count + 1 >= self.values.len()
|
||||
|| matches!(self.logic, dir::DirComparisonLogic::NegativeConjunction)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn advance(&mut self) {
|
||||
if let dir::DirComparisonLogic::PositiveDisjunction = self.logic {
|
||||
self.count = (self.count + 1) % self.values.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset(&mut self) {
|
||||
self.count = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn put(&self, context: &mut types::ConjunctiveContext<'a>) -> Result<(), StateMachineError> {
|
||||
if let dir::DirComparisonLogic::PositiveDisjunction = self.logic {
|
||||
*context
|
||||
.get_mut(self.ctx_idx)
|
||||
.ok_or(StateMachineError::IndexOutOfBounds(
|
||||
"in ComparisonStateMachine while indexing into context",
|
||||
))? = types::ContextValue::assertion(
|
||||
self.values
|
||||
.get(self.count)
|
||||
.ok_or(StateMachineError::IndexOutOfBounds(
|
||||
"in ComparisonStateMachine while indexing into values",
|
||||
))?,
|
||||
self.metadata,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push(&self, context: &mut types::ConjunctiveContext<'a>) -> Result<(), StateMachineError> {
|
||||
match self.logic {
|
||||
dir::DirComparisonLogic::PositiveDisjunction => {
|
||||
context.push(types::ContextValue::assertion(
|
||||
self.values
|
||||
.get(self.count)
|
||||
.ok_or(StateMachineError::IndexOutOfBounds(
|
||||
"in ComparisonStateMachine while pushing",
|
||||
))?,
|
||||
self.metadata,
|
||||
));
|
||||
}
|
||||
|
||||
dir::DirComparisonLogic::NegativeConjunction => {
|
||||
context.push(types::ContextValue::negation(self.values, self.metadata));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConditionStateMachine<'a> {
|
||||
state_machines: Vec<ComparisonStateMachine<'a>>,
|
||||
start_ctx_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> ConditionStateMachine<'a> {
|
||||
fn new(condition: &'a [dir::DirComparison], start_idx: usize) -> Self {
|
||||
let mut machines = Vec::<ComparisonStateMachine<'a>>::with_capacity(condition.len());
|
||||
|
||||
let mut machine_idx = start_idx;
|
||||
for cond in condition {
|
||||
let machine = ComparisonStateMachine {
|
||||
values: &cond.values,
|
||||
logic: &cond.logic,
|
||||
metadata: &cond.metadata,
|
||||
count: 0,
|
||||
ctx_idx: machine_idx,
|
||||
};
|
||||
machines.push(machine);
|
||||
machine_idx += 1;
|
||||
}
|
||||
|
||||
Self {
|
||||
state_machines: machines,
|
||||
start_ctx_idx: start_idx,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&self, context: &mut types::ConjunctiveContext<'a>) -> Result<(), StateMachineError> {
|
||||
for machine in &self.state_machines {
|
||||
machine.push(context)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn destroy(&self, context: &mut types::ConjunctiveContext<'a>) {
|
||||
context.truncate(self.start_ctx_idx);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_finished(&self) -> bool {
|
||||
!self
|
||||
.state_machines
|
||||
.iter()
|
||||
.any(|machine| !machine.is_finished())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_next_ctx_idx(&self) -> usize {
|
||||
self.start_ctx_idx + self.state_machines.len()
|
||||
}
|
||||
|
||||
fn advance(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
for machine in self.state_machines.iter_mut().rev() {
|
||||
if machine.is_finished() {
|
||||
machine.reset();
|
||||
machine.put(context)?;
|
||||
} else {
|
||||
machine.advance();
|
||||
machine.put(context)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct IfStmtStateMachine<'a> {
|
||||
condition_machine: ConditionStateMachine<'a>,
|
||||
nested: Vec<&'a dir::DirIfStatement>,
|
||||
nested_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> IfStmtStateMachine<'a> {
|
||||
fn new(stmt: &'a dir::DirIfStatement, ctx_start_idx: usize) -> Self {
|
||||
let condition_machine = ConditionStateMachine::new(&stmt.condition, ctx_start_idx);
|
||||
let nested: Vec<&'a dir::DirIfStatement> = match &stmt.nested {
|
||||
None => Vec::new(),
|
||||
Some(nested_stmts) => nested_stmts.iter().collect(),
|
||||
};
|
||||
|
||||
Self {
|
||||
condition_machine,
|
||||
nested,
|
||||
nested_idx: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
&self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<Option<Self>, StateMachineError> {
|
||||
self.condition_machine.init(context)?;
|
||||
Ok(self
|
||||
.nested
|
||||
.first()
|
||||
.map(|nested| Self::new(nested, self.condition_machine.get_next_ctx_idx())))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_finished(&self) -> bool {
|
||||
self.nested_idx + 1 >= self.nested.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_condition_machine_finished(&self) -> bool {
|
||||
self.condition_machine.is_finished()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn destroy(&self, context: &mut types::ConjunctiveContext<'a>) {
|
||||
self.condition_machine.destroy(context);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn advance_condition_machine(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
self.condition_machine.advance(context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Result<Option<Self>, StateMachineError> {
|
||||
if self.nested.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.nested_idx = (self.nested_idx + 1) % self.nested.len();
|
||||
Ok(Some(Self::new(
|
||||
self.nested
|
||||
.get(self.nested_idx)
|
||||
.ok_or(StateMachineError::IndexOutOfBounds(
|
||||
"in IfStmtStateMachine while advancing",
|
||||
))?,
|
||||
self.condition_machine.get_next_ctx_idx(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RuleStateMachine<'a> {
|
||||
connector_selection_data: &'a [(dir::DirValue, Metadata)],
|
||||
connectors_added: bool,
|
||||
if_stmt_machines: Vec<IfStmtStateMachine<'a>>,
|
||||
running_stack: Vec<IfStmtStateMachine<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> RuleStateMachine<'a> {
|
||||
fn new<O>(
|
||||
rule: &'a dir::DirRule<O>,
|
||||
connector_selection_data: &'a [(dir::DirValue, Metadata)],
|
||||
) -> Self {
|
||||
let mut if_stmt_machines: Vec<IfStmtStateMachine<'a>> =
|
||||
Vec::with_capacity(rule.statements.len());
|
||||
|
||||
for stmt in rule.statements.iter().rev() {
|
||||
if_stmt_machines.push(IfStmtStateMachine::new(
|
||||
stmt,
|
||||
connector_selection_data.len(),
|
||||
));
|
||||
}
|
||||
|
||||
Self {
|
||||
connector_selection_data,
|
||||
connectors_added: false,
|
||||
if_stmt_machines,
|
||||
running_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_finished(&self) -> bool {
|
||||
self.if_stmt_machines.is_empty() && self.running_stack.is_empty()
|
||||
}
|
||||
|
||||
fn init_next(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
if self.if_stmt_machines.is_empty() || !self.running_stack.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.connectors_added {
|
||||
for (dir_val, metadata) in self.connector_selection_data {
|
||||
context.push(types::ContextValue::assertion(dir_val, metadata));
|
||||
}
|
||||
self.connectors_added = true;
|
||||
}
|
||||
|
||||
context.truncate(self.connector_selection_data.len());
|
||||
|
||||
if let Some(mut next_running) = self.if_stmt_machines.pop() {
|
||||
while let Some(nested_running) = next_running.init(context)? {
|
||||
self.running_stack.push(next_running);
|
||||
next_running = nested_running;
|
||||
}
|
||||
|
||||
self.running_stack.push(next_running);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn advance(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
let mut condition_machines_finished = true;
|
||||
|
||||
for stmt_machine in self.running_stack.iter_mut().rev() {
|
||||
if !stmt_machine.is_condition_machine_finished() {
|
||||
condition_machines_finished = false;
|
||||
stmt_machine.advance_condition_machine(context)?;
|
||||
break;
|
||||
} else {
|
||||
stmt_machine.advance_condition_machine(context)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !condition_machines_finished {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut maybe_next_running: Option<IfStmtStateMachine<'a>> = None;
|
||||
|
||||
while let Some(last) = self.running_stack.last_mut() {
|
||||
if !last.is_finished() {
|
||||
maybe_next_running = last.advance()?;
|
||||
break;
|
||||
} else {
|
||||
last.destroy(context);
|
||||
self.running_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut next_running) = maybe_next_running {
|
||||
while let Some(nested_running) = next_running.init(context)? {
|
||||
self.running_stack.push(next_running);
|
||||
next_running = nested_running;
|
||||
}
|
||||
|
||||
self.running_stack.push(next_running);
|
||||
} else {
|
||||
self.init_next(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RuleContextManager<'a> {
|
||||
context: types::ConjunctiveContext<'a>,
|
||||
machine: RuleStateMachine<'a>,
|
||||
init: bool,
|
||||
}
|
||||
|
||||
impl<'a> RuleContextManager<'a> {
|
||||
pub fn new<O>(
|
||||
rule: &'a dir::DirRule<O>,
|
||||
connector_selection_data: &'a [(dir::DirValue, Metadata)],
|
||||
) -> Self {
|
||||
Self {
|
||||
context: Vec::new(),
|
||||
machine: RuleStateMachine::new(rule, connector_selection_data),
|
||||
init: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self) -> Result<Option<&types::ConjunctiveContext<'a>>, StateMachineError> {
|
||||
if !self.init {
|
||||
self.init = true;
|
||||
self.machine.init_next(&mut self.context)?;
|
||||
Ok(Some(&self.context))
|
||||
} else if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.machine.advance(&mut self.context)?;
|
||||
|
||||
if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(&self.context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_mut(
|
||||
&mut self,
|
||||
) -> Result<Option<&mut types::ConjunctiveContext<'a>>, StateMachineError> {
|
||||
if !self.init {
|
||||
self.init = true;
|
||||
self.machine.init_next(&mut self.context)?;
|
||||
Ok(Some(&mut self.context))
|
||||
} else if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.machine.advance(&mut self.context)?;
|
||||
|
||||
if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(&mut self.context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProgramStateMachine<'a> {
|
||||
rule_machines: Vec<RuleStateMachine<'a>>,
|
||||
current_rule_machine: Option<RuleStateMachine<'a>>,
|
||||
is_init: bool,
|
||||
}
|
||||
|
||||
impl<'a> ProgramStateMachine<'a> {
|
||||
pub fn new<O>(
|
||||
program: &'a dir::DirProgram<O>,
|
||||
connector_selection_data: &'a [Vec<(dir::DirValue, Metadata)>],
|
||||
) -> Self {
|
||||
let mut rule_machines: Vec<RuleStateMachine<'a>> = program
|
||||
.rules
|
||||
.iter()
|
||||
.zip(connector_selection_data.iter())
|
||||
.rev()
|
||||
.map(|(rule, connector_selection_data)| {
|
||||
RuleStateMachine::new(rule, connector_selection_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
current_rule_machine: rule_machines.pop(),
|
||||
rule_machines,
|
||||
is_init: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.current_rule_machine
|
||||
.as_ref()
|
||||
.map_or(true, |rsm| rsm.is_finished())
|
||||
&& self.rule_machines.is_empty()
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
if !self.is_init {
|
||||
if let Some(rsm) = self.current_rule_machine.as_mut() {
|
||||
rsm.init_next(context)?;
|
||||
}
|
||||
self.is_init = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn advance(
|
||||
&mut self,
|
||||
context: &mut types::ConjunctiveContext<'a>,
|
||||
) -> Result<(), StateMachineError> {
|
||||
if self
|
||||
.current_rule_machine
|
||||
.as_ref()
|
||||
.map_or(true, |rsm| rsm.is_finished())
|
||||
{
|
||||
self.current_rule_machine = self.rule_machines.pop();
|
||||
context.clear();
|
||||
if let Some(rsm) = self.current_rule_machine.as_mut() {
|
||||
rsm.init_next(context)?;
|
||||
}
|
||||
} else if let Some(rsm) = self.current_rule_machine.as_mut() {
|
||||
rsm.advance(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnalysisContextManager<'a> {
|
||||
context: types::ConjunctiveContext<'a>,
|
||||
machine: ProgramStateMachine<'a>,
|
||||
init: bool,
|
||||
}
|
||||
|
||||
impl<'a> AnalysisContextManager<'a> {
|
||||
pub fn new<O>(
|
||||
program: &'a dir::DirProgram<O>,
|
||||
connector_selection_data: &'a [Vec<(dir::DirValue, Metadata)>],
|
||||
) -> Self {
|
||||
let machine = ProgramStateMachine::new(program, connector_selection_data);
|
||||
let context: types::ConjunctiveContext<'a> = Vec::new();
|
||||
|
||||
Self {
|
||||
context,
|
||||
machine,
|
||||
init: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self) -> Result<Option<&types::ConjunctiveContext<'a>>, StateMachineError> {
|
||||
if !self.init {
|
||||
self.init = true;
|
||||
self.machine.init(&mut self.context)?;
|
||||
Ok(Some(&self.context))
|
||||
} else if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
self.machine.advance(&mut self.context)?;
|
||||
|
||||
if self.machine.is_finished() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(&self.context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_connector_selection_data<O: EuclidAnalysable>(
|
||||
program: &dir::DirProgram<O>,
|
||||
) -> Vec<Vec<(dir::DirValue, Metadata)>> {
|
||||
program
|
||||
.rules
|
||||
.iter()
|
||||
.map(|rule| {
|
||||
rule.connector_selection
|
||||
.get_dir_value_for_analysis(rule.name.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "ast_parser"))]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use super::*;
|
||||
use crate::{dirval, frontend::ast, types::DummyOutput};
|
||||
|
||||
#[test]
|
||||
fn test_correct_contexts() {
|
||||
let program_str = r#"
|
||||
default: ["stripe", "adyen"]
|
||||
|
||||
stripe_first: ["stripe", "adyen"]
|
||||
{
|
||||
payment_method = wallet {
|
||||
payment_method = (card, bank_redirect) {
|
||||
currency = USD
|
||||
currency = GBP
|
||||
}
|
||||
|
||||
payment_method = pay_later {
|
||||
capture_method = automatic
|
||||
capture_method = manual
|
||||
}
|
||||
}
|
||||
|
||||
payment_method = card {
|
||||
payment_method = (card, bank_redirect) & capture_method = (automatic, manual) {
|
||||
currency = (USD, GBP)
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let (_, program) = ast::parser::program::<DummyOutput>(program_str).expect("Program");
|
||||
let lowered = ast::lowering::lower_program(program).expect("Lowering");
|
||||
|
||||
let selection_data = make_connector_selection_data(&lowered);
|
||||
let mut state_machine = ProgramStateMachine::new(&lowered, &selection_data);
|
||||
let mut ctx: types::ConjunctiveContext<'_> = Vec::new();
|
||||
state_machine.init(&mut ctx).expect("State machine init");
|
||||
|
||||
let expected_contexts: Vec<Vec<dir::DirValue>> = vec![
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = PayLater),
|
||||
dirval!(CaptureMethod = Automatic),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Wallet),
|
||||
dirval!(PaymentMethod = PayLater),
|
||||
dirval!(CaptureMethod = Manual),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(CaptureMethod = Automatic),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(CaptureMethod = Automatic),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(CaptureMethod = Manual),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(CaptureMethod = Manual),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(CaptureMethod = Automatic),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(CaptureMethod = Automatic),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(CaptureMethod = Manual),
|
||||
dirval!(PaymentCurrency = USD),
|
||||
],
|
||||
vec![
|
||||
dirval!("MetadataKey" = "stripe"),
|
||||
dirval!("MetadataKey" = "adyen"),
|
||||
dirval!(PaymentMethod = Card),
|
||||
dirval!(PaymentMethod = BankRedirect),
|
||||
dirval!(CaptureMethod = Manual),
|
||||
dirval!(PaymentCurrency = GBP),
|
||||
],
|
||||
];
|
||||
|
||||
let mut expected_idx = 0usize;
|
||||
while !state_machine.is_finished() {
|
||||
let values = ctx
|
||||
.iter()
|
||||
.flat_map(|c| match c.value {
|
||||
types::CtxValueKind::Assertion(val) => vec![val],
|
||||
types::CtxValueKind::Negation(vals) => vals.iter().collect(),
|
||||
})
|
||||
.collect::<Vec<&dir::DirValue>>();
|
||||
assert_eq!(
|
||||
values,
|
||||
expected_contexts[expected_idx]
|
||||
.iter()
|
||||
.collect::<Vec<&dir::DirValue>>()
|
||||
);
|
||||
expected_idx += 1;
|
||||
state_machine
|
||||
.advance(&mut ctx)
|
||||
.expect("State Machine advance");
|
||||
}
|
||||
|
||||
assert_eq!(expected_idx, 14);
|
||||
|
||||
let mut ctx_manager = AnalysisContextManager::new(&lowered, &selection_data);
|
||||
expected_idx = 0;
|
||||
while let Some(ctx) = ctx_manager.advance().expect("Context Manager Context") {
|
||||
let values = ctx
|
||||
.iter()
|
||||
.flat_map(|c| match c.value {
|
||||
types::CtxValueKind::Assertion(val) => vec![val],
|
||||
types::CtxValueKind::Negation(vals) => vals.iter().collect(),
|
||||
})
|
||||
.collect::<Vec<&dir::DirValue>>();
|
||||
assert_eq!(
|
||||
values,
|
||||
expected_contexts[expected_idx]
|
||||
.iter()
|
||||
.collect::<Vec<&dir::DirValue>>()
|
||||
);
|
||||
expected_idx += 1;
|
||||
}
|
||||
|
||||
assert_eq!(expected_idx, 14);
|
||||
}
|
||||
}
|
||||
29
crates/euclid/src/dssa/truth.rs
Normal file
29
crates/euclid/src/dssa/truth.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use euclid_macros::knowledge;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::dssa::graph;
|
||||
|
||||
pub static ANALYSIS_GRAPH: Lazy<graph::KnowledgeGraph<'_>> = Lazy::new(|| {
|
||||
knowledge! {crate
|
||||
// Payment Method should be `Card` for a CardType to be present
|
||||
PaymentMethod(Card) ->> CardType(any);
|
||||
|
||||
// Payment Method should be `PayLater` for a PayLaterType to be present
|
||||
PaymentMethod(PayLater) ->> PayLaterType(any);
|
||||
|
||||
// Payment Method should be `Wallet` for a WalletType to be present
|
||||
PaymentMethod(Wallet) ->> WalletType(any);
|
||||
|
||||
// Payment Method should be `BankRedirect` for a BankRedirectType to
|
||||
// be present
|
||||
PaymentMethod(BankRedirect) ->> BankRedirectType(any);
|
||||
|
||||
// Payment Method should be `BankTransfer` for a BankTransferType to
|
||||
// be present
|
||||
PaymentMethod(BankTransfer) ->> BankTransferType(any);
|
||||
|
||||
// Payment Method should be `GiftCard` for a GiftCardType to
|
||||
// be present
|
||||
PaymentMethod(GiftCard) ->> GiftCardType(any);
|
||||
}
|
||||
});
|
||||
158
crates/euclid/src/dssa/types.rs
Normal file
158
crates/euclid/src/dssa/types.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
dssa::{self, graph},
|
||||
frontend::{ast, dir},
|
||||
types::{DataType, EuclidValue, Metadata},
|
||||
};
|
||||
|
||||
pub trait EuclidAnalysable: Sized {
|
||||
fn get_dir_value_for_analysis(&self, rule_name: String) -> Vec<(dir::DirValue, Metadata)>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CtxValueKind<'a> {
|
||||
Assertion(&'a dir::DirValue),
|
||||
Negation(&'a [dir::DirValue]),
|
||||
}
|
||||
|
||||
impl<'a> CtxValueKind<'a> {
|
||||
pub fn get_assertion(&self) -> Option<&dir::DirValue> {
|
||||
if let Self::Assertion(val) = self {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_negation(&self) -> Option<&[dir::DirValue]> {
|
||||
if let Self::Negation(vals) = self {
|
||||
Some(vals)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> Option<dir::DirKey> {
|
||||
match self {
|
||||
Self::Assertion(val) => Some(val.get_key()),
|
||||
Self::Negation(vals) => vals.first().map(|v| (*v).get_key()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextValue<'a> {
|
||||
pub value: CtxValueKind<'a>,
|
||||
pub metadata: &'a Metadata,
|
||||
}
|
||||
|
||||
impl<'a> ContextValue<'a> {
|
||||
#[inline]
|
||||
pub fn assertion(value: &'a dir::DirValue, metadata: &'a Metadata) -> Self {
|
||||
Self {
|
||||
value: CtxValueKind::Assertion(value),
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn negation(values: &'a [dir::DirValue], metadata: &'a Metadata) -> Self {
|
||||
Self {
|
||||
value: CtxValueKind::Negation(values),
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ConjunctiveContext<'a> = Vec<ContextValue<'a>>;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub enum AnalyzeResult {
|
||||
AllOk,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, thiserror::Error)]
|
||||
pub struct AnalysisError {
|
||||
#[serde(flatten)]
|
||||
pub error_type: AnalysisErrorType,
|
||||
pub metadata: Metadata,
|
||||
}
|
||||
impl fmt::Display for AnalysisError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.error_type.fmt(f)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ValueData {
|
||||
pub value: dir::DirValue,
|
||||
pub metadata: Metadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, thiserror::Error)]
|
||||
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
|
||||
pub enum AnalysisErrorType {
|
||||
#[error("Invalid program key given: '{0}'")]
|
||||
InvalidKey(String),
|
||||
#[error("Invalid variant '{got}' received for key '{key}'")]
|
||||
InvalidVariant {
|
||||
key: String,
|
||||
expected: Vec<String>,
|
||||
got: String,
|
||||
},
|
||||
#[error(
|
||||
"Invalid data type for value '{}' (expected {expected}, got {got})",
|
||||
key
|
||||
)]
|
||||
InvalidType {
|
||||
key: String,
|
||||
expected: DataType,
|
||||
got: DataType,
|
||||
},
|
||||
#[error("Invalid comparison '{operator:?}' for value type {value_type}")]
|
||||
InvalidComparison {
|
||||
operator: ast::ComparisonType,
|
||||
value_type: DataType,
|
||||
},
|
||||
#[error("Invalid value received for length as '{value}: {:?}'", message)]
|
||||
InvalidValue {
|
||||
key: dir::DirKeyKind,
|
||||
value: String,
|
||||
message: Option<String>,
|
||||
},
|
||||
#[error("Conflicting assertions received for key '{}'", .key.kind)]
|
||||
ConflictingAssertions {
|
||||
key: dir::DirKey,
|
||||
values: Vec<ValueData>,
|
||||
},
|
||||
|
||||
#[error("Key '{}' exhaustively negated", .key.kind)]
|
||||
ExhaustiveNegation {
|
||||
key: dir::DirKey,
|
||||
metadata: Vec<Metadata>,
|
||||
},
|
||||
#[error("The condition '{value}' was asserted and negated in the same condition")]
|
||||
NegatedAssertion {
|
||||
value: dir::DirValue,
|
||||
assertion_metadata: Metadata,
|
||||
negation_metadata: Metadata,
|
||||
},
|
||||
#[error("Graph analysis error: {0:#?}")]
|
||||
GraphAnalysis(graph::AnalysisError, graph::Memoization),
|
||||
#[error("State machine error")]
|
||||
StateMachine(dssa::state_machine::StateMachineError),
|
||||
#[error("Unsupported program key '{0}'")]
|
||||
UnsupportedProgramKey(dir::DirKeyKind),
|
||||
#[error("Ran into an unimplemented feature")]
|
||||
NotImplemented,
|
||||
#[error("The payment method type is not supported under the payment method")]
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ValueType {
|
||||
EnumVariants(Vec<EuclidValue>),
|
||||
Number,
|
||||
}
|
||||
1
crates/euclid/src/dssa/utils.rs
Normal file
1
crates/euclid/src/dssa/utils.rs
Normal file
@ -0,0 +1 @@
|
||||
pub struct Unpacker;
|
||||
Reference in New Issue
Block a user