use crate::common::truncate_text;
use crate::files::llm_output_extraction::xsd_validation::{XsdErrorType, XsdValidationError};
pub fn missing_required_error(
element_name: &str,
parent_element: &str,
example: Option<&str>,
) -> XsdValidationError {
XsdValidationError {
error_type: XsdErrorType::MissingRequiredElement,
element_path: format!("{parent_element}/{element_name}"),
expected: format!("<{element_name}> element (required)"),
found: format!("no <{element_name}> found"),
suggestion: format!(
"Add <{element_name}>value</{element_name}> inside <{parent_element}>."
),
example: example.map(std::convert::Into::into),
}
}
pub fn duplicate_element_error(element_name: &str, parent_element: &str) -> XsdValidationError {
XsdValidationError {
error_type: XsdErrorType::UnexpectedElement,
element_path: format!("{parent_element}/{element_name}"),
expected: format!("only one <{element_name}> element"),
found: format!("duplicate <{element_name}> element"),
suggestion: format!("Remove the duplicate <{element_name}>. Only one is allowed."),
example: None,
}
}
pub fn text_outside_tags_error(text: &str, parent_element: &str) -> XsdValidationError {
let display_text = truncate_text(text, 53);
XsdValidationError {
error_type: XsdErrorType::InvalidContent,
element_path: parent_element.to_string(),
expected: "only XML elements (no loose text)".to_string(),
found: format!("text content: {display_text:?}"),
suggestion: "Remove any text that is not inside a child element tag.".to_string(),
example: None,
}
}
pub fn format_content_preview(content: &str) -> String {
if content.is_empty() {
"empty content".to_string()
} else {
truncate_text(content, 63)
}
}
pub fn malformed_xml_error(error: &quick_xml::Error) -> XsdValidationError {
let error_str = error.to_string();
let suggestion =
if error_str.contains("invalid character") || error_str.contains("Invalid character") {
"Invalid character detected in XML content. Common causes:\n\
- Illegal control characters (NUL, etc.) in text\n\
- Invalid UTF-8 encoding\n\
- Remove or replace illegal characters with valid Unicode"
.to_string()
} else if error_str.contains("code") || error_str.contains("block") {
"Code blocks with special characters (<, >, &) MUST use CDATA sections:\n\
<code-block><![CDATA[\n\
if a < b && c > d { ... }\n\
]]></code-block>"
.to_string()
} else {
"Check that all XML tags are properly opened and closed. \
For code with special characters, use CDATA: <![CDATA[your code]]>"
.to_string()
};
XsdValidationError {
error_type: XsdErrorType::MalformedXml,
element_path: "xml".to_string(),
expected: "well-formed XML".to_string(),
found: format!("parse error: {error}"),
suggestion,
example: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_missing_required_error() {
let error = missing_required_error("child", "parent", Some("<child>example</child>"));
assert_eq!(error.element_path, "parent/child");
assert!(error.example.is_some());
}
#[test]
fn test_duplicate_element_error() {
let error = duplicate_element_error("child", "parent");
assert!(error.suggestion.contains("Remove"));
}
#[test]
fn test_text_outside_tags_error() {
let error = text_outside_tags_error("stray text", "parent");
assert!(error.found.contains("stray text"));
}
#[test]
fn test_text_outside_tags_error_truncates_long_text() {
let long_text = "x".repeat(100);
let error = text_outside_tags_error(&long_text, "parent");
assert!(error.found.contains("..."));
}
#[test]
fn test_malformed_xml_error_suggests_cdata_for_code() {
let error = quick_xml::Error::Syntax(quick_xml::errors::SyntaxError::UnclosedTag);
let result = malformed_xml_error(&error);
assert_eq!(result.error_type, XsdErrorType::MalformedXml);
}
}