systemprompt_files/config/
validator.rs1use systemprompt_config::ProfileBootstrap;
7use systemprompt_models::AppPaths;
8use systemprompt_traits::validation_report::{
9 ValidationError, ValidationReport, ValidationWarning,
10};
11use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
12
13use super::FilesConfig;
14use super::types::FilesConfigYaml;
15
16const MAX_RECOMMENDED_FILE_SIZE: u64 = 2 * 1024 * 1024 * 1024;
17const MIN_VIDEO_FILE_SIZE: u64 = 100 * 1024 * 1024;
18
19fn is_valid_header_value(value: &str) -> bool {
20 !value.is_empty() && value.chars().all(|c| matches!(c, ' '..='~'))
21}
22
23#[derive(Debug, Default)]
24pub struct FilesConfigValidator {
25 config: Option<FilesConfigYaml>,
26}
27
28impl FilesConfigValidator {
29 pub fn new() -> Self {
30 Self::default()
31 }
32}
33
34impl DomainConfig for FilesConfigValidator {
35 fn domain_id(&self) -> &'static str {
36 "files"
37 }
38
39 fn priority(&self) -> u32 {
40 10
41 }
42
43 fn load(&mut self, _config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
44 let profile = ProfileBootstrap::get().map_err(|e| DomainConfigError::LoadError {
45 message: e.to_string(),
46 })?;
47 let paths =
48 AppPaths::from_profile(&profile.paths, profile.path_resolution()).map_err(|e| {
49 DomainConfigError::LoadError {
50 message: e.to_string(),
51 }
52 })?;
53 let yaml_config =
54 FilesConfig::load_yaml_config(&paths).map_err(|e| DomainConfigError::LoadError {
55 message: e.to_string(),
56 })?;
57 self.config = Some(yaml_config);
58 Ok(())
59 }
60
61 fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
62 let mut report = ValidationReport::new("files");
63
64 let config = self
65 .config
66 .as_ref()
67 .ok_or_else(|| DomainConfigError::ValidationError {
68 message: "Not loaded".into(),
69 })?;
70
71 if !config.url_prefix.starts_with('/') {
72 report.add_error(ValidationError::new(
73 "files.urlPrefix",
74 "URL prefix must start with '/'",
75 ));
76 }
77
78 if let Some(cache_control) = config.cache_control.as_deref()
79 && !is_valid_header_value(cache_control)
80 {
81 report.add_error(ValidationError::new(
82 "files.cacheControl",
83 "Cache-Control must be a non-empty string of printable ASCII characters",
84 ));
85 }
86
87 if config.upload.max_file_size_bytes > MAX_RECOMMENDED_FILE_SIZE {
88 report.add_warning(
89 ValidationWarning::new(
90 "files.upload.maxFileSizeBytes",
91 "Max file size > 2GB may cause memory issues",
92 )
93 .with_suggestion("Consider using a smaller max file size for better performance"),
94 );
95 }
96
97 if config.upload.allowed_types.video
98 && config.upload.max_file_size_bytes < MIN_VIDEO_FILE_SIZE
99 {
100 report.add_warning(
101 ValidationWarning::new(
102 "files.upload.allowedTypes.video",
103 "Video uploads enabled but max file size < 100MB",
104 )
105 .with_suggestion("Increase maxFileSizeBytes to at least 100MB for video uploads"),
106 );
107 }
108
109 Ok(report)
110 }
111}