use super::types::ValidationResult;
use txtx_addon_kit::types::diagnostics::Diagnostic;
use std::ops::Range;
#[derive(Debug, Clone)]
pub struct HclDiagnostic {
pub message: String,
pub severity: DiagnosticSeverity,
pub span: Option<Range<usize>>,
pub hint: Option<String>,
pub source: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Error,
Warning,
Information,
Hint,
}
pub fn extract_hcl_diagnostics(error_str: &str, _source: &str) -> Vec<HclDiagnostic> {
let mut diagnostics = Vec::new();
let diagnostic = HclDiagnostic {
message: error_str.to_string(),
severity: DiagnosticSeverity::Error,
span: extract_span_from_error_str(error_str),
hint: extract_hint_from_error_str(error_str),
source: "hcl-parser".to_string(),
};
diagnostics.push(diagnostic);
diagnostics
}
pub fn parse_with_diagnostics(
content: &str,
_file_path: &str,
) -> (Result<txtx_addon_kit::hcl::structure::Body, String>, Vec<HclDiagnostic>) {
use std::str::FromStr;
let mut diagnostics = Vec::new();
let result = txtx_addon_kit::hcl::structure::Body::from_str(content).map_err(|e| {
let error_str = e.to_string();
diagnostics.extend(extract_hcl_diagnostics(&error_str, content));
format!("Failed to parse runbook: {}", error_str)
});
(result, diagnostics)
}
pub fn validate_with_diagnostics(
content: &str,
file_path: &str,
) -> (ValidationResult, Vec<HclDiagnostic>) {
let mut result = ValidationResult::new();
let mut hcl_diagnostics = Vec::new();
let (parse_result, parse_diagnostics) = parse_with_diagnostics(content, file_path);
hcl_diagnostics.extend(parse_diagnostics);
match parse_result {
Ok(_body) => {
if let Err(e) = super::hcl_validator::validate_with_hcl(content, &mut result, file_path)
{
let diagnostic = HclDiagnostic {
message: e,
severity: DiagnosticSeverity::Error,
span: None,
hint: None,
source: "hcl-validator".to_string(),
};
hcl_diagnostics.push(diagnostic);
}
}
Err(e) => {
let error = Diagnostic::error(e.clone())
.with_file(file_path.to_string())
.with_line(0)
.with_column(0);
result.errors.push(error);
}
}
(result, hcl_diagnostics)
}
fn extract_span_from_error_str(_error_str: &str) -> Option<Range<usize>> {
None
}
fn extract_hint_from_error_str(_error_str: &str) -> Option<String> {
None
}
pub fn position_to_offset(source: &str, line: usize, column: usize) -> Option<usize> {
let mut current_line = 1;
let mut current_column = 1;
for (offset, ch) in source.char_indices() {
if current_line == line && current_column == column {
return Some(offset);
}
if ch == '\n' {
current_line += 1;
current_column = 1;
} else {
current_column += 1;
}
}
None
}
pub fn offset_to_position(source: &str, offset: usize) -> (usize, usize) {
let mut line = 1;
let mut column = 1;
for (idx, ch) in source.char_indices() {
if idx >= offset {
break;
}
if ch == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
(line, column)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_conversions() {
let source = "line1\nline2\nline3";
assert_eq!(position_to_offset(source, 1, 1), Some(0));
assert_eq!(position_to_offset(source, 2, 1), Some(6));
assert_eq!(position_to_offset(source, 3, 1), Some(12));
assert_eq!(offset_to_position(source, 0), (1, 1));
assert_eq!(offset_to_position(source, 6), (2, 1));
assert_eq!(offset_to_position(source, 12), (3, 1));
}
#[test]
fn test_diagnostic_severity() {
assert_ne!(DiagnosticSeverity::Error as u8, DiagnosticSeverity::Warning as u8);
assert_ne!(DiagnosticSeverity::Warning as u8, DiagnosticSeverity::Information as u8);
assert_ne!(DiagnosticSeverity::Information as u8, DiagnosticSeverity::Hint as u8);
}
#[test]
fn test_extract_hcl_diagnostics() {
let error_str = "Parse error: unexpected token";
let diagnostics = extract_hcl_diagnostics(error_str, "test content");
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].message, error_str);
assert_eq!(diagnostics[0].severity, DiagnosticSeverity::Error);
assert_eq!(diagnostics[0].source, "hcl-parser");
}
#[test]
fn test_validation_result_integration() {
let mut result = ValidationResult::new();
assert!(result.errors.is_empty());
result.errors.push(
Diagnostic::error("Test error")
.with_file("test.tx")
.with_line(1)
.with_column(1)
);
assert_eq!(result.errors.len(), 1);
}
}