返回 DeepSeek-TUI-2026
execpolicycheck.rs
根目录 / crates / tui / src / execpolicy / execpolicycheck.rs
1 use std::fs;
2 use std::path::PathBuf;
3
4 use anyhow::Context;
5 use anyhow::Result;
6 use clap::Parser;
7 use serde::Serialize;
8
9 use super::Decision;
10 use super::Policy;
11 use super::PolicyParser;
12 use super::RuleMatch;
13
14 /// Arguments for evaluating a command against one or more execpolicy files.
15 #[derive(Debug, Parser, Clone)]
16 pub struct ExecPolicyCheckCommand {
17 /// Paths to execpolicy rule files to evaluate (repeatable).
18 #[arg(short = 'r', long = "rules", value_name = "PATH", required = true)]
19 pub rules: Vec<PathBuf>,
20
21 /// Pretty-print the JSON output.
22 #[arg(long)]
23 pub pretty: bool,
24
25 /// Command tokens to check against the policy.
26 #[arg(
27 value_name = "COMMAND",
28 required = true,
29 trailing_var_arg = true,
30 allow_hyphen_values = true
31 )]
32 pub command: Vec<String>,
33 }
34
35 impl ExecPolicyCheckCommand {
36 /// Load the policies for this command, evaluate the command, and render JSON output.
37 pub fn run(&self) -> Result<()> {
38 let policy = load_policies(&self.rules)?;
39 let matched_rules = policy.matches_for_command(&self.command, None);
40
41 let json = format_matches_json(&matched_rules, self.pretty)?;
42 println!("{json}");
43
44 Ok(())
45 }
46 }
47
48 pub fn format_matches_json(matched_rules: &[RuleMatch], pretty: bool) -> Result<String> {
49 let output = ExecPolicyCheckOutput {
50 matched_rules,
51 decision: matched_rules.iter().map(RuleMatch::decision).max(),
52 };
53
54 if pretty {
55 serde_json::to_string_pretty(&output).map_err(Into::into)
56 } else {
57 serde_json::to_string(&output).map_err(Into::into)
58 }
59 }
60
61 pub fn load_policies(policy_paths: &[PathBuf]) -> Result<Policy> {
62 let mut parser = PolicyParser::new();
63
64 for policy_path in policy_paths {
65 let policy_file_contents = fs::read_to_string(policy_path)
66 .with_context(|| format!("failed to read policy at {}", policy_path.display()))?;
67 let policy_identifier = policy_path.to_string_lossy().to_string();
68 parser
69 .parse(&policy_identifier, &policy_file_contents)
70 .with_context(|| format!("failed to parse policy at {}", policy_path.display()))?;
71 }
72
73 Ok(parser.build())
74 }
75
76 #[derive(Serialize)]
77 #[serde(rename_all = "camelCase")]
78 struct ExecPolicyCheckOutput<'a> {
79 #[serde(rename = "matchedRules")]
80 matched_rules: &'a [RuleMatch],
81 #[serde(skip_serializing_if = "Option::is_none")]
82 decision: Option<Decision>,
83 }
84
84 lines RUST