systemprompt_models/validators/
content.rs1use super::ValidationConfigProvider;
7use crate::ContentConfigRaw;
8use std::path::{Path, PathBuf};
9use systemprompt_traits::validation_report::{ValidationError, ValidationReport};
10use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
11
12#[derive(Debug)]
13struct LoadedContentConfig {
14 config: ContentConfigRaw,
15 services_path: PathBuf,
16}
17
18impl LoadedContentConfig {
19 fn resolve_path(&self, path: &str) -> PathBuf {
20 let path = Path::new(path);
21 if path.is_absolute() {
22 path.to_path_buf()
23 } else {
24 self.services_path.join(path)
25 }
26 }
27}
28
29#[derive(Debug, Default)]
30pub struct ContentConfigValidator {
31 loaded: Option<LoadedContentConfig>,
32}
33
34impl ContentConfigValidator {
35 pub fn new() -> Self {
36 Self::default()
37 }
38}
39
40impl DomainConfig for ContentConfigValidator {
41 fn domain_id(&self) -> &'static str {
42 "content"
43 }
44
45 fn priority(&self) -> u32 {
46 20
47 }
48
49 fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
50 let provider = config
51 .as_any()
52 .downcast_ref::<ValidationConfigProvider>()
53 .ok_or_else(|| DomainConfigError::LoadError {
54 message: "Expected ValidationConfigProvider with pre-loaded configs".into(),
55 })?;
56
57 self.loaded = provider
58 .content_config()
59 .cloned()
60 .map(|config| LoadedContentConfig {
61 config,
62 services_path: PathBuf::from(&provider.config().services_path),
63 });
64 Ok(())
65 }
66
67 fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
68 let mut report = ValidationReport::new("content");
69
70 let Some(loaded) = self.loaded.as_ref() else {
71 return Ok(report);
72 };
73
74 for (name, source) in &loaded.config.content_sources {
75 let source_path = loaded.resolve_path(&source.path);
76 if !source_path.exists() {
77 report.add_error(
78 ValidationError::new(
79 format!("content_sources.{}", name),
80 "Content source directory does not exist",
81 )
82 .with_path(source_path)
83 .with_suggestion("Create the directory or remove the source"),
84 );
85 }
86
87 if source.source_id.as_str().is_empty() {
88 report.add_error(ValidationError::new(
89 format!("content_sources.{}.source_id", name),
90 "Source ID cannot be empty",
91 ));
92 }
93
94 if source.category_id.as_str().is_empty() {
95 report.add_error(ValidationError::new(
96 format!("content_sources.{}.category_id", name),
97 "Category ID cannot be empty",
98 ));
99 }
100 }
101
102 for (name, source) in &loaded.config.content_sources {
103 if !loaded
104 .config
105 .categories
106 .contains_key(source.category_id.as_str())
107 {
108 report.add_error(
109 ValidationError::new(
110 format!("content_sources.{}.category_id", name),
111 format!(
112 "Referenced category '{}' not found in categories",
113 source.category_id
114 ),
115 )
116 .with_suggestion("Add the category to the categories section"),
117 );
118 }
119 }
120
121 Ok(report)
122 }
123}