Skip to main content

eggress_pproxy_compat/
gate.rs

1//! Shared execution-readiness gate for pproxy compatibility execution.
2//!
3//! Both user-facing compatibility execution paths (the standalone
4//! `pproxy`/`eggress-pproxy-compat` binary and the `eggress pproxy run`
5//! subcommand) must apply the same fail-closed policy before any
6//! temporary config, system change, or runtime startup. This helper
7//! encodes that policy without knowing about process I/O, exit codes,
8//! or CLI types so it can be reused from any entry point.
9
10use crate::args::PproxyArgs;
11use crate::warnings::{CompatWarning, TranslationOutput, UnsupportedFeature};
12
13/// One reason why pproxy compatibility execution cannot start.
14#[derive(Debug, Clone, PartialEq)]
15pub enum BlockReason {
16    /// The parser found an unrecognized flag.
17    UnknownFlag(String),
18    /// The parser recognized the flag but Eggress cannot satisfy it.
19    Unsupported(UnsupportedFeature),
20}
21
22/// Aggregate result of the shared compatibility execution gate.
23#[derive(Debug, Clone, PartialEq)]
24pub struct ExecutionGate {
25    /// Reasons that prevent startup, in stable order.
26    pub blockers: Vec<BlockReason>,
27    /// Benign warnings that do not block startup.
28    pub warnings: Vec<CompatWarning>,
29}
30
31impl ExecutionGate {
32    /// Returns `true` when no blocker prevents startup.
33    pub fn allows_start(&self) -> bool {
34        self.blockers.is_empty()
35    }
36
37    /// Render blockers as a stable, human-readable summary.
38    pub fn blocker_summary(&self) -> String {
39        self.blockers
40            .iter()
41            .map(|b| match b {
42                BlockReason::UnknownFlag(f) => format!("unknown option '{f}'"),
43                BlockReason::Unsupported(u) => format!("{u}"),
44            })
45            .collect::<Vec<_>>()
46            .join("\n")
47    }
48}
49
50/// Compute the shared execution gate for a compatibility invocation.
51///
52/// The gate combines:
53/// 1. parser-side unknown flags (always fatal),
54/// 2. translator-side unsupported features (always fatal),
55/// 3. benign warnings that do not block startup.
56pub fn evaluate(args: &PproxyArgs, output: &TranslationOutput) -> ExecutionGate {
57    let mut blockers: Vec<BlockReason> = Vec::new();
58    for flag in args.strict_parser_violations() {
59        blockers.push(BlockReason::UnknownFlag(flag.clone()));
60    }
61    for u in &output.unsupported {
62        blockers.push(BlockReason::Unsupported(u.clone()));
63    }
64    ExecutionGate {
65        blockers,
66        warnings: output.warnings.clone(),
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::args::PproxyArgs;
74
75    fn parse(args: &[&str]) -> PproxyArgs {
76        let raw: Vec<String> = args.iter().map(|s| s.to_string()).collect();
77        PproxyArgs::parse(&raw).expect("parser failed")
78    }
79
80    #[test]
81    fn evaluate_allows_clean_translation() {
82        let args = parse(&["-l", "http://:8080", "-r", "socks5://proxy:1080"]);
83        let output = crate::translate::translate_pproxy_args(&args).unwrap();
84        let gate = evaluate(&args, &output);
85        assert!(gate.allows_start(), "unexpected blockers: {:?}", gate);
86        assert!(gate.warnings.is_empty());
87    }
88
89    #[test]
90    fn evaluate_blocks_unknown_flag() {
91        let args = parse(&["-l", "http://:8080", "--bogus-flag"]);
92        let output = crate::translate::translate_pproxy_args(&args).unwrap();
93        let gate = evaluate(&args, &output);
94        assert!(!gate.allows_start());
95        assert!(matches!(
96            gate.blockers.first(),
97            Some(BlockReason::UnknownFlag(f)) if f == "--bogus-flag"
98        ));
99    }
100
101    #[test]
102    fn evaluate_blocks_daemon() {
103        let args = parse(&["-l", "http://:8080", "--daemon"]);
104        let output = crate::translate::translate_pproxy_args(&args).unwrap();
105        let gate = evaluate(&args, &output);
106        #[cfg(feature = "daemon")]
107        assert!(gate.allows_start());
108        #[cfg(not(feature = "daemon"))]
109        {
110            assert!(!gate.allows_start());
111            assert!(gate
112                .blockers
113                .iter()
114                .any(|b| matches!(b, BlockReason::Unsupported(u) if u.feature == "daemon")));
115        }
116    }
117
118    #[test]
119    fn evaluate_allows_sys_with_warning() {
120        let args = parse(&["-l", "http://:8080", "--sys"]);
121        let output = crate::translate::translate_pproxy_args(&args).unwrap();
122        let gate = evaluate(&args, &output);
123        assert!(gate.allows_start(), "unexpected blockers: {:?}", gate);
124    }
125
126    #[test]
127    fn evaluate_allows_auth_with_warning() {
128        let args = parse(&["-l", "http://:8080", "--auth", "3600"]);
129        let output = crate::translate::translate_pproxy_args(&args).unwrap();
130        let gate = evaluate(&args, &output);
131        assert!(gate.allows_start(), "unexpected blockers: {:?}", gate);
132    }
133
134    #[test]
135    fn evaluate_blocks_malformed_auth() {
136        let raw = vec![
137            "-l".to_string(),
138            "http://:8080".to_string(),
139            "--auth".to_string(),
140            "abc".to_string(),
141        ];
142        let err = PproxyArgs::parse(&raw).unwrap_err();
143        // The parser itself rejects malformed --auth before reaching the
144        // gate, so this path is handled by the parser exit branch.
145        let _ = err;
146    }
147
148    #[test]
149    fn evaluate_does_not_block_d_flag() {
150        let args = parse(&["-l", "http://:8080", "-d"]);
151        let output = crate::translate::translate_pproxy_args(&args).unwrap();
152        let gate = evaluate(&args, &output);
153        assert!(gate.allows_start(), "unexpected blockers: {:?}", gate);
154    }
155
156    #[test]
157    fn evaluate_blocker_summary_lists_each_reason() {
158        let args = parse(&["-l", "http://:8080", "--bogus-flag", "--daemon"]);
159        let output = crate::translate::translate_pproxy_args(&args).unwrap();
160        let gate = evaluate(&args, &output);
161        let summary = gate.blocker_summary();
162        assert!(summary.contains("--bogus-flag"));
163        #[cfg(not(feature = "daemon"))]
164        assert!(summary.contains("daemon"));
165    }
166}