mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(constraint_graph): make the constraint graph framework generic and move it into a separate crate (#3071)
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -45,6 +45,7 @@ crates/router/src/compatibility/ @juspay/hyperswitch-compatibility
|
||||
crates/router/src/core/ @juspay/hyperswitch-core
|
||||
|
||||
crates/api_models/src/routing.rs @juspay/hyperswitch-routing
|
||||
crates/hyperswitch_constraint_graph @juspay/hyperswitch-routing
|
||||
crates/euclid @juspay/hyperswitch-routing
|
||||
crates/euclid_macros @juspay/hyperswitch-routing
|
||||
crates/euclid_wasm @juspay/hyperswitch-routing
|
||||
|
||||
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -2677,6 +2677,15 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.4"
|
||||
@ -2733,10 +2742,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"common_enums",
|
||||
"criterion",
|
||||
"erased-serde",
|
||||
"erased-serde 0.4.4",
|
||||
"euclid_macros",
|
||||
"frunk",
|
||||
"frunk_core",
|
||||
"hyperswitch_constraint_graph",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
@ -2768,6 +2778,7 @@ dependencies = [
|
||||
"currency_conversion",
|
||||
"euclid",
|
||||
"getrandom",
|
||||
"hyperswitch_constraint_graph",
|
||||
"kgraph_utils",
|
||||
"once_cell",
|
||||
"ron-parser",
|
||||
@ -3602,6 +3613,18 @@ dependencies = [
|
||||
"tokio 1.37.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperswitch_constraint_graph"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"erased-serde 0.3.31",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.25.0",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperswitch_domain_models"
|
||||
version = "0.1.0"
|
||||
@ -3911,6 +3934,7 @@ dependencies = [
|
||||
"common_enums",
|
||||
"criterion",
|
||||
"euclid",
|
||||
"hyperswitch_constraint_graph",
|
||||
"masking",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -4067,7 +4091,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes 1.6.0",
|
||||
"diesel",
|
||||
"erased-serde",
|
||||
"erased-serde 0.4.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subtle",
|
||||
@ -5599,7 +5623,7 @@ dependencies = [
|
||||
"digest",
|
||||
"dyn-clone",
|
||||
"encoding_rs",
|
||||
"erased-serde",
|
||||
"erased-serde 0.4.4",
|
||||
"error-stack",
|
||||
"euclid",
|
||||
"events",
|
||||
@ -5608,6 +5632,7 @@ dependencies = [
|
||||
"hex",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.28",
|
||||
"hyperswitch_constraint_graph",
|
||||
"hyperswitch_domain_models",
|
||||
"hyperswitch_interfaces",
|
||||
"image",
|
||||
@ -6698,6 +6723,15 @@ dependencies = [
|
||||
"strum_macros 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
dependencies = [
|
||||
"strum_macros 0.25.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.2"
|
||||
@ -6720,6 +6754,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.2"
|
||||
|
||||
@ -21,6 +21,7 @@ utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order
|
||||
|
||||
# First party dependencies
|
||||
common_enums = { version = "0.1.0", path = "../common_enums" }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" }
|
||||
euclid_macros = { version = "0.1.0", path = "../euclid_macros" }
|
||||
|
||||
[features]
|
||||
|
||||
@ -4,11 +4,15 @@
|
||||
//! 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 hyperswitch_constraint_graph::{ConstraintGraph, Memoization};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::{graph::Memoization, types::EuclidAnalysable};
|
||||
use crate::{
|
||||
dssa::{graph, state_machine, truth, types},
|
||||
dssa::{
|
||||
graph::CgraphExt,
|
||||
state_machine, truth,
|
||||
types::{self, EuclidAnalysable},
|
||||
},
|
||||
frontend::{
|
||||
ast,
|
||||
dir::{self, EuclidDirFilter},
|
||||
@ -203,12 +207,12 @@ fn perform_condition_analyses(
|
||||
|
||||
fn perform_context_analyses(
|
||||
context: &types::ConjunctiveContext<'_>,
|
||||
knowledge_graph: &graph::KnowledgeGraph<'_>,
|
||||
knowledge_graph: &ConstraintGraph<'_, dir::DirValue>,
|
||||
) -> Result<(), types::AnalysisError> {
|
||||
perform_condition_analyses(context)?;
|
||||
let mut memo = Memoization::new();
|
||||
knowledge_graph
|
||||
.perform_context_analysis(context, &mut memo)
|
||||
.perform_context_analysis(context, &mut memo, None)
|
||||
.map_err(|err| types::AnalysisError {
|
||||
error_type: types::AnalysisErrorType::GraphAnalysis(err, memo),
|
||||
metadata: Default::default(),
|
||||
@ -218,7 +222,7 @@ fn perform_context_analyses(
|
||||
|
||||
pub fn analyze<O: EuclidAnalysable + EuclidDirFilter>(
|
||||
program: ast::Program<O>,
|
||||
knowledge_graph: Option<&graph::KnowledgeGraph<'_>>,
|
||||
knowledge_graph: Option<&ConstraintGraph<'_, dir::DirValue>>,
|
||||
) -> Result<vir::ValuedProgram<O>, types::AnalysisError> {
|
||||
let dir_program = ast::lowering::lower_program(program)?;
|
||||
|
||||
@ -241,9 +245,14 @@ mod tests {
|
||||
use std::{ops::Deref, sync::Weak};
|
||||
|
||||
use euclid_macros::knowledge;
|
||||
use hyperswitch_constraint_graph as cgraph;
|
||||
|
||||
use super::*;
|
||||
use crate::{dirval, types::DummyOutput};
|
||||
use crate::{
|
||||
dirval,
|
||||
dssa::graph::{self, euclid_graph_prelude},
|
||||
types::DummyOutput,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_conflicting_assertion_detection() {
|
||||
@ -368,7 +377,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_negation_graph_analysis() {
|
||||
let graph = knowledge! {crate
|
||||
let graph = knowledge! {
|
||||
CaptureMethod(Automatic) ->> PaymentMethod(Card);
|
||||
};
|
||||
|
||||
@ -410,18 +419,18 @@ mod tests {
|
||||
.deref()
|
||||
.clone()
|
||||
{
|
||||
graph::AnalysisTrace::Value { predecessors, .. } => {
|
||||
let _value = graph::NodeValue::Value(dir::DirValue::PaymentMethod(
|
||||
cgraph::AnalysisTrace::Value { predecessors, .. } => {
|
||||
let _value = cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(
|
||||
dir::enums::PaymentMethod::Card,
|
||||
));
|
||||
let _relation = graph::Relation::Positive;
|
||||
let _relation = cgraph::Relation::Positive;
|
||||
predecessors
|
||||
}
|
||||
_ => panic!("Expected Negation Trace for payment method = card"),
|
||||
};
|
||||
|
||||
let pred = match predecessor {
|
||||
Some(graph::ValueTracePredecessor::Mandatory(predecessor)) => predecessor,
|
||||
Some(cgraph::error::ValueTracePredecessor::Mandatory(predecessor)) => predecessor,
|
||||
_ => panic!("No predecessor found"),
|
||||
};
|
||||
assert_eq!(
|
||||
@ -433,11 +442,11 @@ mod tests {
|
||||
*Weak::upgrade(&pred)
|
||||
.expect("Expected Arc not found")
|
||||
.deref(),
|
||||
graph::AnalysisTrace::Value {
|
||||
value: graph::NodeValue::Value(dir::DirValue::CaptureMethod(
|
||||
cgraph::AnalysisTrace::Value {
|
||||
value: cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
|
||||
dir::enums::CaptureMethod::Automatic
|
||||
)),
|
||||
relation: graph::Relation::Positive,
|
||||
relation: cgraph::Relation::Positive,
|
||||
info: None,
|
||||
metadata: None,
|
||||
predecessors: None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,30 @@
|
||||
use euclid_macros::knowledge;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::dssa::graph;
|
||||
use crate::{dssa::graph::euclid_graph_prelude, frontend::dir};
|
||||
|
||||
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);
|
||||
pub static ANALYSIS_GRAPH: Lazy<hyperswitch_constraint_graph::ConstraintGraph<'_, dir::DirValue>> =
|
||||
Lazy::new(|| {
|
||||
knowledge! {
|
||||
// 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 `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 `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 `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 `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);
|
||||
}
|
||||
});
|
||||
// Payment Method should be `GiftCard` for a GiftCardType to
|
||||
// be present
|
||||
PaymentMethod(GiftCard) ->> GiftCardType(any);
|
||||
}
|
||||
});
|
||||
|
||||
@ -140,7 +140,10 @@ pub enum AnalysisErrorType {
|
||||
negation_metadata: Metadata,
|
||||
},
|
||||
#[error("Graph analysis error: {0:#?}")]
|
||||
GraphAnalysis(graph::AnalysisError, graph::Memoization),
|
||||
GraphAnalysis(
|
||||
graph::AnalysisError<dir::DirValue>,
|
||||
hyperswitch_constraint_graph::Memoization<dir::DirValue>,
|
||||
),
|
||||
#[error("State machine error")]
|
||||
StateMachine(dssa::state_machine::StateMachineError),
|
||||
#[error("Unsupported program key '{0}'")]
|
||||
|
||||
@ -4,4 +4,3 @@ pub mod dssa;
|
||||
pub mod enums;
|
||||
pub mod frontend;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub mod dense_map;
|
||||
|
||||
pub use dense_map::{DenseMap, EntityId};
|
||||
@ -329,19 +329,17 @@ impl ToString for Scope {
|
||||
#[derive(Clone)]
|
||||
struct Program {
|
||||
rules: Vec<Rc<Rule>>,
|
||||
scope: Scope,
|
||||
}
|
||||
|
||||
impl Parse for Program {
|
||||
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||
let scope: Scope = input.parse()?;
|
||||
let mut rules: Vec<Rc<Rule>> = Vec::new();
|
||||
|
||||
while !input.is_empty() {
|
||||
rules.push(Rc::new(input.parse::<Rule>()?));
|
||||
}
|
||||
|
||||
Ok(Self { rules, scope })
|
||||
Ok(Self { rules })
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,12 +500,12 @@ impl GenContext {
|
||||
let key = format_ident!("{}", &atom.key);
|
||||
let the_value = match &atom.value {
|
||||
ValueType::Any => quote! {
|
||||
NodeValue::Key(DirKey::new(DirKeyKind::#key,None))
|
||||
cgraph::NodeValue::Key(DirKey::new(DirKeyKind::#key,None))
|
||||
},
|
||||
ValueType::EnumVariant(variant) => {
|
||||
let variant = format_ident!("{}", variant);
|
||||
quote! {
|
||||
NodeValue::Value(DirValue::#key(#key::#variant))
|
||||
cgraph::NodeValue::Value(DirValue::#key(#key::#variant))
|
||||
}
|
||||
}
|
||||
ValueType::Number { number, comparison } => {
|
||||
@ -530,7 +528,7 @@ impl GenContext {
|
||||
};
|
||||
|
||||
quote! {
|
||||
NodeValue::Value(DirValue::#key(NumValue {
|
||||
cgraph::NodeValue::Value(DirValue::#key(NumValue {
|
||||
number: #number,
|
||||
refinement: #comp_type,
|
||||
}))
|
||||
@ -539,7 +537,7 @@ impl GenContext {
|
||||
};
|
||||
|
||||
let compiled = quote! {
|
||||
let #identifier = graph.make_value_node(#the_value, None, Vec::new(), None::<()>).expect("NodeId derivation failed");
|
||||
let #identifier = graph.make_value_node(#the_value, None, None::<()>);
|
||||
};
|
||||
|
||||
tokens.extend(compiled);
|
||||
@ -581,7 +579,6 @@ impl GenContext {
|
||||
Vec::from_iter([#(#values_tokens),*]),
|
||||
None,
|
||||
None::<()>,
|
||||
Vec::new(),
|
||||
).expect("Failed to make In aggregator");
|
||||
};
|
||||
|
||||
@ -606,7 +603,7 @@ impl GenContext {
|
||||
for (from_node, relation) in &node_details {
|
||||
let relation = format_ident!("{}", relation.to_string());
|
||||
tokens.extend(quote! {
|
||||
graph.make_edge(#from_node, #rhs_ident, Strength::#strength, Relation::#relation)
|
||||
graph.make_edge(#from_node, #rhs_ident, cgraph::Strength::#strength, cgraph::Relation::#relation, None::<cgraph::DomainId>)
|
||||
.expect("Failed to make edge");
|
||||
});
|
||||
}
|
||||
@ -614,16 +611,18 @@ impl GenContext {
|
||||
let mut all_agg_nodes: Vec<TokenStream> = Vec::with_capacity(node_details.len());
|
||||
for (from_node, relation) in &node_details {
|
||||
let relation = format_ident!("{}", relation.to_string());
|
||||
all_agg_nodes.push(quote! { (#from_node, Relation::#relation, Strength::Strong) });
|
||||
all_agg_nodes.push(
|
||||
quote! { (#from_node, cgraph::Relation::#relation, cgraph::Strength::Strong) },
|
||||
);
|
||||
}
|
||||
|
||||
let strength = format_ident!("{}", rule.strength.to_string());
|
||||
let (agg_node_ident, _) = self.next_node_ident();
|
||||
tokens.extend(quote! {
|
||||
let #agg_node_ident = graph.make_all_aggregator(&[#(#all_agg_nodes),*], None, None::<()>, Vec::new())
|
||||
let #agg_node_ident = graph.make_all_aggregator(&[#(#all_agg_nodes),*], None, None::<()>, None)
|
||||
.expect("Failed to make all aggregator node");
|
||||
|
||||
graph.make_edge(#agg_node_ident, #rhs_ident, Strength::#strength, Relation::Positive)
|
||||
graph.make_edge(#agg_node_ident, #rhs_ident, cgraph::Strength::#strength, cgraph::Relation::Positive, None::<cgraph::DomainId>)
|
||||
.expect("Failed to create all aggregator edge");
|
||||
|
||||
});
|
||||
@ -638,21 +637,10 @@ impl GenContext {
|
||||
self.compile_rule(rule, &mut tokens)?;
|
||||
}
|
||||
|
||||
let scope = match &program.scope {
|
||||
Scope::Crate => quote! { crate },
|
||||
Scope::Extern => quote! { euclid },
|
||||
};
|
||||
|
||||
let compiled = quote! {{
|
||||
use #scope::{
|
||||
dssa::graph::*,
|
||||
types::*,
|
||||
frontend::dir::{*, enums::*},
|
||||
};
|
||||
use euclid_graph_prelude::*;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
let mut graph = KnowledgeGraphBuilder::new();
|
||||
let mut graph = cgraph::ConstraintGraphBuilder::new();
|
||||
|
||||
#tokens
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ payouts = ["api_models/payouts", "euclid/payouts"]
|
||||
|
||||
[dependencies]
|
||||
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" }
|
||||
currency_conversion = { version = "0.1.0", path = "../currency_conversion" }
|
||||
connector_configs = { version = "0.1.0", path = "../connector_configs" }
|
||||
euclid = { version = "0.1.0", path = "../euclid", features = [] }
|
||||
|
||||
@ -20,11 +20,7 @@ use currency_conversion::{
|
||||
};
|
||||
use euclid::{
|
||||
backend::{inputs, interpreter::InterpreterBackend, EuclidBackend},
|
||||
dssa::{
|
||||
self, analyzer,
|
||||
graph::{self, Memoization},
|
||||
state_machine, truth,
|
||||
},
|
||||
dssa::{self, analyzer, graph::CgraphExt, state_machine, truth},
|
||||
frontend::{
|
||||
ast,
|
||||
dir::{self, enums as dir_enums, EuclidDirFilter},
|
||||
@ -38,7 +34,7 @@ use crate::utils::JsResultExt;
|
||||
type JsResult = Result<JsValue, JsValue>;
|
||||
|
||||
struct SeedData<'a> {
|
||||
kgraph: graph::KnowledgeGraph<'a>,
|
||||
cgraph: hyperswitch_constraint_graph::ConstraintGraph<'a, dir::DirValue>,
|
||||
connectors: Vec<ast::ConnectorChoice>,
|
||||
}
|
||||
|
||||
@ -98,11 +94,12 @@ pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult {
|
||||
|
||||
let mca_graph = kgraph_utils::mca::make_mca_graph(mcas).err_to_js()?;
|
||||
let analysis_graph =
|
||||
graph::KnowledgeGraph::combine(&mca_graph, &truth::ANALYSIS_GRAPH).err_to_js()?;
|
||||
hyperswitch_constraint_graph::ConstraintGraph::combine(&mca_graph, &truth::ANALYSIS_GRAPH)
|
||||
.err_to_js()?;
|
||||
|
||||
SEED_DATA
|
||||
.set(SeedData {
|
||||
kgraph: analysis_graph,
|
||||
cgraph: analysis_graph,
|
||||
connectors,
|
||||
})
|
||||
.map_err(|_| "Knowledge Graph has been already seeded".to_string())
|
||||
@ -138,8 +135,12 @@ pub fn get_valid_connectors_for_rule(rule: JsValue) -> JsResult {
|
||||
// Standalone conjunctive context analysis to ensure the context itself is valid before
|
||||
// checking it against merchant's connectors
|
||||
seed_data
|
||||
.kgraph
|
||||
.perform_context_analysis(ctx, &mut Memoization::new())
|
||||
.cgraph
|
||||
.perform_context_analysis(
|
||||
ctx,
|
||||
&mut hyperswitch_constraint_graph::Memoization::new(),
|
||||
None,
|
||||
)
|
||||
.err_to_js()?;
|
||||
|
||||
// Update conjunctive context and run analysis on all of merchant's connectors.
|
||||
@ -150,9 +151,11 @@ pub fn get_valid_connectors_for_rule(rule: JsValue) -> JsResult {
|
||||
|
||||
let ctx_val = dssa::types::ContextValue::assertion(choice, &dummy_meta);
|
||||
ctx.push(ctx_val);
|
||||
let analysis_result = seed_data
|
||||
.kgraph
|
||||
.perform_context_analysis(ctx, &mut Memoization::new());
|
||||
let analysis_result = seed_data.cgraph.perform_context_analysis(
|
||||
ctx,
|
||||
&mut hyperswitch_constraint_graph::Memoization::new(),
|
||||
None,
|
||||
);
|
||||
if analysis_result.is_err() {
|
||||
invalid_connectors.insert(conn.clone());
|
||||
}
|
||||
@ -171,7 +174,7 @@ pub fn get_valid_connectors_for_rule(rule: JsValue) -> JsResult {
|
||||
#[wasm_bindgen(js_name = analyzeProgram)]
|
||||
pub fn analyze_program(js_program: JsValue) -> JsResult {
|
||||
let program: ast::Program<ConnectorSelection> = serde_wasm_bindgen::from_value(js_program)?;
|
||||
analyzer::analyze(program, SEED_DATA.get().map(|sd| &sd.kgraph)).err_to_js()?;
|
||||
analyzer::analyze(program, SEED_DATA.get().map(|sd| &sd.cgraph)).err_to_js()?;
|
||||
Ok(JsValue::NULL)
|
||||
}
|
||||
|
||||
|
||||
16
crates/hyperswitch_constraint_graph/Cargo.toml
Normal file
16
crates/hyperswitch_constraint_graph/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "hyperswitch_constraint_graph"
|
||||
description = "Constraint Graph Framework for modeling Domain-Specific Constraints"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
erased-serde = "0.3.28"
|
||||
rustc-hash = "1.1.0"
|
||||
serde = { version = "1.0.163", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.96"
|
||||
strum = { version = "0.25", features = ["derive"] }
|
||||
thiserror = "1.0.43"
|
||||
283
crates/hyperswitch_constraint_graph/src/builder.rs
Normal file
283
crates/hyperswitch_constraint_graph/src/builder.rs
Normal file
@ -0,0 +1,283 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{
|
||||
dense_map::DenseMap,
|
||||
error::GraphError,
|
||||
graph::ConstraintGraph,
|
||||
types::{
|
||||
DomainId, DomainIdentifier, DomainInfo, Edge, EdgeId, Metadata, Node, NodeId, NodeType,
|
||||
NodeValue, Relation, Strength, ValueNode,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum DomainIdOrIdentifier<'a> {
|
||||
DomainId(DomainId),
|
||||
DomainIdentifier(DomainIdentifier<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for DomainIdOrIdentifier<'a> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self::DomainIdentifier(DomainIdentifier::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomainId> for DomainIdOrIdentifier<'_> {
|
||||
fn from(value: DomainId) -> Self {
|
||||
Self::DomainId(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstraintGraphBuilder<'a, V: ValueNode> {
|
||||
domain: DenseMap<DomainId, DomainInfo<'a>>,
|
||||
nodes: DenseMap<NodeId, Node<V>>,
|
||||
edges: DenseMap<EdgeId, Edge>,
|
||||
domain_identifier_map: FxHashMap<DomainIdentifier<'a>, DomainId>,
|
||||
value_map: FxHashMap<NodeValue<V>, NodeId>,
|
||||
edges_map: FxHashMap<(NodeId, NodeId, Option<DomainId>), EdgeId>,
|
||||
node_info: DenseMap<NodeId, Option<&'static str>>,
|
||||
node_metadata: DenseMap<NodeId, Option<Arc<dyn Metadata>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl<'a, V> ConstraintGraphBuilder<'a, V>
|
||||
where
|
||||
V: ValueNode,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
domain: DenseMap::new(),
|
||||
nodes: DenseMap::new(),
|
||||
edges: DenseMap::new(),
|
||||
domain_identifier_map: FxHashMap::default(),
|
||||
value_map: FxHashMap::default(),
|
||||
edges_map: FxHashMap::default(),
|
||||
node_info: DenseMap::new(),
|
||||
node_metadata: DenseMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> ConstraintGraph<'a, V> {
|
||||
ConstraintGraph {
|
||||
domain: self.domain,
|
||||
domain_identifier_map: self.domain_identifier_map,
|
||||
nodes: self.nodes,
|
||||
edges: self.edges,
|
||||
value_map: self.value_map,
|
||||
node_info: self.node_info,
|
||||
node_metadata: self.node_metadata,
|
||||
}
|
||||
}
|
||||
|
||||
fn retrieve_domain_from_identifier(
|
||||
&self,
|
||||
domain_ident: DomainIdentifier<'_>,
|
||||
) -> Result<DomainId, GraphError<V>> {
|
||||
self.domain_identifier_map
|
||||
.get(&domain_ident)
|
||||
.copied()
|
||||
.ok_or(GraphError::DomainNotFound)
|
||||
}
|
||||
|
||||
pub fn make_domain(
|
||||
&mut self,
|
||||
domain_identifier: &'a str,
|
||||
domain_description: &str,
|
||||
) -> Result<DomainId, GraphError<V>> {
|
||||
let domain_identifier = DomainIdentifier::new(domain_identifier);
|
||||
Ok(self
|
||||
.domain_identifier_map
|
||||
.clone()
|
||||
.get(&domain_identifier)
|
||||
.map_or_else(
|
||||
|| {
|
||||
let domain_id = self.domain.push(DomainInfo {
|
||||
domain_identifier,
|
||||
domain_description: domain_description.to_string(),
|
||||
});
|
||||
self.domain_identifier_map
|
||||
.insert(domain_identifier, domain_id);
|
||||
domain_id
|
||||
},
|
||||
|domain_id| *domain_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn make_value_node<M: Metadata>(
|
||||
&mut self,
|
||||
value: NodeValue<V>,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<M>,
|
||||
) -> NodeId {
|
||||
self.value_map.get(&value).copied().unwrap_or_else(|| {
|
||||
let node_id = self.nodes.push(Node::new(NodeType::Value(value.clone())));
|
||||
let _node_info_id = self.node_info.push(info);
|
||||
|
||||
let _node_metadata_id = self
|
||||
.node_metadata
|
||||
.push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));
|
||||
|
||||
self.value_map.insert(value, node_id);
|
||||
node_id
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_edge<'short, T: Into<DomainIdOrIdentifier<'short>>>(
|
||||
&mut self,
|
||||
pred_id: NodeId,
|
||||
succ_id: NodeId,
|
||||
strength: Strength,
|
||||
relation: Relation,
|
||||
domain: Option<T>,
|
||||
) -> Result<EdgeId, GraphError<V>> {
|
||||
self.ensure_node_exists(pred_id)?;
|
||||
self.ensure_node_exists(succ_id)?;
|
||||
let domain_id = domain
|
||||
.map(|d| match d.into() {
|
||||
DomainIdOrIdentifier::DomainIdentifier(ident) => {
|
||||
self.retrieve_domain_from_identifier(ident)
|
||||
}
|
||||
DomainIdOrIdentifier::DomainId(domain_id) => {
|
||||
self.ensure_domain_exists(domain_id).map(|_| domain_id)
|
||||
}
|
||||
})
|
||||
.transpose()?;
|
||||
self.edges_map
|
||||
.get(&(pred_id, succ_id, domain_id))
|
||||
.copied()
|
||||
.and_then(|edge_id| self.edges.get(edge_id).cloned().map(|edge| (edge_id, edge)))
|
||||
.map_or_else(
|
||||
|| {
|
||||
let edge_id = self.edges.push(Edge {
|
||||
strength,
|
||||
relation,
|
||||
pred: pred_id,
|
||||
succ: succ_id,
|
||||
domain: domain_id,
|
||||
});
|
||||
self.edges_map
|
||||
.insert((pred_id, succ_id, domain_id), edge_id);
|
||||
|
||||
let pred = self
|
||||
.nodes
|
||||
.get_mut(pred_id)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
pred.succs.push(edge_id);
|
||||
|
||||
let succ = self
|
||||
.nodes
|
||||
.get_mut(succ_id)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
succ.preds.push(edge_id);
|
||||
|
||||
Ok(edge_id)
|
||||
},
|
||||
|(edge_id, edge)| {
|
||||
if edge.strength == strength && edge.relation == relation {
|
||||
Ok(edge_id)
|
||||
} else {
|
||||
Err(GraphError::ConflictingEdgeCreated)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_all_aggregator<M: Metadata>(
|
||||
&mut self,
|
||||
nodes: &[(NodeId, Relation, Strength)],
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<M>,
|
||||
domain: Option<&str>,
|
||||
) -> Result<NodeId, GraphError<V>> {
|
||||
nodes
|
||||
.iter()
|
||||
.try_for_each(|(node_id, _, _)| self.ensure_node_exists(*node_id))?;
|
||||
|
||||
let aggregator_id = self.nodes.push(Node::new(NodeType::AllAggregator));
|
||||
let _aggregator_info_id = self.node_info.push(info);
|
||||
|
||||
let _node_metadata_id = self
|
||||
.node_metadata
|
||||
.push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));
|
||||
|
||||
for (node_id, relation, strength) in nodes {
|
||||
self.make_edge(*node_id, aggregator_id, *strength, *relation, domain)?;
|
||||
}
|
||||
|
||||
Ok(aggregator_id)
|
||||
}
|
||||
|
||||
pub fn make_any_aggregator<M: Metadata>(
|
||||
&mut self,
|
||||
nodes: &[(NodeId, Relation, Strength)],
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<M>,
|
||||
domain: Option<&str>,
|
||||
) -> Result<NodeId, GraphError<V>> {
|
||||
nodes
|
||||
.iter()
|
||||
.try_for_each(|(node_id, _, _)| self.ensure_node_exists(*node_id))?;
|
||||
|
||||
let aggregator_id = self.nodes.push(Node::new(NodeType::AnyAggregator));
|
||||
let _aggregator_info_id = self.node_info.push(info);
|
||||
|
||||
let _node_metadata_id = self
|
||||
.node_metadata
|
||||
.push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));
|
||||
|
||||
for (node_id, relation, strength) in nodes {
|
||||
self.make_edge(*node_id, aggregator_id, *strength, *relation, domain)?;
|
||||
}
|
||||
|
||||
Ok(aggregator_id)
|
||||
}
|
||||
|
||||
pub fn make_in_aggregator<M: Metadata>(
|
||||
&mut self,
|
||||
values: Vec<V>,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<M>,
|
||||
) -> Result<NodeId, GraphError<V>> {
|
||||
let key = values
|
||||
.first()
|
||||
.ok_or(GraphError::NoInAggregatorValues)?
|
||||
.get_key();
|
||||
|
||||
for val in &values {
|
||||
if val.get_key() != key {
|
||||
Err(GraphError::MalformedGraph {
|
||||
reason: "Values for 'In' aggregator not of same key".to_string(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let node_id = self
|
||||
.nodes
|
||||
.push(Node::new(NodeType::InAggregator(FxHashSet::from_iter(
|
||||
values,
|
||||
))));
|
||||
let _aggregator_info_id = self.node_info.push(info);
|
||||
|
||||
let _node_metadata_id = self
|
||||
.node_metadata
|
||||
.push(metadata.map(|meta| -> Arc<dyn Metadata> { Arc::new(meta) }));
|
||||
|
||||
Ok(node_id)
|
||||
}
|
||||
|
||||
fn ensure_node_exists(&self, id: NodeId) -> Result<(), GraphError<V>> {
|
||||
if self.nodes.contains_key(id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GraphError::NodeNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_domain_exists(&self, id: DomainId) -> Result<(), GraphError<V>> {
|
||||
if self.domain.contains_key(id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GraphError::DomainNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,24 @@ pub trait EntityId {
|
||||
fn with_id(id: usize) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_entity {
|
||||
($name:ident) => {
|
||||
impl $crate::dense_map::EntityId for $name {
|
||||
#[inline]
|
||||
fn get_id(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_id(id: usize) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use impl_entity;
|
||||
|
||||
pub struct DenseMap<K, V> {
|
||||
data: Vec<V>,
|
||||
_marker: PhantomData<K>,
|
||||
77
crates/hyperswitch_constraint_graph/src/error.rs
Normal file
77
crates/hyperswitch_constraint_graph/src/error.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crate::types::{Metadata, NodeValue, Relation, RelationResolution, ValueNode};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(tag = "type", content = "predecessor", rename_all = "snake_case")]
|
||||
pub enum ValueTracePredecessor<V: ValueNode> {
|
||||
Mandatory(Box<Weak<AnalysisTrace<V>>>),
|
||||
OneOf(Vec<Weak<AnalysisTrace<V>>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(tag = "type", content = "trace", rename_all = "snake_case")]
|
||||
pub enum AnalysisTrace<V: ValueNode> {
|
||||
Value {
|
||||
value: NodeValue<V>,
|
||||
relation: Relation,
|
||||
predecessors: Option<ValueTracePredecessor<V>>,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<Arc<dyn Metadata>>,
|
||||
},
|
||||
|
||||
AllAggregation {
|
||||
unsatisfied: Vec<Weak<AnalysisTrace<V>>>,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<Arc<dyn Metadata>>,
|
||||
},
|
||||
|
||||
AnyAggregation {
|
||||
unsatisfied: Vec<Weak<AnalysisTrace<V>>>,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<Arc<dyn Metadata>>,
|
||||
},
|
||||
|
||||
InAggregation {
|
||||
expected: Vec<V>,
|
||||
found: Option<V>,
|
||||
relation: Relation,
|
||||
info: Option<&'static str>,
|
||||
metadata: Option<Arc<dyn Metadata>>,
|
||||
},
|
||||
Contradiction {
|
||||
relation: RelationResolution,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, thiserror::Error)]
|
||||
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
|
||||
pub enum GraphError<V: ValueNode> {
|
||||
#[error("An edge was not found in the graph")]
|
||||
EdgeNotFound,
|
||||
#[error("Attempted to create a conflicting edge between two nodes")]
|
||||
ConflictingEdgeCreated,
|
||||
#[error("Cycle detected in graph")]
|
||||
CycleDetected,
|
||||
#[error("Domain wasn't found in the Graph")]
|
||||
DomainNotFound,
|
||||
#[error("Malformed Graph: {reason}")]
|
||||
MalformedGraph { reason: String },
|
||||
#[error("A node was not found in the graph")]
|
||||
NodeNotFound,
|
||||
#[error("A value node was not found: {0:#?}")]
|
||||
ValueNodeNotFound(V),
|
||||
#[error("No values provided for an 'in' aggregator node")]
|
||||
NoInAggregatorValues,
|
||||
#[error("Error during analysis: {0:#?}")]
|
||||
AnalysisError(Weak<AnalysisTrace<V>>),
|
||||
}
|
||||
|
||||
impl<V: ValueNode> GraphError<V> {
|
||||
pub fn get_analysis_trace(self) -> Result<Weak<AnalysisTrace<V>>, Self> {
|
||||
match self {
|
||||
Self::AnalysisError(trace) => Ok(trace),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
587
crates/hyperswitch_constraint_graph/src/graph.rs
Normal file
587
crates/hyperswitch_constraint_graph/src/graph.rs
Normal file
@ -0,0 +1,587 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{
|
||||
builder,
|
||||
dense_map::DenseMap,
|
||||
error::{self, AnalysisTrace, GraphError},
|
||||
types::{
|
||||
CheckingContext, CycleCheck, DomainId, DomainIdentifier, DomainInfo, Edge, EdgeId,
|
||||
Memoization, Metadata, Node, NodeId, NodeType, NodeValue, Relation, RelationResolution,
|
||||
Strength, ValueNode,
|
||||
},
|
||||
};
|
||||
|
||||
struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext<Value = V>> {
|
||||
ctx: &'a C,
|
||||
node: &'a Node<V>,
|
||||
node_id: NodeId,
|
||||
relation: Relation,
|
||||
strength: Strength,
|
||||
memo: &'a mut Memoization<V>,
|
||||
cycle_map: &'a mut CycleCheck,
|
||||
domains: Option<&'a [DomainId]>,
|
||||
}
|
||||
|
||||
pub struct ConstraintGraph<'a, V: ValueNode> {
|
||||
pub domain: DenseMap<DomainId, DomainInfo<'a>>,
|
||||
pub domain_identifier_map: FxHashMap<DomainIdentifier<'a>, DomainId>,
|
||||
pub nodes: DenseMap<NodeId, Node<V>>,
|
||||
pub edges: DenseMap<EdgeId, Edge>,
|
||||
pub value_map: FxHashMap<NodeValue<V>, NodeId>,
|
||||
pub node_info: DenseMap<NodeId, Option<&'static str>>,
|
||||
pub node_metadata: DenseMap<NodeId, Option<Arc<dyn Metadata>>>,
|
||||
}
|
||||
|
||||
impl<'a, V> ConstraintGraph<'a, V>
|
||||
where
|
||||
V: ValueNode,
|
||||
{
|
||||
fn get_predecessor_edges_by_domain(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
domains: Option<&[DomainId]>,
|
||||
) -> Result<Vec<&Edge>, GraphError<V>> {
|
||||
let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;
|
||||
let mut final_list = Vec::new();
|
||||
for &pred in &node.preds {
|
||||
let edge = self.edges.get(pred).ok_or(GraphError::EdgeNotFound)?;
|
||||
if let Some((domain_id, domains)) = edge.domain.zip(domains) {
|
||||
if domains.contains(&domain_id) {
|
||||
final_list.push(edge);
|
||||
}
|
||||
} else if edge.domain.is_none() {
|
||||
final_list.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(final_list)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_node<C>(
|
||||
&self,
|
||||
ctx: &C,
|
||||
node_id: NodeId,
|
||||
relation: Relation,
|
||||
strength: Strength,
|
||||
memo: &mut Memoization<V>,
|
||||
cycle_map: &mut CycleCheck,
|
||||
domains: Option<&[&str]>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let domains = domains
|
||||
.map(|domain_idents| {
|
||||
domain_idents
|
||||
.iter()
|
||||
.map(|domain_ident| {
|
||||
self.domain_identifier_map
|
||||
.get(&DomainIdentifier::new(domain_ident))
|
||||
.copied()
|
||||
.ok_or(GraphError::DomainNotFound)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
self.check_node_inner(
|
||||
ctx,
|
||||
node_id,
|
||||
relation,
|
||||
strength,
|
||||
memo,
|
||||
cycle_map,
|
||||
domains.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_node_inner<C>(
|
||||
&self,
|
||||
ctx: &C,
|
||||
node_id: NodeId,
|
||||
relation: Relation,
|
||||
strength: Strength,
|
||||
memo: &mut Memoization<V>,
|
||||
cycle_map: &mut CycleCheck,
|
||||
domains: Option<&[DomainId]>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;
|
||||
|
||||
if let Some(already_memo) = memo.get(&(node_id, relation, strength)) {
|
||||
already_memo
|
||||
.clone()
|
||||
.map_err(|err| GraphError::AnalysisError(Arc::downgrade(&err)))
|
||||
} else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).cloned()
|
||||
{
|
||||
let strength_relation = Strength::get_resolved_strength(initial_strength, strength);
|
||||
let relation_resolve =
|
||||
RelationResolution::get_resolved_relation(initial_relation, relation.into());
|
||||
cycle_map.entry(node_id).and_modify(|value| {
|
||||
value.0 = strength_relation;
|
||||
value.1 = relation_resolve
|
||||
});
|
||||
Ok(())
|
||||
} else {
|
||||
let check_node_context = CheckNodeContext {
|
||||
node,
|
||||
node_id,
|
||||
relation,
|
||||
strength,
|
||||
memo,
|
||||
cycle_map,
|
||||
ctx,
|
||||
domains,
|
||||
};
|
||||
match &node.node_type {
|
||||
NodeType::AllAggregator => self.validate_all_aggregator(check_node_context),
|
||||
|
||||
NodeType::AnyAggregator => self.validate_any_aggregator(check_node_context),
|
||||
|
||||
NodeType::InAggregator(expected) => {
|
||||
self.validate_in_aggregator(check_node_context, expected)
|
||||
}
|
||||
NodeType::Value(val) => self.validate_value_node(check_node_context, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_all_aggregator<C>(
|
||||
&self,
|
||||
vald: CheckNodeContext<'_, V, C>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();
|
||||
|
||||
for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
|
||||
vald.cycle_map
|
||||
.insert(vald.node_id, (vald.strength, vald.relation.into()));
|
||||
if let Err(e) = self.check_node_inner(
|
||||
vald.ctx,
|
||||
edge.pred,
|
||||
edge.relation,
|
||||
edge.strength,
|
||||
vald.memo,
|
||||
vald.cycle_map,
|
||||
vald.domains,
|
||||
) {
|
||||
unsatisfied.push(e.get_analysis_trace()?);
|
||||
}
|
||||
if let Some((_resolved_strength, resolved_relation)) =
|
||||
vald.cycle_map.remove(&vald.node_id)
|
||||
{
|
||||
if resolved_relation == RelationResolution::Contradiction {
|
||||
let err = Arc::new(AnalysisTrace::Contradiction {
|
||||
relation: resolved_relation,
|
||||
});
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !unsatisfied.is_empty() {
|
||||
let err = Arc::new(AnalysisTrace::AllAggregation {
|
||||
unsatisfied,
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
});
|
||||
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))
|
||||
} else {
|
||||
vald.memo
|
||||
.insert((vald.node_id, vald.relation, vald.strength), Ok(()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_any_aggregator<C>(
|
||||
&self,
|
||||
vald: CheckNodeContext<'_, V, C>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();
|
||||
let mut matched_one = false;
|
||||
|
||||
for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
|
||||
vald.cycle_map
|
||||
.insert(vald.node_id, (vald.strength, vald.relation.into()));
|
||||
if let Err(e) = self.check_node_inner(
|
||||
vald.ctx,
|
||||
edge.pred,
|
||||
edge.relation,
|
||||
edge.strength,
|
||||
vald.memo,
|
||||
vald.cycle_map,
|
||||
vald.domains,
|
||||
) {
|
||||
unsatisfied.push(e.get_analysis_trace()?);
|
||||
} else {
|
||||
matched_one = true;
|
||||
}
|
||||
if let Some((_resolved_strength, resolved_relation)) =
|
||||
vald.cycle_map.remove(&vald.node_id)
|
||||
{
|
||||
if resolved_relation == RelationResolution::Contradiction {
|
||||
let err = Arc::new(AnalysisTrace::Contradiction {
|
||||
relation: resolved_relation,
|
||||
});
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
|
||||
return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matched_one || vald.node.preds.is_empty() {
|
||||
vald.memo
|
||||
.insert((vald.node_id, vald.relation, vald.strength), Ok(()));
|
||||
Ok(())
|
||||
} else {
|
||||
let err = Arc::new(AnalysisTrace::AnyAggregation {
|
||||
unsatisfied: unsatisfied.clone(),
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
});
|
||||
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_in_aggregator<C>(
|
||||
&self,
|
||||
vald: CheckNodeContext<'_, V, C>,
|
||||
expected: &FxHashSet<V>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let the_key = expected
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or_else(|| GraphError::MalformedGraph {
|
||||
reason: "An OnlyIn aggregator node must have at least one expected value"
|
||||
.to_string(),
|
||||
})?
|
||||
.get_key();
|
||||
|
||||
let ctx_vals = if let Some(vals) = vald.ctx.get_values_by_key(&the_key) {
|
||||
vals
|
||||
} else {
|
||||
return if let Strength::Weak = vald.strength {
|
||||
vald.memo
|
||||
.insert((vald.node_id, vald.relation, vald.strength), Ok(()));
|
||||
Ok(())
|
||||
} else {
|
||||
let err = Arc::new(AnalysisTrace::InAggregation {
|
||||
expected: expected.iter().cloned().collect(),
|
||||
found: None,
|
||||
relation: vald.relation,
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
});
|
||||
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))
|
||||
};
|
||||
};
|
||||
|
||||
let relation_bool: bool = vald.relation.into();
|
||||
for ctx_value in ctx_vals {
|
||||
if expected.contains(&ctx_value) != relation_bool {
|
||||
let err = Arc::new(AnalysisTrace::InAggregation {
|
||||
expected: expected.iter().cloned().collect(),
|
||||
found: Some(ctx_value.clone()),
|
||||
relation: vald.relation,
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
});
|
||||
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
|
||||
}
|
||||
}
|
||||
|
||||
vald.memo
|
||||
.insert((vald.node_id, vald.relation, vald.strength), Ok(()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_value_node<C>(
|
||||
&self,
|
||||
vald: CheckNodeContext<'_, V, C>,
|
||||
val: &NodeValue<V>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let mut errors = Vec::<Weak<AnalysisTrace<V>>>::new();
|
||||
let mut matched_one = false;
|
||||
|
||||
self.context_analysis(
|
||||
vald.node_id,
|
||||
vald.relation,
|
||||
vald.strength,
|
||||
vald.ctx,
|
||||
val,
|
||||
vald.memo,
|
||||
)?;
|
||||
|
||||
for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
|
||||
vald.cycle_map
|
||||
.insert(vald.node_id, (vald.strength, vald.relation.into()));
|
||||
let result = self.check_node_inner(
|
||||
vald.ctx,
|
||||
edge.pred,
|
||||
edge.relation,
|
||||
edge.strength,
|
||||
vald.memo,
|
||||
vald.cycle_map,
|
||||
vald.domains,
|
||||
);
|
||||
|
||||
if let Some((resolved_strength, resolved_relation)) =
|
||||
vald.cycle_map.remove(&vald.node_id)
|
||||
{
|
||||
if resolved_relation == RelationResolution::Contradiction {
|
||||
let err = Arc::new(AnalysisTrace::Contradiction {
|
||||
relation: resolved_relation,
|
||||
});
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
|
||||
} else if resolved_strength != vald.strength {
|
||||
self.context_analysis(
|
||||
vald.node_id,
|
||||
vald.relation,
|
||||
resolved_strength,
|
||||
vald.ctx,
|
||||
val,
|
||||
vald.memo,
|
||||
)?
|
||||
}
|
||||
}
|
||||
match (edge.strength, result) {
|
||||
(Strength::Strong, Err(trace)) => {
|
||||
let err = Arc::new(AnalysisTrace::Value {
|
||||
value: val.clone(),
|
||||
relation: vald.relation,
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
predecessors: Some(error::ValueTracePredecessor::Mandatory(Box::new(
|
||||
trace.get_analysis_trace()?,
|
||||
))),
|
||||
});
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
|
||||
}
|
||||
|
||||
(Strength::Strong, Ok(_)) => {
|
||||
matched_one = true;
|
||||
}
|
||||
|
||||
(Strength::Normal | Strength::Weak, Err(trace)) => {
|
||||
errors.push(trace.get_analysis_trace()?);
|
||||
}
|
||||
|
||||
(Strength::Normal | Strength::Weak, Ok(_)) => {
|
||||
matched_one = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matched_one || vald.node.preds.is_empty() {
|
||||
vald.memo
|
||||
.insert((vald.node_id, vald.relation, vald.strength), Ok(()));
|
||||
Ok(())
|
||||
} else {
|
||||
let err = Arc::new(AnalysisTrace::Value {
|
||||
value: val.clone(),
|
||||
relation: vald.relation,
|
||||
info: self.node_info.get(vald.node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
|
||||
predecessors: Some(error::ValueTracePredecessor::OneOf(errors.clone())),
|
||||
});
|
||||
|
||||
vald.memo.insert(
|
||||
(vald.node_id, vald.relation, vald.strength),
|
||||
Err(Arc::clone(&err)),
|
||||
);
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))
|
||||
}
|
||||
}
|
||||
|
||||
fn context_analysis<C>(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
relation: Relation,
|
||||
strength: Strength,
|
||||
ctx: &C,
|
||||
val: &NodeValue<V>,
|
||||
memo: &mut Memoization<V>,
|
||||
) -> Result<(), GraphError<V>>
|
||||
where
|
||||
C: CheckingContext<Value = V>,
|
||||
{
|
||||
let in_context = ctx.check_presence(val, strength);
|
||||
let relation_bool: bool = relation.into();
|
||||
if in_context != relation_bool {
|
||||
let err = Arc::new(AnalysisTrace::Value {
|
||||
value: val.clone(),
|
||||
relation,
|
||||
predecessors: None,
|
||||
info: self.node_info.get(node_id).cloned().flatten(),
|
||||
metadata: self.node_metadata.get(node_id).cloned().flatten(),
|
||||
});
|
||||
memo.insert((node_id, relation, strength), Err(Arc::clone(&err)));
|
||||
Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
|
||||
}
|
||||
if !relation_bool {
|
||||
memo.insert((node_id, relation, strength), Ok(()));
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn combine<'b>(g1: &'b Self, g2: &'b Self) -> Result<Self, GraphError<V>> {
|
||||
let mut node_builder = builder::ConstraintGraphBuilder::new();
|
||||
let mut g1_old2new_id = DenseMap::<NodeId, NodeId>::new();
|
||||
let mut g2_old2new_id = DenseMap::<NodeId, NodeId>::new();
|
||||
let mut g1_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();
|
||||
let mut g2_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();
|
||||
|
||||
let add_domain = |node_builder: &mut builder::ConstraintGraphBuilder<'a, V>,
|
||||
domain: DomainInfo<'a>|
|
||||
-> Result<DomainId, GraphError<V>> {
|
||||
node_builder.make_domain(
|
||||
domain.domain_identifier.into_inner(),
|
||||
&domain.domain_description,
|
||||
)
|
||||
};
|
||||
|
||||
let add_node = |node_builder: &mut builder::ConstraintGraphBuilder<'a, V>,
|
||||
node: &Node<V>|
|
||||
-> Result<NodeId, GraphError<V>> {
|
||||
match &node.node_type {
|
||||
NodeType::Value(node_value) => {
|
||||
Ok(node_builder.make_value_node(node_value.clone(), None, None::<()>))
|
||||
}
|
||||
|
||||
NodeType::AllAggregator => {
|
||||
Ok(node_builder.make_all_aggregator(&[], None, None::<()>, None)?)
|
||||
}
|
||||
|
||||
NodeType::AnyAggregator => {
|
||||
Ok(node_builder.make_any_aggregator(&[], None, None::<()>, None)?)
|
||||
}
|
||||
|
||||
NodeType::InAggregator(expected) => Ok(node_builder.make_in_aggregator(
|
||||
expected.iter().cloned().collect(),
|
||||
None,
|
||||
None::<()>,
|
||||
)?),
|
||||
}
|
||||
};
|
||||
|
||||
for (_old_domain_id, domain) in g1.domain.iter() {
|
||||
let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
|
||||
g1_old2new_domain_id.push(new_domain_id);
|
||||
}
|
||||
|
||||
for (_old_domain_id, domain) in g2.domain.iter() {
|
||||
let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
|
||||
g2_old2new_domain_id.push(new_domain_id);
|
||||
}
|
||||
|
||||
for (_old_node_id, node) in g1.nodes.iter() {
|
||||
let new_node_id = add_node(&mut node_builder, node)?;
|
||||
g1_old2new_id.push(new_node_id);
|
||||
}
|
||||
|
||||
for (_old_node_id, node) in g2.nodes.iter() {
|
||||
let new_node_id = add_node(&mut node_builder, node)?;
|
||||
g2_old2new_id.push(new_node_id);
|
||||
}
|
||||
|
||||
for edge in g1.edges.values() {
|
||||
let new_pred_id = g1_old2new_id
|
||||
.get(edge.pred)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
let new_succ_id = g1_old2new_id
|
||||
.get(edge.succ)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
let domain_ident = edge
|
||||
.domain
|
||||
.map(|domain_id| g1.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
|
||||
.transpose()?
|
||||
.map(|domain| domain.domain_identifier);
|
||||
|
||||
node_builder.make_edge(
|
||||
*new_pred_id,
|
||||
*new_succ_id,
|
||||
edge.strength,
|
||||
edge.relation,
|
||||
domain_ident.as_deref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
for edge in g2.edges.values() {
|
||||
let new_pred_id = g2_old2new_id
|
||||
.get(edge.pred)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
let new_succ_id = g2_old2new_id
|
||||
.get(edge.succ)
|
||||
.ok_or(GraphError::NodeNotFound)?;
|
||||
let domain_ident = edge
|
||||
.domain
|
||||
.map(|domain_id| g2.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
|
||||
.transpose()?
|
||||
.map(|domain| domain.domain_identifier);
|
||||
|
||||
node_builder.make_edge(
|
||||
*new_pred_id,
|
||||
*new_succ_id,
|
||||
edge.strength,
|
||||
edge.relation,
|
||||
domain_ident.as_deref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(node_builder.build())
|
||||
}
|
||||
}
|
||||
13
crates/hyperswitch_constraint_graph/src/lib.rs
Normal file
13
crates/hyperswitch_constraint_graph/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub mod builder;
|
||||
mod dense_map;
|
||||
pub mod error;
|
||||
pub mod graph;
|
||||
pub mod types;
|
||||
|
||||
pub use builder::ConstraintGraphBuilder;
|
||||
pub use error::{AnalysisTrace, GraphError};
|
||||
pub use graph::ConstraintGraph;
|
||||
pub use types::{
|
||||
CheckingContext, CycleCheck, DomainId, DomainIdentifier, Edge, EdgeId, KeyNode, Memoization,
|
||||
Node, NodeId, NodeValue, Relation, Strength, ValueNode,
|
||||
};
|
||||
249
crates/hyperswitch_constraint_graph/src/types.rs
Normal file
249
crates/hyperswitch_constraint_graph/src/types.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt, hash,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{dense_map::impl_entity, error::AnalysisTrace};
|
||||
|
||||
pub trait KeyNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + PartialEq + Eq {}
|
||||
|
||||
pub trait ValueNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + PartialEq + Eq {
|
||||
type Key: KeyNode;
|
||||
|
||||
fn get_key(&self) -> Self::Key;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash)]
|
||||
#[serde(transparent)]
|
||||
pub struct NodeId(usize);
|
||||
|
||||
impl_entity!(NodeId);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Node<V: ValueNode> {
|
||||
pub node_type: NodeType<V>,
|
||||
pub preds: Vec<EdgeId>,
|
||||
pub succs: Vec<EdgeId>,
|
||||
}
|
||||
|
||||
impl<V: ValueNode> Node<V> {
|
||||
pub(crate) fn new(node_type: NodeType<V>) -> Self {
|
||||
Self {
|
||||
node_type,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum NodeType<V: ValueNode> {
|
||||
AllAggregator,
|
||||
AnyAggregator,
|
||||
InAggregator(FxHashSet<V>),
|
||||
Value(NodeValue<V>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
|
||||
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
|
||||
pub enum NodeValue<V: ValueNode> {
|
||||
Key(<V as ValueNode>::Key),
|
||||
Value(V),
|
||||
}
|
||||
|
||||
impl<V: ValueNode> From<V> for NodeValue<V> {
|
||||
fn from(value: V) -> Self {
|
||||
Self::Value(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct EdgeId(usize);
|
||||
|
||||
impl_entity!(EdgeId);
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash, strum::Display, PartialOrd, Ord,
|
||||
)]
|
||||
pub enum Strength {
|
||||
Weak,
|
||||
Normal,
|
||||
Strong,
|
||||
}
|
||||
|
||||
impl Strength {
|
||||
pub fn get_resolved_strength(prev_strength: Self, curr_strength: Self) -> Self {
|
||||
std::cmp::max(prev_strength, curr_strength)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Relation {
|
||||
Positive,
|
||||
Negative,
|
||||
}
|
||||
|
||||
impl From<Relation> for bool {
|
||||
fn from(value: Relation) -> Self {
|
||||
matches!(value, Relation::Positive)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, serde::Serialize)]
|
||||
pub enum RelationResolution {
|
||||
Positive,
|
||||
Negative,
|
||||
Contradiction,
|
||||
}
|
||||
|
||||
impl From<Relation> for RelationResolution {
|
||||
fn from(value: Relation) -> Self {
|
||||
match value {
|
||||
Relation::Positive => Self::Positive,
|
||||
Relation::Negative => Self::Negative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationResolution {
|
||||
pub fn get_resolved_relation(prev_relation: Self, curr_relation: Self) -> Self {
|
||||
if prev_relation != curr_relation {
|
||||
Self::Contradiction
|
||||
} else {
|
||||
curr_relation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Edge {
|
||||
pub strength: Strength,
|
||||
pub relation: Relation,
|
||||
pub pred: NodeId,
|
||||
pub succ: NodeId,
|
||||
pub domain: Option<DomainId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DomainId(usize);
|
||||
|
||||
impl_entity!(DomainId);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DomainIdentifier<'a>(&'a str);
|
||||
|
||||
impl<'a> DomainIdentifier<'a> {
|
||||
pub fn new(identifier: &'a str) -> Self {
|
||||
Self(identifier)
|
||||
}
|
||||
|
||||
pub fn into_inner(&self) -> &'a str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for DomainIdentifier<'a> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for DomainIdentifier<'a> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &'a Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DomainInfo<'a> {
|
||||
pub domain_identifier: DomainIdentifier<'a>,
|
||||
pub domain_description: String,
|
||||
}
|
||||
|
||||
pub trait CheckingContext {
|
||||
type Value: ValueNode;
|
||||
|
||||
fn from_node_values<L>(vals: impl IntoIterator<Item = L>) -> Self
|
||||
where
|
||||
L: Into<Self::Value>;
|
||||
|
||||
fn check_presence(&self, value: &NodeValue<Self::Value>, strength: Strength) -> bool;
|
||||
|
||||
fn get_values_by_key(
|
||||
&self,
|
||||
expected: &<Self::Value as ValueNode>::Key,
|
||||
) -> Option<Vec<Self::Value>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct Memoization<V: ValueNode>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
FxHashMap<(NodeId, Relation, Strength), Result<(), Arc<AnalysisTrace<V>>>>,
|
||||
);
|
||||
|
||||
impl<V: ValueNode> Memoization<V> {
|
||||
pub fn new() -> Self {
|
||||
Self(FxHashMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: ValueNode> Default for Memoization<V> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: ValueNode> Deref for Memoization<V> {
|
||||
type Target = FxHashMap<(NodeId, Relation, Strength), Result<(), Arc<AnalysisTrace<V>>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: ValueNode> DerefMut for Memoization<V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CycleCheck(FxHashMap<NodeId, (Strength, RelationResolution)>);
|
||||
impl CycleCheck {
|
||||
pub fn new() -> Self {
|
||||
Self(FxHashMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CycleCheck {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CycleCheck {
|
||||
type Target = FxHashMap<NodeId, (Strength, RelationResolution)>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for CycleCheck {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Metadata: erased_serde::Serialize + Any + Send + Sync + fmt::Debug {}
|
||||
erased_serde::serialize_trait_object!(Metadata);
|
||||
|
||||
impl<M> Metadata for M where M: erased_serde::Serialize + Any + Send + Sync + fmt::Debug {}
|
||||
@ -13,6 +13,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect
|
||||
[dependencies]
|
||||
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
|
||||
common_enums = { version = "0.1.0", path = "../common_enums" }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" }
|
||||
euclid = { version = "0.1.0", path = "../euclid" }
|
||||
masking = { version = "0.1.0", path = "../masking/" }
|
||||
|
||||
|
||||
@ -8,13 +8,17 @@ use api_models::{
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use euclid::{
|
||||
dirval,
|
||||
dssa::graph::{self, Memoization},
|
||||
dssa::graph::{self, CgraphExt},
|
||||
frontend::dir,
|
||||
types::{NumValue, NumValueRefinement},
|
||||
};
|
||||
use hyperswitch_constraint_graph::{CycleCheck, Memoization};
|
||||
use kgraph_utils::{error::KgraphError, transformers::IntoDirValue};
|
||||
|
||||
fn build_test_data<'a>(total_enabled: usize, total_pm_types: usize) -> graph::KnowledgeGraph<'a> {
|
||||
fn build_test_data<'a>(
|
||||
total_enabled: usize,
|
||||
total_pm_types: usize,
|
||||
) -> hyperswitch_constraint_graph::ConstraintGraph<'a, dir::DirValue> {
|
||||
use api_models::{admin::*, payment_methods::*};
|
||||
|
||||
let mut pms_enabled: Vec<PaymentMethodsEnabled> = Vec::new();
|
||||
@ -88,6 +92,8 @@ fn evaluation(c: &mut Criterion) {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -105,6 +111,8 @@ fn evaluation(c: &mut Criterion) {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use euclid::dssa::{graph::GraphError, types::AnalysisErrorType};
|
||||
use euclid::{dssa::types::AnalysisErrorType, frontend::dir};
|
||||
|
||||
#[derive(Debug, thiserror::Error, serde::Serialize)]
|
||||
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
|
||||
@ -6,7 +6,7 @@ pub enum KgraphError {
|
||||
#[error("Invalid connector name encountered: '{0}'")]
|
||||
InvalidConnectorName(String),
|
||||
#[error("There was an error constructing the graph: {0}")]
|
||||
GraphConstructionError(GraphError),
|
||||
GraphConstructionError(hyperswitch_constraint_graph::GraphError<dir::DirValue>),
|
||||
#[error("There was an error constructing the context")]
|
||||
ContextConstructionError(AnalysisErrorType),
|
||||
#[error("there was an unprecedented indexing error")]
|
||||
|
||||
@ -4,42 +4,39 @@ use api_models::{
|
||||
admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes,
|
||||
};
|
||||
use euclid::{
|
||||
dssa::graph::{self, DomainIdentifier},
|
||||
frontend::{ast, dir},
|
||||
types::{NumValue, NumValueRefinement},
|
||||
};
|
||||
use hyperswitch_constraint_graph as cgraph;
|
||||
|
||||
use crate::{error::KgraphError, transformers::IntoDirValue};
|
||||
|
||||
pub const DOMAIN_IDENTIFIER: &str = "payment_methods_enabled_for_merchantconnectoraccount";
|
||||
|
||||
fn compile_request_pm_types(
|
||||
builder: &mut graph::KnowledgeGraphBuilder<'_>,
|
||||
builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>,
|
||||
pm_types: RequestPaymentMethodTypes,
|
||||
pm: api_enums::PaymentMethod,
|
||||
) -> Result<graph::NodeId, KgraphError> {
|
||||
let mut agg_nodes: Vec<(graph::NodeId, graph::Relation, graph::Strength)> = Vec::new();
|
||||
) -> Result<cgraph::NodeId, KgraphError> {
|
||||
let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
|
||||
|
||||
let pmt_info = "PaymentMethodType";
|
||||
let pmt_id = builder
|
||||
.make_value_node(
|
||||
(pm_types.payment_method_type, pm)
|
||||
.into_dir_value()
|
||||
.map(Into::into)?,
|
||||
Some(pmt_info),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let pmt_id = builder.make_value_node(
|
||||
(pm_types.payment_method_type, pm)
|
||||
.into_dir_value()
|
||||
.map(Into::into)?,
|
||||
Some(pmt_info),
|
||||
None::<()>,
|
||||
);
|
||||
agg_nodes.push((
|
||||
pmt_id,
|
||||
graph::Relation::Positive,
|
||||
cgraph::Relation::Positive,
|
||||
match pm_types.payment_method_type {
|
||||
api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => {
|
||||
graph::Strength::Weak
|
||||
cgraph::Strength::Weak
|
||||
}
|
||||
|
||||
_ => graph::Strength::Strong,
|
||||
_ => cgraph::Strength::Strong,
|
||||
},
|
||||
));
|
||||
|
||||
@ -52,13 +49,13 @@ fn compile_request_pm_types(
|
||||
|
||||
let card_network_info = "Card Networks";
|
||||
let card_network_id = builder
|
||||
.make_in_aggregator(dir_vals, Some(card_network_info), None::<()>, Vec::new())
|
||||
.make_in_aggregator(dir_vals, Some(card_network_info), None::<()>)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
agg_nodes.push((
|
||||
card_network_id,
|
||||
graph::Relation::Positive,
|
||||
graph::Strength::Weak,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Weak,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -71,7 +68,7 @@ fn compile_request_pm_types(
|
||||
.map(IntoDirValue::into_dir_value)
|
||||
.collect::<Result<_, _>>()
|
||||
.ok()?,
|
||||
graph::Relation::Positive,
|
||||
cgraph::Relation::Positive,
|
||||
)),
|
||||
|
||||
admin_api::AcceptedCurrencies::DisableOnly(curr) if !curr.is_empty() => Some((
|
||||
@ -79,7 +76,7 @@ fn compile_request_pm_types(
|
||||
.map(IntoDirValue::into_dir_value)
|
||||
.collect::<Result<_, _>>()
|
||||
.ok()?,
|
||||
graph::Relation::Negative,
|
||||
cgraph::Relation::Negative,
|
||||
)),
|
||||
|
||||
_ => None,
|
||||
@ -88,15 +85,10 @@ fn compile_request_pm_types(
|
||||
if let Some((currencies, relation)) = currencies_data {
|
||||
let accepted_currencies_info = "Accepted Currencies";
|
||||
let accepted_currencies_id = builder
|
||||
.make_in_aggregator(
|
||||
currencies,
|
||||
Some(accepted_currencies_info),
|
||||
None::<()>,
|
||||
Vec::new(),
|
||||
)
|
||||
.make_in_aggregator(currencies, Some(accepted_currencies_info), None::<()>)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
agg_nodes.push((accepted_currencies_id, relation, graph::Strength::Strong));
|
||||
agg_nodes.push((accepted_currencies_id, relation, cgraph::Strength::Strong));
|
||||
}
|
||||
|
||||
let mut amount_nodes = Vec::with_capacity(2);
|
||||
@ -108,14 +100,11 @@ fn compile_request_pm_types(
|
||||
};
|
||||
|
||||
let min_amt_info = "Minimum Amount";
|
||||
let min_amt_id = builder
|
||||
.make_value_node(
|
||||
dir::DirValue::PaymentAmount(num_val).into(),
|
||||
Some(min_amt_info),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let min_amt_id = builder.make_value_node(
|
||||
dir::DirValue::PaymentAmount(num_val).into(),
|
||||
Some(min_amt_info),
|
||||
None::<()>,
|
||||
);
|
||||
|
||||
amount_nodes.push(min_amt_id);
|
||||
}
|
||||
@ -127,14 +116,11 @@ fn compile_request_pm_types(
|
||||
};
|
||||
|
||||
let max_amt_info = "Maximum Amount";
|
||||
let max_amt_id = builder
|
||||
.make_value_node(
|
||||
dir::DirValue::PaymentAmount(num_val).into(),
|
||||
Some(max_amt_info),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let max_amt_id = builder.make_value_node(
|
||||
dir::DirValue::PaymentAmount(num_val).into(),
|
||||
Some(max_amt_info),
|
||||
None::<()>,
|
||||
);
|
||||
|
||||
amount_nodes.push(max_amt_id);
|
||||
}
|
||||
@ -145,14 +131,11 @@ fn compile_request_pm_types(
|
||||
refinement: None,
|
||||
};
|
||||
|
||||
let zero_amt_id = builder
|
||||
.make_value_node(
|
||||
dir::DirValue::PaymentAmount(zero_num_val).into(),
|
||||
Some("zero_amount"),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let zero_amt_id = builder.make_value_node(
|
||||
dir::DirValue::PaymentAmount(zero_num_val).into(),
|
||||
Some("zero_amount"),
|
||||
None::<()>,
|
||||
);
|
||||
|
||||
let or_node_neighbor_id = if amount_nodes.len() == 1 {
|
||||
amount_nodes
|
||||
@ -163,7 +146,13 @@ fn compile_request_pm_types(
|
||||
let nodes = amount_nodes
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|node_id| (node_id, graph::Relation::Positive, graph::Strength::Strong))
|
||||
.map(|node_id| {
|
||||
(
|
||||
node_id,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
builder
|
||||
@ -171,7 +160,7 @@ fn compile_request_pm_types(
|
||||
&nodes,
|
||||
Some("amount_constraint_aggregator"),
|
||||
None::<()>,
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?
|
||||
};
|
||||
@ -179,37 +168,40 @@ fn compile_request_pm_types(
|
||||
let any_aggregator = builder
|
||||
.make_any_aggregator(
|
||||
&[
|
||||
(zero_amt_id, graph::Relation::Positive),
|
||||
(or_node_neighbor_id, graph::Relation::Positive),
|
||||
(
|
||||
zero_amt_id,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
),
|
||||
(
|
||||
or_node_neighbor_id,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
),
|
||||
],
|
||||
Some("zero_plus_limits_amount_aggregator"),
|
||||
None::<()>,
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
agg_nodes.push((
|
||||
any_aggregator,
|
||||
graph::Relation::Positive,
|
||||
graph::Strength::Strong,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
));
|
||||
}
|
||||
|
||||
let pmt_all_aggregator_info = "All Aggregator for PaymentMethodType";
|
||||
builder
|
||||
.make_all_aggregator(
|
||||
&agg_nodes,
|
||||
Some(pmt_all_aggregator_info),
|
||||
None::<()>,
|
||||
Vec::new(),
|
||||
)
|
||||
.make_all_aggregator(&agg_nodes, Some(pmt_all_aggregator_info), None::<()>, None)
|
||||
.map_err(KgraphError::GraphConstructionError)
|
||||
}
|
||||
|
||||
fn compile_payment_method_enabled(
|
||||
builder: &mut graph::KnowledgeGraphBuilder<'_>,
|
||||
builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>,
|
||||
enabled: admin_api::PaymentMethodsEnabled,
|
||||
) -> Result<Option<graph::NodeId>, KgraphError> {
|
||||
) -> Result<Option<cgraph::NodeId>, KgraphError> {
|
||||
let agg_id = if !enabled
|
||||
.payment_method_types
|
||||
.as_ref()
|
||||
@ -217,48 +209,44 @@ fn compile_payment_method_enabled(
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let pm_info = "PaymentMethod";
|
||||
let pm_id = builder
|
||||
.make_value_node(
|
||||
enabled.payment_method.into_dir_value().map(Into::into)?,
|
||||
Some(pm_info),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let pm_id = builder.make_value_node(
|
||||
enabled.payment_method.into_dir_value().map(Into::into)?,
|
||||
Some(pm_info),
|
||||
None::<()>,
|
||||
);
|
||||
|
||||
let mut agg_nodes: Vec<(graph::NodeId, graph::Relation)> = Vec::new();
|
||||
let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
|
||||
|
||||
if let Some(pm_types) = enabled.payment_method_types {
|
||||
for pm_type in pm_types {
|
||||
let node_id = compile_request_pm_types(builder, pm_type, enabled.payment_method)?;
|
||||
agg_nodes.push((node_id, graph::Relation::Positive));
|
||||
agg_nodes.push((
|
||||
node_id,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let any_aggregator_info = "Any aggregation for PaymentMethodsType";
|
||||
let pm_type_agg_id = builder
|
||||
.make_any_aggregator(
|
||||
&agg_nodes,
|
||||
Some(any_aggregator_info),
|
||||
None::<()>,
|
||||
Vec::new(),
|
||||
)
|
||||
.make_any_aggregator(&agg_nodes, Some(any_aggregator_info), None::<()>, None)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
let all_aggregator_info = "All aggregation for PaymentMethod";
|
||||
let enabled_pm_agg_id = builder
|
||||
.make_all_aggregator(
|
||||
&[
|
||||
(pm_id, graph::Relation::Positive, graph::Strength::Strong),
|
||||
(pm_id, cgraph::Relation::Positive, cgraph::Strength::Strong),
|
||||
(
|
||||
pm_type_agg_id,
|
||||
graph::Relation::Positive,
|
||||
graph::Strength::Strong,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
),
|
||||
],
|
||||
Some(all_aggregator_info),
|
||||
None::<()>,
|
||||
Vec::new(),
|
||||
None,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
@ -271,26 +259,30 @@ fn compile_payment_method_enabled(
|
||||
}
|
||||
|
||||
fn compile_merchant_connector_graph(
|
||||
builder: &mut graph::KnowledgeGraphBuilder<'_>,
|
||||
builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>,
|
||||
mca: admin_api::MerchantConnectorResponse,
|
||||
) -> Result<(), KgraphError> {
|
||||
let connector = common_enums::RoutableConnectors::from_str(&mca.connector_name)
|
||||
.map_err(|_| KgraphError::InvalidConnectorName(mca.connector_name.clone()))?;
|
||||
|
||||
let mut agg_nodes: Vec<(graph::NodeId, graph::Relation)> = Vec::new();
|
||||
let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
|
||||
|
||||
if let Some(pms_enabled) = mca.payment_methods_enabled {
|
||||
for pm_enabled in pms_enabled {
|
||||
let maybe_pm_enabled_id = compile_payment_method_enabled(builder, pm_enabled)?;
|
||||
if let Some(pm_enabled_id) = maybe_pm_enabled_id {
|
||||
agg_nodes.push((pm_enabled_id, graph::Relation::Positive));
|
||||
agg_nodes.push((
|
||||
pm_enabled_id,
|
||||
cgraph::Relation::Positive,
|
||||
cgraph::Strength::Strong,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let aggregator_info = "Available Payment methods for connector";
|
||||
let pms_enabled_agg_id = builder
|
||||
.make_any_aggregator(&agg_nodes, Some(aggregator_info), None::<()>, Vec::new())
|
||||
.make_any_aggregator(&agg_nodes, Some(aggregator_info), None::<()>, None)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
let connector_dir_val = dir::DirValue::Connector(Box::new(ast::ConnectorChoice {
|
||||
@ -300,21 +292,16 @@ fn compile_merchant_connector_graph(
|
||||
}));
|
||||
|
||||
let connector_info = "Connector";
|
||||
let connector_node_id = builder
|
||||
.make_value_node(
|
||||
connector_dir_val.into(),
|
||||
Some(connector_info),
|
||||
vec![DomainIdentifier::new(DOMAIN_IDENTIFIER)],
|
||||
None::<()>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
let connector_node_id =
|
||||
builder.make_value_node(connector_dir_val.into(), Some(connector_info), None::<()>);
|
||||
|
||||
builder
|
||||
.make_edge(
|
||||
pms_enabled_agg_id,
|
||||
connector_node_id,
|
||||
graph::Strength::Normal,
|
||||
graph::Relation::Positive,
|
||||
cgraph::Strength::Normal,
|
||||
cgraph::Relation::Positive,
|
||||
None::<cgraph::DomainId>,
|
||||
)
|
||||
.map_err(KgraphError::GraphConstructionError)?;
|
||||
|
||||
@ -323,11 +310,11 @@ fn compile_merchant_connector_graph(
|
||||
|
||||
pub fn make_mca_graph<'a>(
|
||||
accts: Vec<admin_api::MerchantConnectorResponse>,
|
||||
) -> Result<graph::KnowledgeGraph<'a>, KgraphError> {
|
||||
let mut builder = graph::KnowledgeGraphBuilder::new();
|
||||
) -> Result<cgraph::ConstraintGraph<'a, dir::DirValue>, KgraphError> {
|
||||
let mut builder = cgraph::ConstraintGraphBuilder::new();
|
||||
let _domain = builder.make_domain(
|
||||
DomainIdentifier::new(DOMAIN_IDENTIFIER),
|
||||
"Payment methods enabled for MerchantConnectorAccount".to_string(),
|
||||
DOMAIN_IDENTIFIER,
|
||||
"Payment methods enabled for MerchantConnectorAccount",
|
||||
);
|
||||
for acct in accts {
|
||||
compile_merchant_connector_graph(&mut builder, acct)?;
|
||||
@ -343,12 +330,13 @@ mod tests {
|
||||
use api_models::enums as api_enums;
|
||||
use euclid::{
|
||||
dirval,
|
||||
dssa::graph::{AnalysisContext, Memoization},
|
||||
dssa::graph::{AnalysisContext, CgraphExt},
|
||||
};
|
||||
use hyperswitch_constraint_graph::{ConstraintGraph, CycleCheck, Memoization};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn build_test_data<'a>() -> graph::KnowledgeGraph<'a> {
|
||||
fn build_test_data<'a>() -> ConstraintGraph<'a, dir::DirValue> {
|
||||
use api_models::{admin::*, payment_methods::*};
|
||||
|
||||
let stripe_account = MerchantConnectorResponse {
|
||||
@ -428,6 +416,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
@ -448,6 +438,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
@ -468,6 +460,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
@ -488,6 +482,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 7),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
@ -507,6 +503,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 7),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
//println!("{:#?}", result);
|
||||
@ -529,6 +527,8 @@ mod tests {
|
||||
dirval!(PaymentAmount = 100),
|
||||
]),
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
//println!("{:#?}", result);
|
||||
@ -725,6 +725,8 @@ mod tests {
|
||||
dirval!(Connector = Stripe),
|
||||
&context,
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "stripe validation failed");
|
||||
@ -733,6 +735,8 @@ mod tests {
|
||||
dirval!(Connector = Bluesnap),
|
||||
&context,
|
||||
&mut Memoization::new(),
|
||||
&mut CycleCheck::new(),
|
||||
None,
|
||||
);
|
||||
assert!(result.is_err(), "bluesnap validation failed");
|
||||
}
|
||||
|
||||
@ -103,6 +103,7 @@ analytics = { version = "0.1.0", path = "../analytics", optional = true }
|
||||
cards = { version = "0.1.0", path = "../cards" }
|
||||
common_enums = { version = "0.1.0", path = "../common_enums" }
|
||||
common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs"] }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" }
|
||||
currency_conversion = { version = "0.1.0", path = "../currency_conversion" }
|
||||
hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false }
|
||||
diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] }
|
||||
|
||||
@ -17,9 +17,9 @@ use diesel_models::enums as storage_enums;
|
||||
use error_stack::ResultExt;
|
||||
use euclid::{
|
||||
backend::{self, inputs as dsl_inputs, EuclidBackend},
|
||||
dssa::graph::{self as euclid_graph, Memoization},
|
||||
dssa::graph::{self as euclid_graph, CgraphExt},
|
||||
enums as euclid_enums,
|
||||
frontend::ast,
|
||||
frontend::{ast, dir as euclid_dir},
|
||||
};
|
||||
use kgraph_utils::{
|
||||
mca as mca_graph,
|
||||
@ -82,7 +82,9 @@ pub struct SessionRoutingPmTypeInput<'a> {
|
||||
profile_id: Option<String>,
|
||||
}
|
||||
static ROUTING_CACHE: StaticCache<CachedAlgorithm> = StaticCache::new();
|
||||
static KGRAPH_CACHE: StaticCache<euclid_graph::KnowledgeGraph<'_>> = StaticCache::new();
|
||||
static KGRAPH_CACHE: StaticCache<
|
||||
hyperswitch_constraint_graph::ConstraintGraph<'_, euclid_dir::DirValue>,
|
||||
> = StaticCache::new();
|
||||
|
||||
type RoutingResult<O> = oss_errors::CustomResult<O, errors::RoutingError>;
|
||||
|
||||
@ -542,7 +544,7 @@ pub async fn get_merchant_kgraph<'a>(
|
||||
merchant_last_modified: i64,
|
||||
#[cfg(feature = "business_profile_routing")] profile_id: Option<String>,
|
||||
transaction_type: &api_enums::TransactionType,
|
||||
) -> RoutingResult<Arc<euclid_graph::KnowledgeGraph<'a>>> {
|
||||
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<'a, euclid_dir::DirValue>>> {
|
||||
let merchant_id = &key_store.merchant_id;
|
||||
|
||||
#[cfg(feature = "business_profile_routing")]
|
||||
@ -690,7 +692,13 @@ async fn perform_kgraph_filtering(
|
||||
.into_dir_value()
|
||||
.change_context(errors::RoutingError::KgraphAnalysisError)?;
|
||||
let kgraph_eligible = cached_kgraph
|
||||
.check_value_validity(dir_val, &context, &mut Memoization::new())
|
||||
.check_value_validity(
|
||||
dir_val,
|
||||
&context,
|
||||
&mut hyperswitch_constraint_graph::Memoization::new(),
|
||||
&mut hyperswitch_constraint_graph::CycleCheck::new(),
|
||||
None,
|
||||
)
|
||||
.change_context(errors::RoutingError::KgraphAnalysisError)?;
|
||||
|
||||
let filter_eligible =
|
||||
|
||||
Reference in New Issue
Block a user