mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
feat(constraint_graph): add visualization functionality to the constraint graph (#4701)
This commit is contained in:
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -2618,6 +2618,21 @@ dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dot-generator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0aaac7ada45f71873ebce336491d1c1bc4a7c8042c7cea978168ad59e805b871"
|
||||
dependencies = [
|
||||
"dot-structures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dot-structures"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "675e35c02a51bb4d4618cb4885b3839ce6d1787c97b664474d9208d074742e20"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@ -3253,6 +3268,22 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphviz-rust"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27dafd1ac303e0dfb347a3861d9ac440859bab26ec2f534bbceb262ea492a1e0"
|
||||
dependencies = [
|
||||
"dot-generator",
|
||||
"dot-structures",
|
||||
"into-attr",
|
||||
"into-attr-derive",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rand",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.25"
|
||||
@ -3624,6 +3655,7 @@ name = "hyperswitch_constraint_graph"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"erased-serde 0.3.31",
|
||||
"graphviz-rust",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3774,6 +3806,28 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "into-attr"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18b48c537e49a709e678caec3753a7dba6854661a1eaa27675024283b3f8b376"
|
||||
dependencies = [
|
||||
"dot-structures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "into-attr-derive"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecac7c1ae6cd2c6a3a64d1061a8bdc7f52ff62c26a831a2301e54c1b5d70d5b1"
|
||||
dependencies = [
|
||||
"dot-generator",
|
||||
"dot-structures",
|
||||
"into-attr",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
|
||||
@ -21,7 +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" }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph", features = ["viz"] }
|
||||
euclid_macros = { version = "0.1.0", path = "../euclid_macros" }
|
||||
|
||||
[features]
|
||||
|
||||
@ -22,6 +22,12 @@ pub mod euclid_graph_prelude {
|
||||
|
||||
impl cgraph::KeyNode for dir::DirKey {}
|
||||
|
||||
impl cgraph::NodeViz for dir::DirKey {
|
||||
fn viz(&self) -> String {
|
||||
self.kind.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl cgraph::ValueNode for dir::DirValue {
|
||||
type Key = dir::DirKey;
|
||||
|
||||
@ -30,6 +36,41 @@ impl cgraph::ValueNode for dir::DirValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl cgraph::NodeViz for dir::DirValue {
|
||||
fn viz(&self) -> String {
|
||||
match self {
|
||||
Self::PaymentMethod(pm) => pm.to_string(),
|
||||
Self::CardBin(bin) => bin.value.clone(),
|
||||
Self::CardType(ct) => ct.to_string(),
|
||||
Self::CardNetwork(cn) => cn.to_string(),
|
||||
Self::PayLaterType(plt) => plt.to_string(),
|
||||
Self::WalletType(wt) => wt.to_string(),
|
||||
Self::UpiType(ut) => ut.to_string(),
|
||||
Self::BankTransferType(btt) => btt.to_string(),
|
||||
Self::BankRedirectType(brt) => brt.to_string(),
|
||||
Self::BankDebitType(bdt) => bdt.to_string(),
|
||||
Self::CryptoType(ct) => ct.to_string(),
|
||||
Self::RewardType(rt) => rt.to_string(),
|
||||
Self::PaymentAmount(amt) => amt.number.to_string(),
|
||||
Self::PaymentCurrency(curr) => curr.to_string(),
|
||||
Self::AuthenticationType(at) => at.to_string(),
|
||||
Self::CaptureMethod(cm) => cm.to_string(),
|
||||
Self::BusinessCountry(bc) => bc.to_string(),
|
||||
Self::BillingCountry(bc) => bc.to_string(),
|
||||
Self::Connector(conn) => conn.connector.to_string(),
|
||||
Self::MetaData(mv) => format!("[{} = {}]", mv.key, mv.value),
|
||||
Self::MandateAcceptanceType(mat) => mat.to_string(),
|
||||
Self::MandateType(mt) => mt.to_string(),
|
||||
Self::PaymentType(pt) => pt.to_string(),
|
||||
Self::VoucherType(vt) => vt.to_string(),
|
||||
Self::GiftCardType(gct) => gct.to_string(),
|
||||
Self::BusinessLabel(bl) => bl.value.to_string(),
|
||||
Self::SetupFutureUsage(sfu) => sfu.to_string(),
|
||||
Self::CardRedirectType(crt) => crt.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(tag = "type", content = "details", rename_all = "snake_case")]
|
||||
pub enum AnalysisError<V: cgraph::ValueNode> {
|
||||
|
||||
@ -289,7 +289,6 @@ pub enum DirKeyKind {
|
||||
#[serde(rename = "billing_country")]
|
||||
BillingCountry,
|
||||
#[serde(skip_deserializing, rename = "connector")]
|
||||
#[strum(disabled)]
|
||||
Connector,
|
||||
#[strum(
|
||||
serialize = "business_label",
|
||||
|
||||
@ -7,8 +7,12 @@ rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
viz = ["dep:graphviz-rust"]
|
||||
|
||||
[dependencies]
|
||||
erased-serde = "0.3.28"
|
||||
graphviz-rust = { version = "0.6.2", optional = true }
|
||||
rustc-hash = "1.1.0"
|
||||
serde = { version = "1.0.163", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.96"
|
||||
|
||||
@ -585,3 +585,92 @@ where
|
||||
Ok(node_builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "viz")]
|
||||
mod viz {
|
||||
use graphviz_rust::{
|
||||
dot_generator::*,
|
||||
dot_structures::*,
|
||||
printer::{DotPrinter, PrinterContext},
|
||||
};
|
||||
|
||||
use crate::{dense_map::EntityId, types, ConstraintGraph, NodeViz, ValueNode};
|
||||
|
||||
fn get_node_id(node_id: types::NodeId) -> String {
|
||||
format!("N{}", node_id.get_id())
|
||||
}
|
||||
|
||||
impl<'a, V> ConstraintGraph<'a, V>
|
||||
where
|
||||
V: ValueNode + NodeViz,
|
||||
<V as ValueNode>::Key: NodeViz,
|
||||
{
|
||||
fn get_node_label(node: &types::Node<V>) -> String {
|
||||
let label = match &node.node_type {
|
||||
types::NodeType::Value(types::NodeValue::Key(key)) => format!("any {}", key.viz()),
|
||||
types::NodeType::Value(types::NodeValue::Value(val)) => {
|
||||
format!("{} = {}", val.get_key().viz(), val.viz())
|
||||
}
|
||||
types::NodeType::AllAggregator => "&&".to_string(),
|
||||
types::NodeType::AnyAggregator => "| |".to_string(),
|
||||
types::NodeType::InAggregator(agg) => {
|
||||
let key = if let Some(val) = agg.iter().next() {
|
||||
val.get_key().viz()
|
||||
} else {
|
||||
return "empty in".to_string();
|
||||
};
|
||||
|
||||
let nodes = agg.iter().map(NodeViz::viz).collect::<Vec<_>>();
|
||||
format!("{key} in [{}]", nodes.join(", "))
|
||||
}
|
||||
};
|
||||
|
||||
format!("\"{label}\"")
|
||||
}
|
||||
|
||||
fn build_node(cg_node_id: types::NodeId, cg_node: &types::Node<V>) -> Node {
|
||||
let viz_node_id = get_node_id(cg_node_id);
|
||||
let viz_node_label = Self::get_node_label(cg_node);
|
||||
|
||||
node!(viz_node_id; attr!("label", viz_node_label))
|
||||
}
|
||||
|
||||
fn build_edge(cg_edge: &types::Edge) -> Edge {
|
||||
let pred_vertex = get_node_id(cg_edge.pred);
|
||||
let succ_vertex = get_node_id(cg_edge.succ);
|
||||
let arrowhead = match cg_edge.strength {
|
||||
types::Strength::Weak => "onormal",
|
||||
types::Strength::Normal => "normal",
|
||||
types::Strength::Strong => "normalnormal",
|
||||
};
|
||||
let color = match cg_edge.relation {
|
||||
types::Relation::Positive => "blue",
|
||||
types::Relation::Negative => "red",
|
||||
};
|
||||
|
||||
edge!(
|
||||
node_id!(pred_vertex) => node_id!(succ_vertex);
|
||||
attr!("arrowhead", arrowhead),
|
||||
attr!("color", color)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_viz_digraph(&self) -> Graph {
|
||||
graph!(
|
||||
strict di id!("constraint_graph"),
|
||||
self.nodes
|
||||
.iter()
|
||||
.map(|(node_id, node)| Self::build_node(node_id, node))
|
||||
.map(Stmt::Node)
|
||||
.chain(self.edges.values().map(Self::build_edge).map(Stmt::Edge))
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_viz_digraph_string(&self) -> String {
|
||||
let mut ctx = PrinterContext::default();
|
||||
let digraph = self.get_viz_digraph();
|
||||
digraph.print(&mut ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ pub mod types;
|
||||
pub use builder::ConstraintGraphBuilder;
|
||||
pub use error::{AnalysisTrace, GraphError};
|
||||
pub use graph::ConstraintGraph;
|
||||
#[cfg(feature = "viz")]
|
||||
pub use types::NodeViz;
|
||||
pub use types::{
|
||||
CheckingContext, CycleCheck, DomainId, DomainIdentifier, Edge, EdgeId, KeyNode, Memoization,
|
||||
Node, NodeId, NodeValue, Relation, Strength, ValueNode,
|
||||
|
||||
@ -17,6 +17,11 @@ pub trait ValueNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + Partia
|
||||
fn get_key(&self) -> Self::Key;
|
||||
}
|
||||
|
||||
#[cfg(feature = "viz")]
|
||||
pub trait NodeViz {
|
||||
fn viz(&self) -> String;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash)]
|
||||
#[serde(transparent)]
|
||||
pub struct NodeId(usize);
|
||||
|
||||
@ -13,7 +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" }
|
||||
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph", features = ["viz"] }
|
||||
euclid = { version = "0.1.0", path = "../euclid" }
|
||||
masking = { version = "0.1.0", path = "../masking/" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user