use crate::config::Config;
use crate::container::get_registered_services;
use crate::metrics;
use crate::middleware::get_global_middleware_info;
use crate::routing::get_registered_routes;
use bytes::Bytes;
use chrono::Utc;
use http_body_util::Full;
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct DebugResponse<T: Serialize> {
pub success: bool,
pub data: T,
pub timestamp: String,
}
#[derive(Debug, Serialize)]
pub struct DebugErrorResponse {
pub success: bool,
pub error: String,
pub timestamp: String,
}
pub fn is_debug_enabled() -> bool {
if Config::is_production() {
return std::env::var("FERRO_DEBUG_ENDPOINTS")
.map(|v| v == "true" || v == "1")
.unwrap_or(false);
}
true
}
fn json_response<T: Serialize>(data: T, status: u16) -> hyper::Response<Full<Bytes>> {
let body = serde_json::to_string_pretty(&data).unwrap_or_else(|_| "{}".to_string());
hyper::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(Full::new(Bytes::from(body)))
.unwrap()
}
pub fn handle_routes() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
let routes = get_registered_routes();
json_response(
DebugResponse {
success: true,
data: routes,
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}
#[derive(Debug, Serialize)]
pub struct MiddlewareInfo {
pub global: Vec<String>,
}
pub fn handle_middleware() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
let global = get_global_middleware_info();
json_response(
DebugResponse {
success: true,
data: MiddlewareInfo { global },
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}
pub fn handle_services() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
let services = get_registered_services();
json_response(
DebugResponse {
success: true,
data: services,
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}
pub fn handle_metrics() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
let snapshot = metrics::get_metrics();
json_response(
DebugResponse {
success: true,
data: snapshot,
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}
#[derive(Debug, Serialize)]
pub struct QueueJobsInfo {
pub pending: Vec<ferro_queue::JobInfo>,
pub delayed: Vec<ferro_queue::JobInfo>,
pub failed: Vec<ferro_queue::FailedJobInfo>,
}
pub async fn handle_queue_jobs() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
if !ferro_queue::Queue::is_initialized() {
return json_response(
DebugErrorResponse {
success: false,
error: "Queue not initialized (QUEUE_CONNECTION=sync or Redis not configured)"
.to_string(),
timestamp: Utc::now().to_rfc3339(),
},
503,
);
}
let conn = ferro_queue::Queue::connection();
let default_queue = conn.config().default_queue.as_str();
let pending = conn
.get_pending_jobs(default_queue, 100)
.await
.unwrap_or_default();
let delayed = conn
.get_delayed_jobs(default_queue, 100)
.await
.unwrap_or_default();
let failed = conn.get_failed_jobs(100).await.unwrap_or_default();
json_response(
DebugResponse {
success: true,
data: QueueJobsInfo {
pending,
delayed,
failed,
},
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}
pub async fn handle_queue_stats() -> hyper::Response<Full<Bytes>> {
if !is_debug_enabled() {
return json_response(
DebugErrorResponse {
success: false,
error: "Debug endpoints disabled in production".to_string(),
timestamp: Utc::now().to_rfc3339(),
},
403,
);
}
if !ferro_queue::Queue::is_initialized() {
return json_response(
DebugErrorResponse {
success: false,
error: "Queue not initialized (QUEUE_CONNECTION=sync or Redis not configured)"
.to_string(),
timestamp: Utc::now().to_rfc3339(),
},
503,
);
}
let conn = ferro_queue::Queue::connection();
let default_queue = conn.config().default_queue.as_str();
let stats = conn.get_stats(&[default_queue]).await.unwrap_or_default();
json_response(
DebugResponse {
success: true,
data: stats,
timestamp: Utc::now().to_rfc3339(),
},
200,
)
}