mockforge_core/overrides/
loader.rs

1//! Override loading functionality
2//!
3//! This module handles loading override rules from YAML files
4//! using glob patterns and environment variable configuration.
5
6use globwalk::GlobWalkerBuilder;
7use std::collections::HashMap;
8
9use super::models::{OverrideRule, Overrides, PatchOp};
10use crate::templating::expand_tokens as core_expand_tokens;
11
12impl Overrides {
13    /// Load overrides from glob patterns, with support for MOCKFORGE_HTTP_OVERRIDES_GLOB
14    pub async fn load_from_globs(patterns: &[&str]) -> anyhow::Result<Self> {
15        // Check for environment variable override
16        let patterns: Vec<String> =
17            if let Ok(env_patterns) = std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB") {
18                env_patterns.split(',').map(|s| s.trim().to_string()).collect()
19            } else {
20                patterns.iter().map(|s| s.to_string()).collect()
21            };
22
23        let mut rules = Vec::new();
24        let mut regex_cache = HashMap::new();
25
26        for pat in patterns {
27            // Check if the pattern is an absolute path to a specific file
28            if std::path::Path::new(&pat).is_absolute() && std::path::Path::new(&pat).is_file() {
29                // Handle absolute file path
30                let path = std::path::Path::new(&pat).to_path_buf();
31                if path.extension().map(|e| e == "yaml" || e == "yml").unwrap_or(false) {
32                    let text = tokio::fs::read_to_string(&path).await?;
33                    let mut file_rules: Vec<OverrideRule> = serde_yaml::from_str(&text)?;
34
35                    for r in file_rules.iter_mut() {
36                        // Pre-expand templating tokens in patch values
37                        for op in r.patch.iter_mut() {
38                            match op {
39                                PatchOp::Add { value, .. } | PatchOp::Replace { value, .. } => {
40                                    *value = core_expand_tokens(value);
41                                }
42                                _ => {}
43                            }
44                        }
45
46                        // Compile regex patterns for performance
47                        for target in &r.targets {
48                            if target.starts_with("regex:") || target.starts_with("path:") {
49                                let pattern = target
50                                    .strip_prefix("regex:")
51                                    .or_else(|| target.strip_prefix("path:"))
52                                    .unwrap();
53                                if !regex_cache.contains_key(pattern) {
54                                    let regex = regex::Regex::new(pattern)?;
55                                    regex_cache.insert(pattern.to_string(), regex);
56                                }
57                            }
58                        }
59                    }
60
61                    rules.extend(file_rules);
62                }
63            } else {
64                // Handle glob patterns
65                for entry in GlobWalkerBuilder::from_patterns(".", &[pat]).build()? {
66                    let entry = entry?;
67                    let path = entry.path().to_path_buf();
68                    if path.extension().map(|e| e == "yaml" || e == "yml").unwrap_or(false) {
69                        let text = tokio::fs::read_to_string(&path).await?;
70                        let mut file_rules: Vec<OverrideRule> = serde_yaml::from_str(&text)?;
71
72                        for r in file_rules.iter_mut() {
73                            // Pre-expand templating tokens in patch values
74                            for op in r.patch.iter_mut() {
75                                match op {
76                                    PatchOp::Add { value, .. } | PatchOp::Replace { value, .. } => {
77                                        *value = core_expand_tokens(value);
78                                    }
79                                    _ => {}
80                                }
81                            }
82
83                            // Compile regex patterns for performance
84                            for target in &r.targets {
85                                if target.starts_with("regex:") || target.starts_with("path:") {
86                                    let pattern = target
87                                        .strip_prefix("regex:")
88                                        .or_else(|| target.strip_prefix("path:"))
89                                        .unwrap();
90                                    if !regex_cache.contains_key(pattern) {
91                                        let regex = regex::Regex::new(pattern)?;
92                                        regex_cache.insert(pattern.to_string(), regex);
93                                    }
94                                }
95                            }
96                        }
97
98                        rules.extend(file_rules);
99                    }
100                }
101            }
102        }
103
104        Ok(Self { rules, regex_cache })
105    }
106}