qubit_redact/text/log_safe_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 is safe to render at a plain-text log boundary.
9
10use std::{
11 borrow::Cow,
12 fmt::{
13 self,
14 Display,
15 Formatter,
16 },
17};
18
19use super::{
20 BoundedLogSafeDisplay,
21 LogOutputLimit,
22};
23
24/// Redacted text whose log-structure and bidirectional controls are escaped.
25///
26/// Values can only be constructed inside this crate after escaping, preventing
27/// arbitrary untrusted text from being labeled log-safe.
28///
29/// # Type Parameters
30///
31/// * `'a` - Lifetime of any borrowed escaped text stored by the value.
32#[must_use = "render or otherwise consume the log-safe value"]
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct LogSafeText<'a>(
35 /// Borrowed safe input or an owned escaped value.
36 Cow<'a, str>,
37);
38
39impl<'a> LogSafeText<'a> {
40 /// Creates log-safe text from contents that have already been escaped.
41 ///
42 /// # Parameters
43 ///
44 /// * `value` - Escaped text with its ownership form preserved.
45 ///
46 /// # Returns
47 ///
48 /// Typed log-safe text.
49 #[inline(always)]
50 pub(crate) const fn from_escaped(value: Cow<'a, str>) -> Self {
51 Self(value)
52 }
53
54 /// Borrows the escaped log-safe contents.
55 ///
56 /// # Returns
57 ///
58 /// The escaped text without allocating or formatting it.
59 #[inline(always)]
60 pub fn as_str(&self) -> &str {
61 self.0.as_ref()
62 }
63
64 /// Converts this value into an owned escaped string.
65 ///
66 /// # Returns
67 ///
68 /// The existing string allocation when this value already owns its
69 /// contents, or a copied string for borrowed contents.
70 #[inline(always)]
71 pub fn into_owned(self) -> String {
72 self.0.into_owned()
73 }
74
75 /// Creates a display adapter bounded by one final log-output limit.
76 ///
77 /// # Parameters
78 ///
79 /// * `limit` - Validated final output-byte limit.
80 ///
81 /// # Returns
82 ///
83 /// A bounded adapter borrowing this escaped text.
84 #[must_use = "format the bounded log-safe text"]
85 #[inline(always)]
86 pub const fn with_output_limit(
87 &self,
88 limit: LogOutputLimit,
89 ) -> BoundedLogSafeDisplay<'_> {
90 BoundedLogSafeDisplay::new(self, limit)
91 }
92}
93
94impl AsRef<str> for LogSafeText<'_> {
95 /// Borrows the escaped log-safe contents.
96 ///
97 /// # Returns
98 ///
99 /// The escaped text as a string slice.
100 #[inline(always)]
101 fn as_ref(&self) -> &str {
102 self.as_str()
103 }
104}
105
106impl Display for LogSafeText<'_> {
107 /// Writes the already escaped contents without surrounding quotes.
108 ///
109 /// # Parameters
110 ///
111 /// * `formatter` - Destination formatting context.
112 ///
113 /// # Returns
114 ///
115 /// The formatter result from writing the complete escaped string.
116 ///
117 /// # Errors
118 ///
119 /// Returns [`fmt::Error`] when the destination formatter cannot accept the
120 /// complete escaped string.
121 #[inline(always)]
122 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
123 formatter.write_str(self.as_str())
124 }
125}