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(|| DomainConfigError::LoadError {
51 message: "Expected ValidationConfigProvider with pre-loaded configs".into(),
52 })?;
53
54 self.loaded = provider
55 .content_config()
56 .cloned()
57 .map(|config| LoadedContentConfig {
58 config,
59 services_path: PathBuf::from(&provider.config().services_path),
60 });
61 Ok(())
62 }
63
64 fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
65 let mut report = ValidationReport::new("content");
66
67 let Some(loaded) = self.loaded.as_ref() else {
68 return Ok(report);
69 };
70
71 for (name, source) in &loaded.config.content_sources {
72 let source_path = loaded.resolve_path(&source.path);
73 if !source_path.exists() {
74 report.add_error(
75 ValidationError::new(
76 format!("content_sources.{}", name),
77 "Content source directory does not exist",
78 )
79 .with_path(source_path)
80 .with_suggestion("Create the directory or remove the source"),
81 );
82 }
83
84 if source.source_id.as_str().is_empty() {
85 report.add_error(ValidationError::new(
86 format!("content_sources.{}.source_id", name),
87 "Source ID cannot be empty",
88 ));
89 }
90
91 if source.category_id.as_str().is_empty() {
92 report.add_error(ValidationError::new(
93 format!("content_sources.{}.category_id", name),
94 "Category ID cannot be empty",
95 ));
96 }
97 }
98
99 for (name, source) in &loaded.config.content_sources {
100 if !loaded
101 .config
102 .categories
103 .contains_key(source.category_id.as_str())
104 {
105 report.add_error(
106 ValidationError::new(
107 format!("content_sources.{}.category_id", name),
108 format!(
109 "Referenced category '{}' not found in categories",
110 source.category_id
111 ),
112 )
113 .with_suggestion("Add the category to the categories section"),
114 );
115 }
116 }
117
118 Ok(report)
119 }
120}