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}