qubit_redact/formats/env/env_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//! Shared-session environment redaction.
9
10use std::ffi::OsStr;
11
12use super::redaction::redact_os_pairs_with_policy;
13use super::redaction::redact_pair_with_policy;
14use crate::runtime::TextSession;
15use crate::runtime::admit_flat_format_item;
16use crate::runtime::collect_flat_format_items;
17use crate::runtime::runtime_session::RuntimeSession;
18
19/// A borrowed environment façade over one mutable diagnostic session.
20///
21/// # Type Parameters
22///
23/// * `'session` - Borrow of the parent composer's unpublished transaction.
24///
25/// # Examples
26///
27/// ```
28/// use qubit_redact::Redactor;
29///
30/// let output = Redactor::standard().text_composer().env(|env| {
31/// env.pair("PASSWORD", "raw-password");
32/// }).finish();
33/// assert!(!output.text().as_str().contains("raw-password"));
34/// ```
35pub struct EnvRedactionWriter<'session> {
36 /// Shared policy and accounting owned by the parent session.
37 session: &'session mut TextSession,
38}
39
40impl<'session> EnvRedactionWriter<'session> {
41 /// Creates a façade from a mutable diagnostic session.
42 ///
43 /// # Parameters
44 ///
45 /// * `session` - Parent transaction receiving admitted environment pairs.
46 ///
47 /// # Returns
48 ///
49 /// A writer borrowing the transaction's existing policy and budget.
50 #[inline(always)]
51 #[must_use]
52 pub(crate) const fn new(session: &'session mut TextSession) -> Self {
53 Self { session }
54 }
55
56 /// Redacts one pair into the parent session's aggregate output.
57 ///
58 /// The name selects field policy; both name and value bytes consume the
59 /// parent's input allowance before rendering the assignment.
60 ///
61 /// # Parameters
62 ///
63 /// * `name` - Environment variable name used for classification.
64 /// * `value` - Borrowed value to redact or preserve under that policy.
65 ///
66 /// # Returns
67 ///
68 /// This writer for further operations in the same transaction.
69 pub fn pair(&mut self, name: &str, value: &str) -> &mut Self {
70 if self.session.skip_aggregate_for_exhausted_output() {
71 return self;
72 }
73 if !admit_flat_format_item(self.session, name.len().saturating_add(value.len())) {
74 return self;
75 }
76 let result = redact_pair_with_policy(
77 self.session.policy(),
78 name,
79 value,
80 self.session.remaining_output_bytes(),
81 );
82 self.session.append_rendered_operation(result);
83 self
84 }
85
86 /// Redacts an environment list into the parent session's aggregate output.
87 ///
88 /// Iterator advancement is guarded by shared structural admission.
89 /// Non-Unicode pairs use the format's bounded fallback policy.
90 ///
91 /// # Type Parameters
92 ///
93 /// * `'items` - Lifetime of borrowed operating-system names and values.
94 /// * `I` - Finite source of environment pairs.
95 ///
96 /// # Parameters
97 ///
98 /// * `pairs` - Name/value pairs in the desired diagnostic order.
99 ///
100 /// # Returns
101 ///
102 /// This writer for further operations in the same transaction.
103 pub fn os_pairs<'items, I>(&mut self, pairs: I) -> &mut Self
104 where
105 I: IntoIterator<Item = (&'items OsStr, &'items OsStr)>,
106 {
107 if self.session.skip_aggregate_for_exhausted_output() {
108 return self;
109 }
110 let Some(pairs) = collect_flat_format_items(self.session, pairs, |(name, value)| {
111 name.as_encoded_bytes()
112 .len()
113 .saturating_add(value.as_encoded_bytes().len())
114 }) else {
115 return self;
116 };
117 let result = redact_os_pairs_with_policy(self.session.policy(), pairs, self.session.remaining_output_bytes());
118 self.session.append_rendered_operation(result);
119 self
120 }
121}