use std::collections::HashSet;
#[path = "syntax_validation.rs"]
mod syntax_validation;
#[path = "template_extraction.rs"]
mod template_extraction;
#[path = "template_types.rs"]
mod template_types;
pub use syntax_validation::validate_syntax;
pub use template_extraction::{extract_metadata, extract_partials, extract_variables};
pub use template_types::{
RenderedPromptError, RenderedTemplate, SubstitutionEntry, SubstitutionLog, SubstitutionSource,
TemplateMetadata, TemplateVariablesInvalidError, ValidationError, ValidationResult,
ValidationWarning, VariableInfo,
};
#[must_use]
pub fn validate_template<S: std::hash::BuildHasher>(
content: &str,
available_partials: &HashSet<String, S>,
) -> ValidationResult {
let syntax_errors = validate_syntax(content);
let has_syntax_errors = !syntax_errors.is_empty();
let variables = extract_variables(content);
let partials = extract_partials(content);
let (missing_partials, valid_partials): (Vec<_>, Vec<_>) = partials
.iter()
.partition(|partial| !available_partials.contains(*partial));
let partial_errors: Vec<ValidationError> = missing_partials
.into_iter()
.map(|partial| ValidationError::PartialNotFound {
name: (*partial).clone(),
})
.collect();
let valid_partials: Vec<String> = valid_partials.into_iter().map(|s| (*s).clone()).collect();
let warnings: Vec<ValidationWarning> = variables
.iter()
.filter(|var| !var.has_default)
.map(|var| ValidationWarning::VariableMayError {
name: var.name.clone(),
})
.collect();
let is_valid = !has_syntax_errors && partial_errors.is_empty();
ValidationResult {
is_valid,
variables,
partials: valid_partials,
errors: syntax_errors.into_iter().chain(partial_errors).collect(),
warnings,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_template_complete() {
let content = "Hello {{NAME|default=\"Guest\"}}";
let partials = HashSet::new();
let result = validate_template(content, &partials);
assert!(result.is_valid);
assert_eq!(result.variables.len(), 1);
assert!(result.errors.is_empty());
}
#[test]
fn test_validate_template_with_missing_partial() {
let content = "{{> missing_partial}}";
let partials = HashSet::new();
let result = validate_template(content, &partials);
assert!(!result.is_valid);
assert!(!result.errors.is_empty());
}
#[test]
fn test_validate_template_ignores_partials_inside_comments() {
let content = "{# {{> commented_partial}} #}\nHello {{NAME|default=\"Guest\"}}";
let partials = HashSet::new();
let result = validate_template(content, &partials);
assert!(
result.is_valid,
"commented-out partials should not trigger validation errors"
);
assert!(
result.errors.is_empty(),
"no PartialNotFound errors should be emitted for comment content"
);
assert!(
result.partials.is_empty(),
"commented partials should not be extracted"
);
}
}