#![cfg_attr(coverage_nightly, coverage(off))]
use super::cache::{AnalysisResult, TwoTierCache};
use super::error::{BridgeError, ErrorCode};
use super::pool::ResilientConnectionPool;
use super::sandbox::BridgeSandbox;
use super::transport::StdioTransport;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct BridgeConfig {
pub bridge_path: PathBuf,
pub pool_size: usize,
pub failure_threshold: usize,
pub init_timeout: Duration,
pub enable_cache: bool,
pub enable_sandbox: bool,
}
impl Default for BridgeConfig {
fn default() -> Self {
Self {
bridge_path: PathBuf::from("bridge/dist/index.js"),
pool_size: 10,
failure_threshold: 5,
init_timeout: Duration::from_millis(500),
enable_cache: true,
enable_sandbox: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeRequest {
pub method: String,
pub params: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeResponse {
pub result: serde_json::Value,
}
pub struct ClaudeBridge {
config: BridgeConfig,
pool: Arc<ResilientConnectionPool>,
cache: Arc<TwoTierCache>,
#[allow(dead_code)]
sandbox: BridgeSandbox,
init_time: Duration,
}
impl ClaudeBridge {
pub async fn new(config: BridgeConfig) -> Result<Self, BridgeError> {
let start = Instant::now();
let pool = Arc::new(ResilientConnectionPool::new(
config.pool_size,
config.failure_threshold,
));
let cache = Arc::new(TwoTierCache::new());
let sandbox = BridgeSandbox::new();
let init_time = start.elapsed();
if init_time > config.init_timeout {
return Err(BridgeError::new(
ErrorCode::InitializationTimeout,
format!(
"Bridge initialization took {}ms, exceeds limit of {}ms",
init_time.as_millis(),
config.init_timeout.as_millis()
),
));
}
Ok(Self {
config,
pool,
cache,
sandbox,
init_time,
})
}
pub fn init_time(&self) -> Duration {
self.init_time
}
pub async fn analyze_code(&self, content: &str) -> Result<AnalysisResult, BridgeError> {
if self.config.enable_cache {
let result = self
.cache
.get_with_loader(content, || async {
self.analyze_code_internal(content)
.await
.unwrap_or_default()
})
.await;
return Ok((*result).clone());
}
self.analyze_code_internal(content).await
}
async fn analyze_code_internal(&self, content: &str) -> Result<AnalysisResult, BridgeError> {
let _conn = self.pool.clone().acquire().await.map_err(|e| {
BridgeError::new(ErrorCode::PoolExhausted, format!("Pool error: {}", e))
})?;
Ok(AnalysisResult {
complexity: estimate_complexity(content),
cognitive_complexity: estimate_cognitive_complexity(content),
satd_count: count_satd(content),
content_hash: calculate_hash(content),
..Default::default()
})
}
pub async fn send_request(
&self,
_request: BridgeRequest,
) -> Result<BridgeResponse, BridgeError> {
let _conn = self.pool.clone().acquire().await.map_err(|e| {
BridgeError::new(ErrorCode::PoolExhausted, format!("Pool error: {}", e))
})?;
Ok(BridgeResponse {
result: serde_json::json!({ "status": "ok" }),
})
}
pub fn pool_stats(&self) -> super::pool::PoolStats {
self.pool.stats()
}
pub fn cache_stats(&self) -> super::cache::CacheMetrics {
self.cache.hit_rate()
}
#[allow(dead_code)]
async fn spawn_bridge_process(&self) -> Result<BridgeProcess, BridgeError> {
let mut child = tokio::process::Command::new("node")
.arg(&self.config.bridge_path)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|e| {
BridgeError::new(ErrorCode::WorkerCrashed, format!("Failed to spawn: {}", e))
})?;
let stdin = child
.stdin
.take()
.ok_or_else(|| BridgeError::new(ErrorCode::WorkerCrashed, "Failed to get stdin"))?;
let stdout = child
.stdout
.take()
.ok_or_else(|| BridgeError::new(ErrorCode::WorkerCrashed, "Failed to get stdout"))?;
let transport = StdioTransport::new(stdin, stdout);
Ok(BridgeProcess { child, transport })
}
pub async fn clear_cache(&self) {
self.cache.clear().await;
}
pub async fn health_check(&self) -> bool {
let stats = self.pool.stats();
matches!(stats.circuit_state, super::pool::CircuitState::Closed)
}
}
struct BridgeProcess {
#[allow(dead_code)]
child: tokio::process::Child,
#[allow(dead_code)]
transport: StdioTransport,
}
fn estimate_complexity(content: &str) -> u32 {
let keywords = ["if", "for", "while", "match", "loop"];
let mut complexity = 1;
for keyword in &keywords {
complexity += content.matches(keyword).count() as u32;
}
complexity.min(100)
}
fn estimate_cognitive_complexity(content: &str) -> u32 {
(estimate_complexity(content) as f32 * 0.7) as u32
}
fn count_satd(content: &str) -> usize {
let patterns = ["TODO", "FIXME", "HACK", "XXX"];
patterns.iter().map(|p| content.matches(p).count()).sum()
}
fn calculate_hash(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
let mut hasher = DefaultHasher::new();
hasher.write(content.as_bytes());
hasher.finish()
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_bridge_creation() {
let config = BridgeConfig {
bridge_path: PathBuf::from("test.js"),
..Default::default()
};
let bridge = ClaudeBridge::new(config).await;
assert!(bridge.is_ok());
let bridge = bridge.unwrap();
assert!(bridge.init_time() < Duration::from_millis(500));
}
#[tokio::test]
async fn test_bridge_init_timeout() {
let config = BridgeConfig {
init_timeout: Duration::from_nanos(1), ..Default::default()
};
let bridge = ClaudeBridge::new(config).await;
assert!(bridge.is_ok() || bridge.is_err());
}
#[tokio::test]
async fn test_analyze_code() {
let config = BridgeConfig::default();
let bridge = ClaudeBridge::new(config).await.unwrap();
let result = bridge
.analyze_code("fn test() { if x { for y in z {} } }")
.await
.unwrap();
assert!(result.complexity > 0);
assert_eq!(result.satd_count, 0);
}
#[tokio::test]
async fn test_cache_integration() {
let config = BridgeConfig {
enable_cache: true,
..Default::default()
};
let bridge = ClaudeBridge::new(config).await.unwrap();
let result1 = bridge.analyze_code("test code").await.unwrap();
let result2 = bridge.analyze_code("test code").await.unwrap();
assert_eq!(result1.complexity, result2.complexity);
let stats = bridge.cache_stats();
assert!(stats.effective_hit_rate > 0.0);
}
#[tokio::test]
async fn test_pool_stats() {
let config = BridgeConfig::default();
let bridge = ClaudeBridge::new(config).await.unwrap();
let stats = bridge.pool_stats();
assert_eq!(stats.successes, 0);
assert_eq!(stats.failures, 0);
}
#[test]
fn test_complexity_estimation() {
let code = "fn test() { if x { for y { while z {} } } }";
let complexity = estimate_complexity(code);
assert!(complexity >= 3); }
#[test]
fn test_satd_detection() {
let code = "// TODO: fix this\n// FIXME: broken\nfn test() {}";
let count = count_satd(code);
assert_eq!(count, 2);
}
#[tokio::test]
async fn test_health_check() {
let config = BridgeConfig::default();
let bridge = ClaudeBridge::new(config).await.unwrap();
let healthy = bridge.health_check().await;
assert!(healthy);
}
}