use axum::extract::Request;
use mockforge_core::consistency::UnifiedState;
#[cfg(feature = "persona-graph")]
use mockforge_data::apply_lifecycle_effects;
use serde_json::Value;
#[allow(unused_mut)]
pub async fn enrich_response(
request: &Request,
mut response: Value,
_workspace_id: &str,
_endpoint_type: Option<&str>,
) -> Value {
let unified_state = request.extensions().get::<UnifiedState>();
if let Some(_state) = unified_state {
#[cfg(feature = "persona-graph")]
if let Some(ref persona) = _state.active_persona {
let endpoint_type = _endpoint_type.unwrap_or_else(|| {
let path = request.uri().path();
if path.contains("/users/") || path.contains("/user/") {
"user"
} else if path.contains("/orders/") || path.contains("/order/") {
"order"
} else if path.contains("/billing")
|| path.contains("/payment")
|| path.contains("/subscription")
{
"billing"
} else if path.contains("/support") || path.contains("/tickets") {
"support"
} else if path.contains("/fulfillment")
|| path.contains("/shipment")
|| path.contains("/delivery")
{
"fulfillment"
} else if path.contains("/loan")
|| path.contains("/credit")
|| path.contains("/application")
{
"loan"
} else {
"default"
}
});
let entity_id = extract_entity_id_from_path(request.uri().path());
if let Some(graph) = _state.persona_graph() {
enrich_with_persona_graph(
graph,
&persona.id,
&mut response,
request.uri().path(),
entity_id.as_deref(),
);
}
if let Some(ref lifecycle) = persona.lifecycle {
apply_lifecycle_effects(&mut response, lifecycle, endpoint_type);
}
}
}
response
}
#[cfg(feature = "persona-graph")]
fn extract_entity_id_from_path(path: &str) -> Option<String> {
let segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if segments.len() >= 2 {
let last = segments.last()?;
if !last.contains('?') && !last.contains('&') {
return Some(last.to_string());
}
}
None
}
#[cfg(feature = "persona-graph")]
fn enrich_with_persona_graph(
graph: &mockforge_data::PersonaGraph,
_persona_id: &str,
response: &mut Value,
path: &str,
entity_id: Option<&str>,
) {
let path_lower = path.to_lowercase();
let entity_type = if path_lower.contains("/users/") || path_lower.contains("/user/") {
"user"
} else if path_lower.contains("/orders/") || path.contains("/order/") {
"order"
} else {
return; };
if let Some(id) = entity_id {
let entity_persona_id = format!("{}:{}", entity_type, id);
if entity_type == "user" {
let related_orders =
graph.find_related_by_entity_type(&entity_persona_id, "order", Some("has_orders"));
if let Some(obj) = response.as_object_mut() {
if !related_orders.is_empty() {
let order_ids: Vec<String> = related_orders
.iter()
.filter_map(|pid| pid.strip_prefix("order:").map(|s| s.to_string()))
.collect();
obj.insert(
"order_ids".to_string(),
Value::Array(
order_ids.iter().map(|id| Value::String(id.clone())).collect(),
),
);
obj.insert("order_count".to_string(), Value::Number(order_ids.len().into()));
}
}
}
if entity_type == "order" {
let related_payments = graph.find_related_by_entity_type(
&entity_persona_id,
"payment",
Some("has_payment"),
);
if let Some(obj) = response.as_object_mut() {
if !related_payments.is_empty() {
let payment_ids: Vec<String> = related_payments
.iter()
.filter_map(|pid| pid.strip_prefix("payment:").map(|s| s.to_string()))
.collect();
obj.insert(
"payment_ids".to_string(),
Value::Array(
payment_ids.iter().map(|id| Value::String(id.clone())).collect(),
),
);
}
}
}
}
}
pub fn extract_workspace_id(request: &Request) -> String {
request
.headers()
.get("X-MockForge-Workspace")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
.or_else(|| {
request.uri().query().and_then(|q| {
q.split('&').find_map(|pair| {
let mut parts = pair.splitn(2, '=');
if parts.next() == Some("workspace") {
parts.next().and_then(|v| {
urlencoding::decode(v).ok().map(|decoded| decoded.to_string())
})
} else {
None
}
})
})
})
.unwrap_or_else(|| "default".to_string())
}