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}