Skip to main content

systemprompt_models/validators/
content.rs

1//! Content configuration validator.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use 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}