systemprompt_api/services/static_content/
config.rs1use anyhow::Result;
2use systemprompt_models::ContentConfigRaw;
3
4#[derive(Debug, Clone)]
5pub struct StaticContentMatcher {
6 patterns: Vec<(String, String)>,
7}
8
9impl StaticContentMatcher {
10 pub fn from_config(config_path: &str) -> Result<Self> {
11 let yaml_content = std::fs::read_to_string(config_path)?;
12 let config: ContentConfigRaw = serde_yaml::from_str(&yaml_content)?;
13
14 let patterns = config
15 .content_sources
16 .into_iter()
17 .filter(|(_, source)| source.enabled)
18 .filter_map(|(source_id, source)| {
19 source
20 .sitemap
21 .filter(|s| s.enabled)
22 .map(|sitemap| (sitemap.url_pattern, source_id))
23 })
24 .collect();
25
26 Ok(Self { patterns })
27 }
28
29 pub const fn empty() -> Self {
30 Self {
31 patterns: Vec::new(),
32 }
33 }
34
35 pub fn matches(&self, path: &str) -> Option<(String, String)> {
36 self.patterns.iter().find_map(|(pattern, source_id)| {
37 extract_slug(path, pattern).map(|slug| (slug, source_id.clone()))
38 })
39 }
40}
41
42fn extract_slug(path: &str, pattern: &str) -> Option<String> {
43 let pattern_parts: Vec<&str> = pattern.split('{').collect();
44 if pattern_parts.len() != 2 {
45 return None;
46 }
47
48 let prefix = pattern_parts[0];
49 if !path.starts_with(prefix) {
50 return None;
51 }
52
53 let slug = path.trim_start_matches(prefix).trim_end_matches('/');
54 (!slug.is_empty()).then(|| slug.to_string())
55}