mockforge_core/graph/
relationships.rs

1//! Relationship discovery for graph visualization
2//!
3//! This module analyzes various data sources to discover relationships
4//! between endpoints, services, and chains.
5
6use crate::graph::{EdgeType, GraphEdge};
7use crate::request_chaining::{ChainDefinition, ChainLink};
8use std::collections::HashMap;
9
10/// Discover relationships from chain definitions
11pub fn discover_chain_relationships(chains: &[ChainDefinition]) -> Vec<GraphEdge> {
12    let mut edges = Vec::new();
13
14    for chain in chains {
15        for link in &chain.links {
16            let link_node_id = format!("chain_link:{}:{}", chain.id, link.request.id);
17
18            // Create dependency edges
19            for dep_id in &link.request.depends_on {
20                let dep_node_id = format!("chain_link:{}:{}", chain.id, dep_id);
21                edges.push(GraphEdge {
22                    from: dep_node_id,
23                    to: link_node_id.clone(),
24                    edge_type: EdgeType::Dependency,
25                    label: Some("depends on".to_string()),
26                    metadata: HashMap::new(),
27                });
28            }
29
30            // Try to discover cross-service calls from URLs
31            if let Some(service_call) = discover_service_call_from_url(&link.request.url) {
32                edges.push(service_call);
33            }
34        }
35    }
36
37    edges
38}
39
40/// Discover service call relationships from URL patterns
41fn discover_service_call_from_url(url: &str) -> Option<GraphEdge> {
42    // Simple heuristic: if URL contains a different service identifier
43    // This is a placeholder - in production, you'd want more sophisticated
44    // URL parsing and service discovery
45    if url.contains("://") {
46        // Could parse URL and identify service boundaries
47        // For now, return None as this requires more context
48        None
49    } else {
50        None
51    }
52}
53
54/// Discover state transition relationships from state machines
55pub fn discover_state_transitions(
56    state_machines: &[crate::intelligent_behavior::rules::StateMachine],
57) -> Vec<GraphEdge> {
58    use serde_json;
59    let mut edges = Vec::new();
60
61    for state_machine in state_machines {
62        for transition in &state_machine.transitions {
63            // Create edges for state transitions
64            // Note: This requires mapping states to endpoint nodes
65            // For now, we'll create placeholder edges that need to be
66            // connected to actual nodes by the builder
67            let from_node_id =
68                format!("state:{}:{}", state_machine.resource_type, transition.from_state);
69            let to_node_id =
70                format!("state:{}:{}", state_machine.resource_type, transition.to_state);
71
72            edges.push(GraphEdge {
73                from: from_node_id,
74                to: to_node_id,
75                edge_type: EdgeType::StateTransition,
76                label: Some(format!("{} → {}", transition.from_state, transition.to_state)),
77                metadata: {
78                    let mut meta = HashMap::new();
79                    meta.insert(
80                        "resource_type".to_string(),
81                        serde_json::Value::String(state_machine.resource_type.clone()),
82                    );
83                    meta.insert(
84                        "probability".to_string(),
85                        serde_json::Value::Number(
86                            serde_json::Number::from_f64(transition.probability)
87                                .unwrap_or_else(|| serde_json::Number::from(0)),
88                        ),
89                    );
90                    meta
91                },
92            });
93        }
94    }
95
96    edges
97}
98
99/// Extract endpoint IDs from chain links
100pub fn extract_endpoint_ids_from_chain(chain: &ChainDefinition) -> Vec<String> {
101    chain.links.iter().map(|link| link.request.id.clone()).collect()
102}
103
104/// Group endpoints by service based on URL patterns
105pub fn group_endpoints_by_service(
106    endpoints: &[(String, String)], // (endpoint_id, url)
107) -> HashMap<String, Vec<String>> {
108    let mut service_groups: HashMap<String, Vec<String>> = HashMap::new();
109
110    for (endpoint_id, url) in endpoints {
111        // Simple heuristic: extract service name from URL
112        // In production, this would be more sophisticated
113        let service_name = extract_service_name_from_url(url);
114        service_groups
115            .entry(service_name)
116            .or_insert_with(Vec::new)
117            .push(endpoint_id.clone());
118    }
119
120    service_groups
121}
122
123/// Extract service name from URL
124fn extract_service_name_from_url(url: &str) -> String {
125    // Simple heuristic: use domain or first path segment
126    if let Some(domain) = url.split("://").nth(1) {
127        if let Some(host) = domain.split('/').next() {
128            return host.split('.').next().unwrap_or("default").to_string();
129        }
130    }
131
132    // Fallback: use first path segment
133    if let Some(first_segment) = url.split('/').nth(1) {
134        return first_segment.to_string();
135    }
136
137    "default".to_string()
138}