Skip to main content

fastskill_core/validation/
file_structure.rs

1//! File structure validation (SKILL.md existence/size/readable, path lists).
2
3use crate::core::service::ServiceError;
4use crate::core::skill_manager::SkillDefinition;
5use crate::validation::result::{ErrorSeverity, ValidationResult};
6use std::path::{Path, PathBuf};
7use tokio::fs;
8
9fn add_path_list_existence_warnings(
10    mut result: ValidationResult,
11    paths: Option<&[PathBuf]>,
12    field_label: &str,
13    missing_message: &str,
14) -> ValidationResult {
15    if let Some(list) = paths {
16        for (i, path) in list.iter().enumerate() {
17            if !path.exists() {
18                result = result.with_warning(
19                    &format!("{}[{}]", field_label, i),
20                    &format!("{} {}", missing_message, path.display()),
21                );
22            }
23        }
24    }
25    result
26}
27
28async fn check_skill_file_size(
29    path: &Path,
30    mut result: ValidationResult,
31    max_file_size_mb: usize,
32) -> Result<ValidationResult, ServiceError> {
33    let metadata = fs::metadata(path).await?;
34    let file_size_mb = metadata.len() / (1024 * 1024);
35    if file_size_mb > max_file_size_mb as u64 {
36        result = result.with_error(
37            "skill_file",
38            &format!(
39                "SKILL.md file is too large ({} MB, max: {} MB)",
40                file_size_mb, max_file_size_mb
41            ),
42            ErrorSeverity::Error,
43        );
44    }
45    Ok(result)
46}
47
48async fn check_skill_file_readable(
49    path: &Path,
50    mut result: ValidationResult,
51) -> Result<ValidationResult, ServiceError> {
52    if let Err(e) = fs::read_to_string(path).await {
53        result = result.with_error(
54            "skill_file",
55            &format!("Cannot read SKILL.md file: {}", e),
56            ErrorSeverity::Critical,
57        );
58    }
59    Ok(result)
60}
61
62pub(crate) async fn validate_file_structure(
63    skill: &SkillDefinition,
64    mut result: ValidationResult,
65    max_file_size_mb: usize,
66) -> Result<ValidationResult, ServiceError> {
67    if !skill.skill_file.exists() {
68        result = result.with_error(
69            "skill_file",
70            "SKILL.md file does not exist",
71            ErrorSeverity::Critical,
72        );
73    } else {
74        result = check_skill_file_size(&skill.skill_file, result, max_file_size_mb).await?;
75        result = check_skill_file_readable(&skill.skill_file, result).await?;
76    }
77
78    result = add_path_list_existence_warnings(
79        result,
80        skill.reference_files.as_deref(),
81        "reference_files",
82        "Reference file does not exist:",
83    );
84    result = add_path_list_existence_warnings(
85        result,
86        skill.script_files.as_deref(),
87        "script_files",
88        "Script file does not exist:",
89    );
90    result = add_path_list_existence_warnings(
91        result,
92        skill.asset_files.as_deref(),
93        "asset_files",
94        "Asset file does not exist:",
95    );
96
97    Ok(result)
98}