Skip to main content

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}