Skip to main content

qubit_redact/http/
body_redaction.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//! Log-safe bounded result of HTTP body redaction.
9
10use std::{
11    borrow::Cow,
12    fmt::{
13        self,
14        Display,
15        Formatter,
16    },
17};
18
19use crate::LogSafeText;
20
21use super::BodyRedactionStatus;
22
23/// Holds only escaped, bounded body text plus read-only source metadata.
24#[must_use = "inspect or render the redacted body instead of discarding it"]
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct BodyRedaction {
27    /// Escaped and output-bounded diagnostic representation.
28    text: LogSafeText<'static>,
29    /// How the diagnostic representation was produced.
30    status: BodyRedactionStatus,
31    /// Number of source bytes inspected after applying the input budget.
32    captured_len: usize,
33    /// Exact complete source length when known.
34    source_len: Option<usize>,
35    /// Exact number of source bytes omitted when known.
36    omitted_len: Option<usize>,
37    /// Whether capture, input budget, or output budget omitted data.
38    truncated: bool,
39}
40
41impl BodyRedaction {
42    /// Creates a completed safe body result.
43    ///
44    /// # Parameters
45    ///
46    /// * `text` - Escaped and bounded output text.
47    /// * `status` - Classification of the redaction outcome.
48    /// * `captured_len` - Number of source bytes inspected.
49    /// * `source_len` - Exact source length when known.
50    /// * `omitted_len` - Exact number of uninspected source bytes when known.
51    /// * `truncated` - Whether source or rendered data was omitted.
52    ///
53    /// # Returns
54    ///
55    /// A body result exposing only log-safe text.
56    #[inline(always)]
57    pub(super) fn new(
58        text: String,
59        status: BodyRedactionStatus,
60        captured_len: usize,
61        source_len: Option<usize>,
62        omitted_len: Option<usize>,
63        truncated: bool,
64    ) -> Self {
65        Self {
66            text: LogSafeText::from_escaped(Cow::Owned(text)),
67            status,
68            captured_len,
69            source_len,
70            omitted_len,
71            truncated,
72        }
73    }
74
75    /// Returns the escaped and output-bounded diagnostic text.
76    ///
77    /// # Returns
78    ///
79    /// A borrowed log-safe body representation including a complete
80    /// truncation marker whenever [`Self::is_truncated`] is `true`.
81    #[inline]
82    pub const fn log_safe_text(&self) -> &LogSafeText<'static> {
83        &self.text
84    }
85
86    /// Consumes this result and returns its escaped diagnostic text.
87    ///
88    /// # Returns
89    ///
90    /// Owned log-safe body text including any truncation marker.
91    #[inline(always)]
92    pub fn into_log_safe_text(self) -> LogSafeText<'static> {
93        self.text
94    }
95
96    /// Returns how the body representation was produced.
97    ///
98    /// # Returns
99    ///
100    /// The immutable redaction status.
101    #[inline(always)]
102    pub const fn status(&self) -> BodyRedactionStatus {
103        self.status
104    }
105
106    /// Returns the number of source bytes inspected.
107    ///
108    /// # Returns
109    ///
110    /// The byte count after applying the hard input budget.
111    #[must_use]
112    #[inline]
113    pub const fn captured_len(&self) -> usize {
114        self.captured_len
115    }
116
117    /// Returns the complete source length when known.
118    ///
119    /// # Returns
120    ///
121    /// `Some(total)` for known source size, or `None` when a truncated source
122    /// had no exact total length.
123    #[inline(always)]
124    pub const fn source_len(&self) -> Option<usize> {
125        self.source_len
126    }
127
128    /// Returns the exact number of omitted source bytes when known.
129    ///
130    /// # Returns
131    ///
132    /// `Some(count)` when the source length is known, or `None` otherwise.
133    #[inline]
134    pub const fn omitted_len(&self) -> Option<usize> {
135        self.omitted_len
136    }
137
138    /// Reports whether any source or rendered data was omitted.
139    ///
140    /// # Returns
141    ///
142    /// `true` for source capture, input-budget, or output-budget truncation.
143    #[must_use]
144    #[inline]
145    pub const fn is_truncated(&self) -> bool {
146        self.truncated
147    }
148}
149
150impl Display for BodyRedaction {
151    /// Writes the bounded log-safe body representation.
152    ///
153    /// # Parameters
154    ///
155    /// * `formatter` - Destination formatting context.
156    ///
157    /// # Returns
158    ///
159    /// The formatter result from writing the complete safe text.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`fmt::Error`] when the destination rejects a write.
164    #[inline(always)]
165    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
166        Display::fmt(&self.text, formatter)
167    }
168}