Skip to main content

systemprompt_cli/commands/web/validate/
template_validation.rs

1use std::collections::HashSet;
2use std::fs;
3
4use systemprompt_models::content_config::ContentConfigRaw;
5
6use super::super::paths::WebPaths;
7use super::super::types::{TemplatesConfig, ValidationIssue};
8
9pub fn validate_templates(
10    profile: &systemprompt_models::Profile,
11    web_paths: &WebPaths,
12    errors: &mut Vec<ValidationIssue>,
13    warnings: &mut Vec<ValidationIssue>,
14) {
15    let templates_dir = &web_paths.templates;
16    let templates_yaml_path = templates_dir.join("templates.yaml");
17
18    if !templates_yaml_path.exists() {
19        warnings.push(ValidationIssue {
20            source: "templates".to_owned(),
21            message: format!(
22                "templates.yaml not found at {}",
23                templates_yaml_path.display()
24            ),
25            suggestion: Some("Create a templates.yaml file".to_owned()),
26        });
27        return;
28    }
29
30    let Ok(content) = fs::read_to_string(&templates_yaml_path) else {
31        errors.push(ValidationIssue {
32            source: "templates".to_owned(),
33            message: "Failed to read templates.yaml".to_owned(),
34            suggestion: None,
35        });
36        return;
37    };
38
39    let Ok(templates_config) = serde_yaml::from_str::<TemplatesConfig>(&content) else {
40        errors.push(ValidationIssue {
41            source: "templates".to_owned(),
42            message: "Failed to parse templates.yaml".to_owned(),
43            suggestion: Some("Check YAML syntax".to_owned()),
44        });
45        return;
46    };
47
48    for name in templates_config.templates.keys() {
49        let html_path = templates_dir.join(format!("{}.html", name));
50        if !html_path.exists() {
51            errors.push(ValidationIssue {
52                source: "templates".to_owned(),
53                message: format!("Missing HTML file for template '{}'", name),
54                suggestion: Some(format!("Create {}", html_path.display())),
55            });
56        }
57    }
58
59    let content_config_path = profile.paths.content_config();
60    let Ok(content) = fs::read_to_string(&content_config_path) else {
61        return;
62    };
63    let Ok(content_config) = serde_yaml::from_str::<ContentConfigRaw>(&content) else {
64        return;
65    };
66
67    let content_type_names: HashSet<&String> = content_config.content_sources.keys().collect();
68
69    for (template_name, entry) in &templates_config.templates {
70        for ct in &entry.content_types {
71            if !content_type_names.contains(ct) {
72                warnings.push(ValidationIssue {
73                    source: "templates".to_owned(),
74                    message: format!(
75                        "Template '{}' references unknown content type '{}'",
76                        template_name, ct
77                    ),
78                    suggestion: Some("Add the content type to content config".to_owned()),
79                });
80            }
81        }
82    }
83
84    let template_content_types: HashSet<&String> = templates_config
85        .templates
86        .values()
87        .flat_map(|e| e.content_types.iter())
88        .collect();
89
90    for name in content_type_names {
91        if !template_content_types.contains(name) {
92            warnings.push(ValidationIssue {
93                source: "templates".to_owned(),
94                message: format!("Content type '{}' has no associated template", name),
95                suggestion: Some("Link a template to this content type".to_owned()),
96            });
97        }
98    }
99}