systemprompt_cli/commands/web/validate/
template_validation.rs1use 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}