fastskill_core/validation/
file_structure.rs1use 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}