systemprompt-cli 0.2.2

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use std::collections::HashSet;
use std::fs;

use systemprompt_models::content_config::ContentConfigRaw;

use super::super::paths::WebPaths;
use super::super::types::{TemplatesConfig, ValidationIssue};

pub fn validate_templates(
    profile: &systemprompt_models::Profile,
    web_paths: &WebPaths,
    errors: &mut Vec<ValidationIssue>,
    warnings: &mut Vec<ValidationIssue>,
) {
    let templates_dir = &web_paths.templates;
    let templates_yaml_path = templates_dir.join("templates.yaml");

    if !templates_yaml_path.exists() {
        warnings.push(ValidationIssue {
            source: "templates".to_string(),
            message: format!(
                "templates.yaml not found at {}",
                templates_yaml_path.display()
            ),
            suggestion: Some("Create a templates.yaml file".to_string()),
        });
        return;
    }

    let Ok(content) = fs::read_to_string(&templates_yaml_path) else {
        errors.push(ValidationIssue {
            source: "templates".to_string(),
            message: "Failed to read templates.yaml".to_string(),
            suggestion: None,
        });
        return;
    };

    let Ok(templates_config) = serde_yaml::from_str::<TemplatesConfig>(&content) else {
        errors.push(ValidationIssue {
            source: "templates".to_string(),
            message: "Failed to parse templates.yaml".to_string(),
            suggestion: Some("Check YAML syntax".to_string()),
        });
        return;
    };

    for name in templates_config.templates.keys() {
        let html_path = templates_dir.join(format!("{}.html", name));
        if !html_path.exists() {
            errors.push(ValidationIssue {
                source: "templates".to_string(),
                message: format!("Missing HTML file for template '{}'", name),
                suggestion: Some(format!("Create {}", html_path.display())),
            });
        }
    }

    let content_config_path = profile.paths.content_config();
    let Ok(content) = fs::read_to_string(&content_config_path) else {
        return;
    };
    let Ok(content_config) = serde_yaml::from_str::<ContentConfigRaw>(&content) else {
        return;
    };

    let content_type_names: HashSet<&String> = content_config.content_sources.keys().collect();

    for (template_name, entry) in &templates_config.templates {
        for ct in &entry.content_types {
            if !content_type_names.contains(ct) {
                warnings.push(ValidationIssue {
                    source: "templates".to_string(),
                    message: format!(
                        "Template '{}' references unknown content type '{}'",
                        template_name, ct
                    ),
                    suggestion: Some("Add the content type to content config".to_string()),
                });
            }
        }
    }

    let template_content_types: HashSet<&String> = templates_config
        .templates
        .values()
        .flat_map(|e| e.content_types.iter())
        .collect();

    for name in content_type_names {
        if !template_content_types.contains(name) {
            warnings.push(ValidationIssue {
                source: "templates".to_string(),
                message: format!("Content type '{}' has no associated template", name),
                suggestion: Some("Link a template to this content type".to_string()),
            });
        }
    }
}