File size: 4,295 Bytes
d90101d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | use std::path::Path;
use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use forge_app::GrpcInfra;
use forge_domain::{SyntaxError, ValidationRepository};
use tracing::{debug, warn};
use crate::proto_generated::forge_service_client::ForgeServiceClient;
use crate::proto_generated::{self, File, ValidateFilesRequest};
/// gRPC implementation of ValidationRepository
pub struct ForgeValidationRepository<I> {
infra: Arc<I>,
}
impl<I> ForgeValidationRepository<I> {
/// Create a new repository with the given infrastructure
///
/// # Arguments
/// * `infra` - Infrastructure that provides gRPC connection
pub fn new(infra: Arc<I>) -> Self {
Self { infra }
}
}
#[async_trait]
impl<I: GrpcInfra> ValidationRepository for ForgeValidationRepository<I> {
async fn validate_file(
&self,
path: impl AsRef<Path> + Send,
content: &str,
) -> Result<Vec<SyntaxError>> {
let path = path.as_ref();
let path_str = path.to_string_lossy().to_string();
debug!(path = %path_str, "Starting syntax validation");
// Create validation request for single file
let proto_file = File { path: path_str.clone(), content: content.to_string() };
let request = tonic::Request::new(ValidateFilesRequest { files: vec![proto_file] });
// Call gRPC API
let channel = self.infra.channel()?;
let mut client = ForgeServiceClient::new(channel);
let response = client
.validate_files(request)
.await
.context("Failed to call ValidateFiles gRPC")?
.into_inner();
// Extract validation result for our file
let result = response
.results
.into_iter()
.find(|r| r.file_path == path_str)
.context("Validation response missing file result")?;
// Convert proto status to error message
match result.status {
Some(proto_generated::ValidationStatus { status: Some(status) }) => match status {
proto_generated::validation_status::Status::Valid(_) => {
debug!(path = %path_str, "Syntax validation passed");
Ok(vec![])
}
proto_generated::validation_status::Status::Errors(error_list) => {
if error_list.errors.is_empty() {
return Ok(vec![]);
}
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("unknown");
let error_count = error_list.errors.len();
// Log and convert proto errors to domain errors
let errors = error_list
.errors
.into_iter()
.map(|error| {
warn!(
path = %path_str,
extension = ext,
error_count,
error_line = error.line,
error_column = error.column,
error_message = %error.message,
"Syntax validation failed"
);
SyntaxError {
line: error.line,
column: error.column,
message: error.message,
}
})
.collect();
Ok(errors)
}
proto_generated::validation_status::Status::UnsupportedLanguage(_) => {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("unknown");
debug!(
path = %path_str,
extension = ext,
"Syntax validation skipped: unsupported language"
);
Ok(vec![])
}
},
_ => Ok(vec![]),
}
}
}
|