Skip to main content

qubit_redact/facade/
redaction_text_output.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//! Final output paired with its execution summary.
9
10use std::borrow::Cow;
11
12use super::RedactedText;
13use super::RedactionSummary;
14use crate::RedactionCompletion;
15
16/// Published policy-transformed text and completion metadata from one
17/// operation.
18///
19/// Incomplete output retains the selected policy's confidentiality guarantees.
20/// `Truncated` and `Exhausted` describe incomplete diagnostics; they do not
21/// authorize disclosure of fields protected by that policy. As with
22/// [`RedactedText`], disabled policies and explicitly unredacted operations can
23/// deliberately preserve source content. Callers can reject or replace
24/// incomplete text when their own contract requires completeness.
25///
26/// # Examples
27///
28/// ```
29/// use qubit_redact::RedactionCompletion;
30/// use qubit_redact::Redactor;
31///
32/// let output = Redactor::strict().redact_field("password", "raw-secret");
33/// assert_eq!(output.summary().completion(), RedactionCompletion::Complete);
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct RedactionTextOutput {
37    /// Final log-safe text owned by this completed transaction.
38    text: RedactedText,
39    /// Completion, provenance, and resource use for the same transaction.
40    summary: RedactionSummary,
41}
42
43impl RedactionTextOutput {
44    /// Pairs published text with its actual completion and accounting summary.
45    ///
46    /// # Parameters
47    ///
48    /// - `text`: Final policy-transformed and escaped text.
49    /// - `summary`: Accounting and completion from the same operation.
50    ///
51    /// # Returns
52    ///
53    /// An output pairing the text with its actual execution metadata.
54    #[must_use]
55    #[inline(always)]
56    pub(crate) fn new(text: RedactedText, summary: RedactionSummary) -> Self {
57        Self { text, summary }
58    }
59
60    /// Borrows the final text.
61    ///
62    /// # Returns
63    ///
64    /// Borrowed final text, including safe incomplete representations.
65    #[must_use]
66    #[inline(always)]
67    pub const fn text(&self) -> &RedactedText {
68        &self.text
69    }
70
71    /// Borrows the execution summary.
72    ///
73    /// # Returns
74    ///
75    /// Borrowed completion, reasons, and resource use for this output.
76    #[must_use]
77    #[inline(always)]
78    pub const fn summary(&self) -> &RedactionSummary {
79        &self.summary
80    }
81
82    /// Borrows the final text when the operation completed without truncation
83    /// or exhaustion.
84    ///
85    /// # Errors
86    ///
87    /// Returns the execution summary when the safe output was truncated or
88    /// exhausted. The error reports completeness; it does not imply that the
89    /// published text is unsafe for diagnostics.
90    ///
91    /// # Returns
92    ///
93    /// Borrowed text only for Complete; otherwise the borrowed execution
94    /// summary.
95    #[inline]
96    pub fn complete_text(&self) -> Result<&RedactedText, &RedactionSummary> {
97        if self.summary.completion() == RedactionCompletion::Complete {
98            Ok(&self.text)
99        } else {
100            Err(&self.summary)
101        }
102    }
103
104    /// Borrows the final text or returns an escaped caller-selected marker
105    /// when the operation was incomplete.
106    ///
107    /// The complete path does not allocate. An incomplete marker is escaped
108    /// before publication so control characters cannot forge diagnostic log
109    /// structure. The marker is selected after the transaction and therefore
110    /// does not consume its resource budget.
111    ///
112    /// # Parameters
113    ///
114    /// - `marker`: Fallback escaped outside the completed operation’s byte
115    ///   budget.
116    ///
117    /// # Returns
118    ///
119    /// Borrowed complete text, or an owned escaped marker for incomplete
120    /// output.
121    #[must_use]
122    #[inline]
123    pub fn text_or_marker(&self, marker: &str) -> Cow<'_, str> {
124        self.complete_text().map_or_else(
125            |_| {
126                Cow::Owned(crate::output::log_escape::escape_log_control_characters(Cow::Borrowed(marker)).into_owned())
127            },
128            |text| Cow::Borrowed(text.as_str()),
129        )
130    }
131
132    /// Consumes a complete output and returns its final text.
133    ///
134    /// # Errors
135    ///
136    /// Returns the execution summary when the safe output was truncated or
137    /// exhausted. The error reports completeness; it does not imply that the
138    /// published text is unsafe for diagnostics.
139    ///
140    /// # Returns
141    ///
142    /// Owned text only for Complete; otherwise the owned execution summary.
143    #[inline]
144    pub fn into_complete_text(self) -> Result<RedactedText, RedactionSummary> {
145        if self.summary.completion() == RedactionCompletion::Complete {
146            Ok(self.text)
147        } else {
148            Err(self.summary)
149        }
150    }
151
152    /// Consumes the output and returns a caller-selected marker when it is
153    /// incomplete.
154    ///
155    /// The marker is escaped before becoming [`RedactedText`], so it remains
156    /// safe for diagnostic presentation.
157    ///
158    /// # Parameters
159    ///
160    /// - `marker`: Fallback escaped outside the completed operation’s byte
161    ///   budget.
162    ///
163    /// # Returns
164    ///
165    /// Owned complete text, or a wrapper around the escaped fallback marker.
166    #[must_use]
167    #[inline]
168    pub fn into_text_or_marker(self, marker: &str) -> RedactedText {
169        self.into_complete_text().unwrap_or_else(|_| {
170            RedactedText::from_escaped(
171                crate::output::log_escape::escape_log_control_characters(Cow::Borrowed(marker)).into_owned(),
172            )
173        })
174    }
175
176    /// Consumes the output and returns both parts.
177    ///
178    /// # Returns
179    ///
180    /// The owned text and summary, in that order, without checking
181    /// completeness.
182    #[must_use]
183    #[inline(always)]
184    pub fn into_parts(self) -> (RedactedText, RedactionSummary) {
185        (self.text, self.summary)
186    }
187}