Skip to main content

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}