pub mod capping;
pub mod completions;
pub mod diagnostics;
pub mod hover;
pub mod locations;
pub mod symbols;
pub mod workspace_diagnostics;
pub mod workspace_symbols;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::config::Config;
use crate::error::LspzError;
use crate::interceptors::completions::CompletionCompressor;
use crate::interceptors::diagnostics::DiagnosticsCompressor;
use crate::interceptors::hover::HoverCompressor;
use crate::interceptors::locations::LocationCompressor;
use crate::interceptors::symbols::DocumentSymbolCompressor;
use crate::interceptors::workspace_diagnostics::WorkspaceDiagnosticCompressor;
use crate::interceptors::workspace_symbols::WorkspaceSymbolCompressor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
ClientToServer,
ServerToClient,
}
#[async_trait::async_trait]
pub trait Interceptor: Send + Sync {
fn name(&self) -> &str;
fn applies_to(&self, method: &str, direction: Direction) -> bool;
async fn intercept(
&self,
method: &str,
params: serde_json::Value,
direction: Direction,
) -> Result<Option<serde_json::Value>, LspzError>;
}
pub struct InterceptorChain {
interceptors: Vec<Box<dyn Interceptor>>,
config: Arc<RwLock<Config>>,
}
impl InterceptorChain {
pub fn new(interceptors: Vec<Box<dyn Interceptor>>, config: Arc<RwLock<Config>>) -> Self {
Self {
interceptors,
config,
}
}
pub async fn process(
&self,
method: &str,
params: serde_json::Value,
direction: Direction,
) -> Result<Option<serde_json::Value>, LspzError> {
let config = self.config.read().await;
let mut params = Some(params);
for interceptor in &self.interceptors {
if interceptor.applies_to(method, direction)
&& config.is_interceptor_enabled(interceptor.name())
&& let Some(p) = params.take()
{
let original = Some(p.clone());
match interceptor.intercept(method, p, direction).await {
Ok(Some(new_params)) => params = Some(new_params),
Ok(None) => return Ok(None),
Err(e) => {
tracing::warn!(interceptor = %interceptor.name(), error = %e, "Interceptor failed, forwarding original");
return Ok(original);
}
}
}
}
Ok(params)
}
}
pub fn default_interceptors() -> Vec<Box<dyn Interceptor>> {
vec![
Box::new(DiagnosticsCompressor::default()),
Box::new(CompletionCompressor::default()),
Box::new(HoverCompressor::default()),
Box::new(DocumentSymbolCompressor),
Box::new(LocationCompressor),
Box::new(WorkspaceSymbolCompressor),
Box::new(WorkspaceDiagnosticCompressor),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use serde_json::json;
use std::sync::Arc;
use tokio::sync::RwLock;
struct AlwaysFailInterceptor;
#[async_trait::async_trait]
impl Interceptor for AlwaysFailInterceptor {
fn name(&self) -> &str {
"always_fail"
}
fn applies_to(&self, method: &str, _direction: Direction) -> bool {
!method.is_empty()
}
async fn intercept(
&self,
_method: &str,
_params: serde_json::Value,
_direction: Direction,
) -> Result<Option<serde_json::Value>, LspzError> {
Err(LspzError::Protocol("simulated failure".into()))
}
}
fn make_chain(interceptors: Vec<Box<dyn Interceptor>>) -> InterceptorChain {
InterceptorChain::new(interceptors, Arc::new(RwLock::new(Config::default())))
}
#[tokio::test]
async fn test_fail_open_returns_original() {
let chain = make_chain(vec![Box::new(AlwaysFailInterceptor)]);
let params = json!({"key": "value"});
let result = chain
.process("someMethod", params.clone(), Direction::ServerToClient)
.await
.expect("fail-open should not propagate error");
assert_eq!(
result,
Some(params),
"fail-open should return original params"
);
}
#[tokio::test]
async fn test_empty_chain_passthrough() {
let chain = make_chain(vec![]);
let params = json!({"key": "value"});
let result = chain
.process("someMethod", params.clone(), Direction::ServerToClient)
.await
.expect("empty chain should succeed");
assert_eq!(result, Some(params));
}
}