use crate::graph::{EdgeType, GraphEdge};
use crate::request_chaining::ChainDefinition;
use std::collections::HashMap;
pub fn discover_chain_relationships(chains: &[ChainDefinition]) -> Vec<GraphEdge> {
let mut edges = Vec::new();
for chain in chains {
for link in &chain.links {
let link_node_id = format!("chain_link:{}:{}", chain.id, link.request.id);
for dep_id in &link.request.depends_on {
let dep_node_id = format!("chain_link:{}:{}", chain.id, dep_id);
edges.push(GraphEdge {
from: dep_node_id,
to: link_node_id.clone(),
edge_type: EdgeType::Dependency,
label: Some("depends on".to_string()),
metadata: HashMap::new(),
});
}
if let Some(service_call) = discover_service_call_from_url(&link.request.url) {
edges.push(service_call);
}
}
}
edges
}
fn discover_service_call_from_url(url: &str) -> Option<GraphEdge> {
let host =
if let Some(rest) = url.strip_prefix("https://").or_else(|| url.strip_prefix("http://")) {
let host_part = rest.split('/').next().unwrap_or(rest);
host_part.split(':').next().unwrap_or(host_part)
} else {
return None;
};
if host.is_empty() || host == "localhost" || host == "127.0.0.1" || host == "0.0.0.0" {
return None;
}
let service_name = host.split('.').next().unwrap_or(host);
let path = url
.find("://")
.and_then(|i| url[i + 3..].find('/'))
.map(|i| &url[url.find("://").unwrap() + 3 + i..])
.unwrap_or("/");
Some(GraphEdge {
from: "caller".to_string(),
to: format!("service:{service_name}"),
edge_type: EdgeType::ServiceCall,
label: Some(format!("calls {path}")),
metadata: {
let mut meta = HashMap::new();
meta.insert("target_host".to_string(), serde_json::Value::String(host.to_string()));
meta.insert("url".to_string(), serde_json::Value::String(url.to_string()));
meta
},
})
}
pub fn discover_state_transitions(
state_machines: &[crate::intelligent_behavior::rules::StateMachine],
) -> Vec<GraphEdge> {
use serde_json;
let mut edges = Vec::new();
for state_machine in state_machines {
for transition in &state_machine.transitions {
let from_node_id =
format!("state:{}:{}", state_machine.resource_type, transition.from_state);
let to_node_id =
format!("state:{}:{}", state_machine.resource_type, transition.to_state);
edges.push(GraphEdge {
from: from_node_id,
to: to_node_id,
edge_type: EdgeType::StateTransition,
label: Some(format!("{} → {}", transition.from_state, transition.to_state)),
metadata: {
let mut meta = HashMap::new();
meta.insert(
"resource_type".to_string(),
serde_json::Value::String(state_machine.resource_type.clone()),
);
meta.insert(
"probability".to_string(),
serde_json::Value::Number(
serde_json::Number::from_f64(transition.probability)
.unwrap_or_else(|| serde_json::Number::from(0)),
),
);
meta
},
});
}
}
edges
}
pub fn extract_endpoint_ids_from_chain(chain: &ChainDefinition) -> Vec<String> {
chain.links.iter().map(|link| link.request.id.clone()).collect()
}
pub fn group_endpoints_by_service(
endpoints: &[(String, String)], ) -> HashMap<String, Vec<String>> {
let mut service_groups: HashMap<String, Vec<String>> = HashMap::new();
for (endpoint_id, url) in endpoints {
let service_name = extract_service_name_from_url(url);
service_groups.entry(service_name).or_default().push(endpoint_id.clone());
}
service_groups
}
fn extract_service_name_from_url(url: &str) -> String {
if let Some(domain) = url.split("://").nth(1) {
if let Some(host) = domain.split('/').next() {
return host.split('.').next().unwrap_or("default").to_string();
}
}
if let Some(first_segment) = url.split('/').nth(1) {
return first_segment.to_string();
}
"default".to_string()
}