use crate::incident::{Incident, Location};
use std::fmt;
#[derive(Debug)]
pub struct ProviderError {
pub message: String,
}
impl fmt::Display for ProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ProviderError {}
impl ProviderError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl From<anyhow::Error> for ProviderError {
fn from(err: anyhow::Error) -> Self {
Self {
message: err.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct CapabilityInfo {
pub name: String,
}
#[derive(Debug)]
pub struct EvaluateResult {
pub matched: bool,
pub incidents: Vec<Incident>,
}
pub trait Provider: Send + Sync + 'static {
fn capabilities(&self) -> Vec<CapabilityInfo>;
fn init(&self, location: &str) -> Result<(), ProviderError>;
fn evaluate(&self, capability: &str, condition: &str) -> Result<EvaluateResult, ProviderError>;
fn stop(&self) {
}
fn get_code_snip(
&self,
_uri: &str,
_location: &Location,
_context_lines: usize,
) -> Result<String, ProviderError> {
Ok(String::new())
}
}
use super::proto;
use super::proto::provider_code_location_service_server::ProviderCodeLocationService;
use super::proto::provider_service_server::ProviderService;
use std::pin::Pin;
use std::sync::Arc;
use tokio_stream::Stream;
use tonic::{Request, Response, Status};
type ProgressStream = Pin<Box<dyn Stream<Item = Result<proto::ProgressEvent, Status>> + Send>>;
pub struct ProviderAdapter<T: Provider> {
inner: Arc<T>,
}
impl<T: Provider> ProviderAdapter<T> {
pub fn new(inner: Arc<T>) -> Self {
Self { inner }
}
}
#[tonic::async_trait]
impl<T: Provider> ProviderService for ProviderAdapter<T> {
async fn capabilities(
&self,
_request: Request<()>,
) -> Result<Response<proto::CapabilitiesResponse>, Status> {
let caps = self.inner.capabilities();
let capabilities = caps
.into_iter()
.map(|c| proto::Capability {
name: c.name,
template_context: None,
})
.collect();
Ok(Response::new(proto::CapabilitiesResponse { capabilities }))
}
async fn init(
&self,
request: Request<proto::Config>,
) -> Result<Response<proto::InitResponse>, Status> {
let config = request.into_inner();
let location = config.location.clone();
tracing::info!("Initializing provider with location: {}", location);
match self.inner.init(&location) {
Ok(()) => Ok(Response::new(proto::InitResponse {
error: String::new(),
successful: true,
id: 1,
builtin_config: None,
})),
Err(e) => Ok(Response::new(proto::InitResponse {
error: e.to_string(),
successful: false,
id: 0,
builtin_config: None,
})),
}
}
async fn evaluate(
&self,
request: Request<proto::EvaluateRequest>,
) -> Result<Response<proto::EvaluateResponse>, Status> {
let req = request.into_inner();
tracing::info!(
"Evaluate request: cap={}, condition_info={}",
&req.cap,
&req.condition_info
);
match self.inner.evaluate(&req.cap, &req.condition_info) {
Ok(result) => {
let incident_contexts: Vec<proto::IncidentContext> = result
.incidents
.iter()
.map(proto::IncidentContext::from)
.collect();
Ok(Response::new(proto::EvaluateResponse {
error: String::new(),
successful: true,
response: Some(proto::ProviderEvaluateResponse {
matched: result.matched,
incident_contexts,
template_context: None,
}),
}))
}
Err(e) => Ok(Response::new(proto::EvaluateResponse {
error: e.to_string(),
successful: false,
response: None,
})),
}
}
async fn stop(&self, _request: Request<proto::ServiceRequest>) -> Result<Response<()>, Status> {
tracing::info!("Provider stopping");
self.inner.stop();
Ok(Response::new(()))
}
async fn get_dependencies(
&self,
_request: Request<proto::ServiceRequest>,
) -> Result<Response<proto::DependencyResponse>, Status> {
Ok(Response::new(proto::DependencyResponse {
successful: true,
error: String::new(),
file_dep: vec![],
}))
}
async fn get_dependencies_dag(
&self,
_request: Request<proto::ServiceRequest>,
) -> Result<Response<proto::DependencyDagResponse>, Status> {
Ok(Response::new(proto::DependencyDagResponse {
successful: true,
error: String::new(),
file_dag_dep: vec![],
}))
}
async fn notify_file_changes(
&self,
_request: Request<proto::NotifyFileChangesRequest>,
) -> Result<Response<proto::NotifyFileChangesResponse>, Status> {
Ok(Response::new(proto::NotifyFileChangesResponse {
error: String::new(),
}))
}
async fn prepare(
&self,
_request: Request<proto::PrepareRequest>,
) -> Result<Response<proto::PrepareResponse>, Status> {
Ok(Response::new(proto::PrepareResponse {
error: String::new(),
}))
}
type StreamPrepareProgressStream = ProgressStream;
async fn stream_prepare_progress(
&self,
_request: Request<proto::PrepareProgressRequest>,
) -> Result<Response<Self::StreamPrepareProgressStream>, Status> {
let stream = async_stream::stream! {
yield Ok(proto::ProgressEvent {
r#type: 0,
provider_name: "provider".into(),
files_processed: 0,
total_files: 0,
});
};
Ok(Response::new(Box::pin(stream)))
}
}
#[tonic::async_trait]
impl<T: Provider> ProviderCodeLocationService for ProviderAdapter<T> {
async fn get_code_snip(
&self,
request: Request<proto::GetCodeSnipRequest>,
) -> Result<Response<proto::GetCodeSnipResponse>, Status> {
let req = request.into_inner();
let code_location = req
.code_location
.ok_or_else(|| Status::invalid_argument("no code location sent"))?;
let start_position = code_location
.start_position
.ok_or_else(|| Status::invalid_argument("no start position sent"))?;
let end_position = code_location
.end_position
.ok_or_else(|| Status::invalid_argument("no end position sent"))?;
let location = crate::incident::Location {
start: crate::incident::Position {
line: start_position.line as u32,
character: start_position.character as u32,
},
end: crate::incident::Position {
line: end_position.line as u32,
character: end_position.character as u32,
},
};
match self.inner.get_code_snip(&req.uri, &location, 3) {
Ok(snip) => Ok(Response::new(proto::GetCodeSnipResponse { snip })),
Err(e) => Err(Status::internal(e.to_string())),
}
}
}