Skip to main content

qubit_redact/formats/process/
process_redaction_writer.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8// qubit-style: allow multiple-public-types
9//! Aggregate process-command rendering through one borrowed transaction.
10
11use std::ffi::OsStr;
12
13use super::command_items::CommandItems;
14use crate::formats::argv::ArgvItem;
15use crate::formats::argv::ArgvRedactionWriter;
16use crate::formats::env::EnvRedactionWriter;
17use crate::runtime::TextSession;
18use crate::runtime::runtime_session::RuntimeSession;
19
20/// A borrowed process-command facade over one active redaction transaction.
21///
22/// This type owns no redactor, policy, result, or budget. Each operation
23/// delegates directly to the argv or environment namespace of the parent
24/// session, so process diagnostics participate in the same atomic output.
25pub struct ProcessRedactionWriter<'session> {
26    /// The transaction receiving every rendered process component.
27    session: &'session mut TextSession,
28}
29
30impl<'session> ProcessRedactionWriter<'session> {
31    /// Creates a process facade that borrows `session` for one adapter closure.
32    ///
33    /// # Parameters
34    ///
35    /// * `session` - The active transaction that owns policy, budget, and
36    ///   unpublished output.
37    ///
38    /// # Returns
39    ///
40    /// A façade that cannot outlive the borrowed transaction.
41    #[inline(always)]
42    #[must_use]
43    pub(crate) const fn new(session: &'session mut TextSession) -> Self {
44        Self { session }
45    }
46
47    /// Redacts a complete process command into the parent aggregate output.
48    ///
49    /// `program` is inserted as the first argv item. `arguments` therefore
50    /// must contain only the arguments following the executable. Each argument
51    /// preserves caller-supplied [`ArgvItem`] sensitivity. `variables` uses
52    /// borrowed operating-system name/value pairs and is rendered after argv.
53    /// No key or independently published result is created.
54    ///
55    /// # Type Parameters
56    ///
57    /// * `'arguments` - Lifetime of borrowed argument values.
58    /// * `A` - Finite argument source.
59    /// * `'variables` - Lifetime of borrowed environment names and values.
60    /// * `E` - Finite environment source.
61    ///
62    /// # Parameters
63    ///
64    /// * `program` - Executable path or program name.
65    /// * `arguments` - Arguments after `program`.
66    /// * `variables` - Environment-variable pairs associated with the command.
67    ///
68    /// # Returns
69    ///
70    /// This facade for further aggregate process operations.
71    pub fn command<'arguments, 'variables, A, E>(
72        &mut self,
73        program: &'arguments OsStr,
74        arguments: A,
75        variables: E,
76    ) -> &mut Self
77    where
78        A: IntoIterator<Item = ArgvItem<'arguments>>,
79        E: IntoIterator<Item = (&'variables OsStr, &'variables OsStr)>,
80    {
81        if self.session.skip_aggregate_for_exhausted_output() {
82            return self;
83        }
84        let arguments = arguments.into_iter();
85        let mut argv = ArgvRedactionWriter::new(self.session);
86        argv.heuristic_items(CommandItems::new(ArgvItem::plain(program), arguments));
87        if self.session.skip_aggregate_for_exhausted_output() {
88            return self;
89        }
90        let mut environment = EnvRedactionWriter::new(self.session);
91        environment.os_pairs(variables);
92        self
93    }
94
95    /// Redacts command-line arguments into the parent aggregate output.
96    ///
97    /// Plain arguments use the argv namespace's documented heuristic rules;
98    /// explicitly sensitive [`ArgvItem`] values are masked at their supplied
99    /// sensitivity. No key or standalone output is created.
100    ///
101    /// # Type Parameters
102    ///
103    /// * `'arguments` - Lifetime of borrowed argument values.
104    /// * `A` - Finite argument source.
105    ///
106    /// # Parameters
107    ///
108    /// * `arguments` - Borrowed argv items to render.
109    ///
110    /// # Returns
111    ///
112    /// This facade for further aggregate process operations.
113    pub fn arguments<'arguments, A>(&mut self, arguments: A) -> &mut Self
114    where
115        A: IntoIterator<Item = ArgvItem<'arguments>>,
116    {
117        let mut argv = ArgvRedactionWriter::new(self.session);
118        argv.heuristic_items(arguments);
119        self
120    }
121
122    /// Redacts process environment variables into the parent aggregate output.
123    ///
124    /// Names and values remain borrowed until the environment namespace has
125    /// processed them. No key or standalone output is created.
126    ///
127    /// # Type Parameters
128    ///
129    /// * `'variables` - Lifetime of borrowed environment names and values.
130    /// * `E` - Finite environment source.
131    ///
132    /// # Parameters
133    ///
134    /// * `variables` - Borrowed operating-system environment name/value pairs.
135    ///
136    /// # Returns
137    ///
138    /// This facade for further aggregate process operations.
139    pub fn variables<'variables, E>(&mut self, variables: E) -> &mut Self
140    where
141        E: IntoIterator<Item = (&'variables OsStr, &'variables OsStr)>,
142    {
143        let mut env = EnvRedactionWriter::new(self.session);
144        env.os_pairs(variables);
145        self
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use std::ffi::OsStr;
152
153    use super::ProcessRedactionWriter;
154    use crate::Redactor;
155    use crate::formats::argv::ArgvItem;
156
157    /// Verifies process components are appended to the one borrowed session.
158    #[test]
159    fn test_command_appends_redacted_argv_and_environment_to_parent_session() {
160        let arguments = [
161            ArgvItem::plain(OsStr::new("--password")),
162            ArgvItem::plain(OsStr::new("argv-secret")),
163        ];
164        let variables = [(OsStr::new("PASSWORD"), OsStr::new("env-secret"))];
165        let mut session = Redactor::strict().text_runtime();
166        let mut process = ProcessRedactionWriter::new(&mut session);
167
168        process.command(OsStr::new("client"), arguments, variables);
169
170        let output = session.finish();
171        assert!(!output.text().as_str().contains("argv-secret"));
172        assert!(!output.text().as_str().contains("env-secret"));
173    }
174}