Skip to main content

qubit_redact/text/
redacted_text.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Text that has passed through a redaction policy.
9
10use std::borrow::Cow;
11
12use super::{
13    LogSafeText,
14    log_escape::escape_log_control_characters,
15};
16
17/// A value that has passed through field-sensitive redaction.
18///
19/// This type deliberately does not implement [`std::fmt::Display`]. Plain-text
20/// log sinks must first call [`Self::escape_for_log`] so that controls and
21/// bidirectional formatting characters cannot manipulate rendered log output.
22/// Its [`std::fmt::Debug`] implementation is retained for interactive
23/// debugging and displays the already-processed value; it is not a substitute
24/// for crossing the explicit plain-text log boundary.
25///
26/// ```compile_fail
27/// use qubit_redact::Redactor;
28///
29/// let value = Redactor::default().redact_field("message", "hello");
30/// let _ = format!("{value}");
31/// ```
32///
33/// # Type Parameters
34///
35/// * `'a` - Lifetime of any borrowed redacted text stored by the value.
36#[must_use = "use the redacted value instead of the original value"]
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct RedactedText<'a>(
39    /// Borrowed input or an owned masked value.
40    Cow<'a, str>,
41);
42
43impl<'a> RedactedText<'a> {
44    /// Creates typed redacted text from a borrowed or owned value.
45    ///
46    /// # Parameters
47    ///
48    /// * `value` - Value already processed by a redaction policy.
49    ///
50    /// # Returns
51    ///
52    /// Typed redacted text retaining the input ownership form.
53    #[inline(always)]
54    pub(crate) const fn new(value: Cow<'a, str>) -> Self {
55        Self(value)
56    }
57
58    /// Borrows the redacted contents.
59    ///
60    /// # Returns
61    ///
62    /// The redacted text as a string slice.
63    #[must_use = "use the redacted value instead of the original value"]
64    #[inline(always)]
65    pub fn as_str(&self) -> &str {
66        self.0.as_ref()
67    }
68
69    /// Converts the redacted contents into an owned string.
70    ///
71    /// # Returns
72    ///
73    /// The redacted text, allocating only when the value is borrowed.
74    #[must_use = "use the redacted value instead of the original value"]
75    #[inline(always)]
76    pub fn into_owned(self) -> String {
77        self.0.into_owned()
78    }
79
80    /// Escapes the redacted contents for a plain-text log boundary.
81    ///
82    /// All control characters, Unicode line and paragraph separators, and
83    /// Unicode bidirectional formatting controls are rendered with debug
84    /// escapes. Safe borrowed input remains borrowed.
85    ///
86    /// # Returns
87    ///
88    /// Typed text that is safe to render with [`std::fmt::Display`].
89    #[inline]
90    pub fn escape_for_log(self) -> LogSafeText<'a> {
91        LogSafeText::from_escaped(escape_log_control_characters(self.0))
92    }
93
94    /// Returns the underlying borrowed or owned value to crate internals.
95    ///
96    /// # Returns
97    ///
98    /// The redacted text with its ownership form preserved.
99    #[cfg(feature = "http")]
100    #[must_use = "use the redacted value instead of the original value"]
101    #[inline(always)]
102    pub(crate) fn into_inner(self) -> Cow<'a, str> {
103        self.0
104    }
105}