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}