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