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