Skip to main content

rippy_cli/config/
string_loader.rs

1//! In-memory config loading for embedders and fuzz targets.
2//!
3//! `Config::load_from_str` parses a config string directly without touching
4//! the filesystem. Unlike `Config::load`, it does NOT consult stdlib rules,
5//! the home directory, or project discovery — it parses exactly the given
6//! string and returns the resulting `Config`.
7
8use std::path::Path;
9
10use super::Config;
11use super::loader::load_file_from_content;
12use crate::error::RippyError;
13
14/// Format hint for `Config::load_from_str`.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ConfigFormat {
17    /// Parse as TOML (`.rippy.toml` / `config.toml`).
18    Toml,
19    /// Parse as legacy line-based syntax (`.rippy` / `~/.rippy/config`).
20    Lines,
21}
22
23impl Config {
24    /// Parse a config string directly into a `Config`, without touching the filesystem.
25    ///
26    /// # Errors
27    ///
28    /// Returns `RippyError::Config` if `content` contains invalid syntax.
29    /// The error's `path` field is set to the sentinel `<memory>` so callers
30    /// can distinguish in-memory parses from file-based ones.
31    pub fn load_from_str(content: &str, format: ConfigFormat) -> Result<Self, RippyError> {
32        let sentinel: &Path = Path::new("<memory>");
33        let mut directives = Vec::new();
34        match format {
35            ConfigFormat::Toml => {
36                directives.extend(crate::toml_config::parse_toml_config(content, sentinel)?);
37            }
38            ConfigFormat::Lines => {
39                // Reuse the file-based loader's line parser. The sentinel path
40                // has no `.toml` extension, so the loader routes it through the
41                // line-based branch.
42                load_file_from_content(content, sentinel, &mut directives)?;
43            }
44        }
45        Ok(Self::from_directives(directives))
46    }
47}
48
49#[cfg(test)]
50#[allow(clippy::unwrap_used, clippy::panic)]
51mod tests {
52    use std::path::PathBuf;
53
54    use super::*;
55    use crate::verdict::Decision;
56
57    #[test]
58    fn load_from_str_toml_minimal() {
59        let cfg = Config::load_from_str(
60            "[[rules]]\naction = \"allow\"\npattern = \"git status\"\n",
61            ConfigFormat::Toml,
62        )
63        .unwrap();
64        let v = cfg.match_command("git status", None).unwrap();
65        assert_eq!(v.decision, Decision::Allow);
66    }
67
68    #[test]
69    fn load_from_str_lines_minimal() {
70        let cfg = Config::load_from_str("allow git status\n", ConfigFormat::Lines).unwrap();
71        let v = cfg.match_command("git status", None).unwrap();
72        assert_eq!(v.decision, Decision::Allow);
73    }
74
75    #[test]
76    fn load_from_str_toml_error_has_memory_path() {
77        let err = Config::load_from_str("this is not toml [[", ConfigFormat::Toml).unwrap_err();
78        match err {
79            RippyError::Config { path, .. } => {
80                assert_eq!(path, PathBuf::from("<memory>"));
81            }
82            other => panic!("wrong error variant: {other:?}"),
83        }
84    }
85
86    #[test]
87    fn load_from_str_lines_skips_comments_and_blanks() {
88        let cfg = Config::load_from_str(
89            "# a comment\n\nallow ls\n# another\nallow pwd\n",
90            ConfigFormat::Lines,
91        )
92        .unwrap();
93        assert!(cfg.match_command("ls", None).is_some());
94        assert!(cfg.match_command("pwd", None).is_some());
95    }
96
97    #[test]
98    fn load_from_str_lines_error_reports_line_number() {
99        let err = Config::load_from_str(
100            "# header comment\nallow ls\nbogus directive here\n",
101            ConfigFormat::Lines,
102        )
103        .unwrap_err();
104        match err {
105            RippyError::Config { path, line, .. } => {
106                assert_eq!(path, PathBuf::from("<memory>"));
107                assert_eq!(line, 3);
108            }
109            other => panic!("wrong error variant: {other:?}"),
110        }
111    }
112}