Skip to main content

rippy_cli/config/
types.rs

1use crate::condition::Condition;
2use crate::pattern::Pattern;
3use crate::verdict::Decision;
4
5use std::path::PathBuf;
6
7/// What kind of entity a rule targets.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum RuleTarget {
10    Command,
11    Redirect,
12    Mcp,
13    FileRead,
14    FileWrite,
15    FileEdit,
16    After,
17}
18
19/// A single rule: target + decision + pattern + optional message + conditions.
20///
21/// Rules can use glob-pattern matching (the `pattern` field), structured matching
22/// (command/subcommand/flags fields), or both. When both are present, all must match (AND).
23#[derive(Debug, Clone)]
24pub struct Rule {
25    pub target: RuleTarget,
26    pub decision: Decision,
27    pub pattern: Pattern,
28    pub message: Option<String>,
29    pub conditions: Vec<Condition>,
30    // Structured matching fields (all optional, combined with AND).
31    pub command: Option<String>,
32    pub subcommand: Option<String>,
33    pub subcommands: Option<Vec<String>>,
34    pub flags: Option<Vec<String>>,
35    pub args_contain: Option<String>,
36}
37
38impl Rule {
39    #[must_use]
40    pub fn new(target: RuleTarget, decision: Decision, pattern: &str) -> Self {
41        Self {
42            target,
43            decision,
44            pattern: Pattern::new(pattern),
45            message: None,
46            conditions: vec![],
47            command: None,
48            subcommand: None,
49            subcommands: None,
50            flags: None,
51            args_contain: None,
52        }
53    }
54
55    #[must_use]
56    pub fn with_message(mut self, msg: impl Into<String>) -> Self {
57        self.message = Some(msg.into());
58        self
59    }
60
61    #[must_use]
62    pub fn with_conditions(mut self, c: Vec<Condition>) -> Self {
63        self.conditions = c;
64        self
65    }
66
67    /// Format structured fields as a human-readable description.
68    #[must_use]
69    pub fn structured_description(&self) -> String {
70        let mut parts = Vec::new();
71        if let Some(c) = &self.command {
72            parts.push(format!("command={c}"));
73        }
74        if let Some(s) = &self.subcommand {
75            parts.push(format!("subcommand={s}"));
76        }
77        if let Some(list) = &self.subcommands {
78            parts.push(format!("subcommands=[{}]", list.join(",")));
79        }
80        if let Some(f) = &self.flags {
81            parts.push(format!("flags=[{}]", f.join(",")));
82        }
83        if let Some(a) = &self.args_contain {
84            parts.push(format!("args-contain={a}"));
85        }
86        parts.join(" ")
87    }
88
89    /// Returns `true` if this rule has any structured matching fields set.
90    #[must_use]
91    pub const fn has_structured_fields(&self) -> bool {
92        self.command.is_some()
93            || self.subcommand.is_some()
94            || self.subcommands.is_some()
95            || self.flags.is_some()
96            || self.args_contain.is_some()
97    }
98
99    /// Return the action string for this rule (e.g. "allow", "deny-redirect", "ask-read").
100    #[must_use]
101    pub fn action_str(&self) -> String {
102        let base = self.decision.as_str();
103        match self.target {
104            RuleTarget::Command => base.to_string(),
105            RuleTarget::Redirect => format!("{base}-redirect"),
106            RuleTarget::Mcp => format!("{base}-mcp"),
107            RuleTarget::FileRead => format!("{base}-read"),
108            RuleTarget::FileWrite => format!("{base}-write"),
109            RuleTarget::FileEdit => format!("{base}-edit"),
110            RuleTarget::After => "after".to_string(),
111        }
112    }
113}
114
115/// A parsed config directive — either a Rule, a Set key/value, or an Alias.
116#[derive(Debug, Clone)]
117pub enum ConfigDirective {
118    Rule(Rule),
119    Set {
120        key: String,
121        value: String,
122    },
123    Alias {
124        source: String,
125        target: String,
126    },
127    CdAllow(PathBuf),
128    /// Marker separating baseline (stdlib + global) from project rules.
129    ProjectBoundary,
130}