mockforge_core/graph/
relationships.rs1use crate::graph::{EdgeType, GraphEdge};
7use crate::request_chaining::ChainDefinition;
8use std::collections::HashMap;
9
10pub 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 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 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
40fn discover_service_call_from_url(url: &str) -> Option<GraphEdge> {
46 let host =
48 if let Some(rest) = url.strip_prefix("https://").or_else(|| url.strip_prefix("http://")) {
49 let host_part = rest.split('/').next().unwrap_or(rest);
51 host_part.split(':').next().unwrap_or(host_part)
53 } else {
54 return None;
56 };
57
58 if host.is_empty() || host == "localhost" || host == "127.0.0.1" || host == "0.0.0.0" {
59 return None;
60 }
61
62 let service_name = host.split('.').next().unwrap_or(host);
64
65 let path = url
67 .find("://")
68 .and_then(|i| url[i + 3..].find('/'))
69 .map(|i| &url[url.find("://").unwrap() + 3 + i..])
70 .unwrap_or("/");
71
72 Some(GraphEdge {
73 from: "caller".to_string(),
74 to: format!("service:{service_name}"),
75 edge_type: EdgeType::ServiceCall,
76 label: Some(format!("calls {path}")),
77 metadata: {
78 let mut meta = HashMap::new();
79 meta.insert("target_host".to_string(), serde_json::Value::String(host.to_string()));
80 meta.insert("url".to_string(), serde_json::Value::String(url.to_string()));
81 meta
82 },
83 })
84}
85
86pub fn discover_state_transitions(
88 state_machines: &[crate::intelligent_behavior::rules::StateMachine],
89) -> Vec<GraphEdge> {
90 use serde_json;
91 let mut edges = Vec::new();
92
93 for state_machine in state_machines {
94 for transition in &state_machine.transitions {
95 let from_node_id =
100 format!("state:{}:{}", state_machine.resource_type, transition.from_state);
101 let to_node_id =
102 format!("state:{}:{}", state_machine.resource_type, transition.to_state);
103
104 edges.push(GraphEdge {
105 from: from_node_id,
106 to: to_node_id,
107 edge_type: EdgeType::StateTransition,
108 label: Some(format!("{} → {}", transition.from_state, transition.to_state)),
109 metadata: {
110 let mut meta = HashMap::new();
111 meta.insert(
112 "resource_type".to_string(),
113 serde_json::Value::String(state_machine.resource_type.clone()),
114 );
115 meta.insert(
116 "probability".to_string(),
117 serde_json::Value::Number(
118 serde_json::Number::from_f64(transition.probability)
119 .unwrap_or_else(|| serde_json::Number::from(0)),
120 ),
121 );
122 meta
123 },
124 });
125 }
126 }
127
128 edges
129}
130
131pub fn extract_endpoint_ids_from_chain(chain: &ChainDefinition) -> Vec<String> {
133 chain.links.iter().map(|link| link.request.id.clone()).collect()
134}
135
136pub fn group_endpoints_by_service(
138 endpoints: &[(String, String)], ) -> HashMap<String, Vec<String>> {
140 let mut service_groups: HashMap<String, Vec<String>> = HashMap::new();
141
142 for (endpoint_id, url) in endpoints {
143 let service_name = extract_service_name_from_url(url);
146 service_groups.entry(service_name).or_default().push(endpoint_id.clone());
147 }
148
149 service_groups
150}
151
152fn extract_service_name_from_url(url: &str) -> String {
154 if let Some(domain) = url.split("://").nth(1) {
156 if let Some(host) = domain.split('/').next() {
157 return host.split('.').next().unwrap_or("default").to_string();
158 }
159 }
160
161 if let Some(first_segment) = url.split('/').nth(1) {
163 return first_segment.to_string();
164 }
165
166 "default".to_string()
167}