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}