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///
23/// ```compile_fail
24/// use qubit_redact::Redactor;
25///
26/// let value = Redactor::default().redact("message", "hello");
27/// let _ = format!("{value}");
28/// ```
29///
30/// # Type Parameters
31///
32/// * `'a` - Lifetime of any borrowed redacted text stored by the value.
33#[must_use = "use the redacted value instead of the original value"]
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct RedactedText<'a>(
36    /// Borrowed input or an owned masked value.
37    Cow<'a, str>,
38);
39
40impl<'a> RedactedText<'a> {
41    /// Creates typed redacted text from a borrowed or owned value.
42    ///
43    /// # Parameters
44    ///
45    /// * `value` - Value already processed by a redaction policy.
46    ///
47    /// # Returns
48    ///
49    /// Typed redacted text retaining the input ownership form.
50    #[inline(always)]
51    pub(crate) const fn new(value: Cow<'a, str>) -> Self {
52        Self(value)
53    }
54
55    /// Borrows the redacted contents.
56    ///
57    /// # Returns
58    ///
59    /// The redacted text as a string slice.
60    #[must_use = "use the redacted value instead of the original value"]
61    #[inline(always)]
62    pub fn as_str(&self) -> &str {
63        self.0.as_ref()
64    }
65
66    /// Converts the redacted contents into an owned string.
67    ///
68    /// # Returns
69    ///
70    /// The redacted text, allocating only when the value is borrowed.
71    #[must_use = "use the redacted value instead of the original value"]
72    #[inline(always)]
73    pub fn into_owned(self) -> String {
74        self.0.into_owned()
75    }
76
77    /// Escapes the redacted contents for a plain-text log boundary.
78    ///
79    /// All control characters, Unicode line and paragraph separators, and
80    /// Unicode bidirectional formatting controls are rendered with debug
81    /// escapes. Safe borrowed input remains borrowed.
82    ///
83    /// # Returns
84    ///
85    /// Typed text that is safe to render with [`std::fmt::Display`].
86    #[inline]
87    pub fn escape_for_log(self) -> LogSafeText<'a> {
88        LogSafeText::from_escaped(escape_log_control_characters(self.0))
89    }
90
91    /// Returns the underlying borrowed or owned value to crate internals.
92    ///
93    /// # Returns
94    ///
95    /// The redacted text with its ownership form preserved.
96    #[cfg(feature = "http")]
97    #[must_use = "use the redacted value instead of the original value"]
98    #[inline(always)]
99    pub(crate) fn into_inner(self) -> Cow<'a, str> {
100        self.0
101    }
102}