Skip to main content

systemprompt_models/validators/
skills.rs

1use std::path::Path;
2
3use crate::{DiskSkillConfig, SKILL_CONFIG_FILENAME};
4use systemprompt_traits::validation_report::{ValidationError, ValidationReport};
5use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
6
7#[derive(Debug, Default)]
8pub struct SkillConfigValidator {
9    skills_path: Option<String>,
10}
11
12impl SkillConfigValidator {
13    pub fn new() -> Self {
14        Self::default()
15    }
16}
17
18impl DomainConfig for SkillConfigValidator {
19    fn domain_id(&self) -> &'static str {
20        "skills"
21    }
22
23    fn priority(&self) -> u32 {
24        25
25    }
26
27    fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
28        let skills_path = config
29            .get("skills_path")
30            .ok_or_else(|| DomainConfigError::NotFound("skills_path not configured".into()))?;
31
32        self.skills_path = Some(skills_path);
33        Ok(())
34    }
35
36    fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
37        let mut report = ValidationReport::new("skills");
38
39        let skills_path = self
40            .skills_path
41            .as_ref()
42            .ok_or_else(|| DomainConfigError::ValidationError("Skills path not set".into()))?;
43
44        let skills_dir = Path::new(skills_path);
45        if !skills_dir.exists() {
46            report.add_error(
47                ValidationError::new("skills_path", "Skills directory does not exist")
48                    .with_path(skills_dir)
49                    .with_suggestion("Create the skills directory or update skills_path in config"),
50            );
51            return Ok(report);
52        }
53
54        let entries = std::fs::read_dir(skills_dir).map_err(|e| {
55            DomainConfigError::LoadError(format!("Cannot read skills directory: {e}"))
56        })?;
57
58        for entry in entries {
59            let entry = entry.map_err(|e| {
60                DomainConfigError::LoadError(format!("Cannot read directory entry: {e}"))
61            })?;
62
63            if !entry.path().is_dir() {
64                continue;
65            }
66
67            let dir_name = entry.file_name().to_string_lossy().to_string();
68            let config_path = entry.path().join(SKILL_CONFIG_FILENAME);
69
70            if !config_path.exists() {
71                report.add_error(
72                    ValidationError::new(
73                        format!("skills.{dir_name}"),
74                        format!("Missing {SKILL_CONFIG_FILENAME}"),
75                    )
76                    .with_path(&config_path)
77                    .with_suggestion("Add a config.yaml with id, name, and description"),
78                );
79                continue;
80            }
81
82            let config_text = match std::fs::read_to_string(&config_path) {
83                Ok(text) => text,
84                Err(e) => {
85                    report.add_error(
86                        ValidationError::new(
87                            format!("skills.{dir_name}"),
88                            format!("Cannot read {SKILL_CONFIG_FILENAME}: {e}"),
89                        )
90                        .with_path(&config_path),
91                    );
92                    continue;
93                },
94            };
95
96            let config: DiskSkillConfig = match serde_yaml::from_str(&config_text) {
97                Ok(cfg) => cfg,
98                Err(e) => {
99                    report.add_error(
100                        ValidationError::new(
101                            format!("skills.{dir_name}"),
102                            format!("Invalid {SKILL_CONFIG_FILENAME}: {e}"),
103                        )
104                        .with_path(&config_path)
105                        .with_suggestion(
106                            "Ensure config.yaml has required fields: id, name, description",
107                        ),
108                    );
109                    continue;
110                },
111            };
112
113            let content_file = config.content_file();
114            let content_path = entry.path().join(content_file);
115            if !content_path.exists() {
116                report.add_error(
117                    ValidationError::new(
118                        format!("skills.{dir_name}.file"),
119                        format!("Content file '{content_file}' not found"),
120                    )
121                    .with_path(&content_path)
122                    .with_suggestion(
123                        "Create the content file or update the file field in config.yaml",
124                    ),
125                );
126            }
127        }
128
129        Ok(report)
130    }
131}