1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// =============================================================================
// Copyright (c) 2026 Haixing Hu.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Operation-scoped mutable accounting for bounded redaction.
use std::cell::RefCell;
use super::{
InputOutputLimit,
RedactionPolicy,
};
mod budget {
use super::InputOutputLimit;
/// Mutable input/output accounting for one redaction event.
///
/// A budget is intentionally not cloneable. Callers must pass the same
/// instance through every component that contributes to one diagnostic
/// rendering so that a child cannot reset the parent's allowance.
#[must_use]
#[derive(Debug)]
pub(crate) struct DiagnosticBudget {
remaining_input_bytes: usize,
remaining_output_bytes: usize,
input_exhausted: bool,
output_exhausted: bool,
}
/// Result of charging an eagerly returned diagnostic fragment.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutputCharge {
/// The complete fragment was charged and may be returned.
Complete,
/// The complete fragment did not fit, but one fallback marker was
/// charged.
Fallback,
/// Neither the fragment nor its fallback can be emitted within the
/// budget.
Exhausted,
}
impl DiagnosticBudget {
/// Creates runtime accounting from an immutable input/output limit.
#[must_use = "retain the runtime budget for accounting"]
#[inline]
pub(crate) const fn new(limit: InputOutputLimit) -> Self {
Self {
remaining_input_bytes: limit.max_input_bytes(),
remaining_output_bytes: limit.max_output_bytes(),
input_exhausted: false,
output_exhausted: false,
}
}
/// Reserves input bytes before inspecting source data.
#[inline]
pub(crate) fn consume_input(&mut self, bytes: usize) -> bool {
if self.input_exhausted || bytes > self.remaining_input_bytes {
self.input_exhausted = true;
self.remaining_input_bytes = 0;
return false;
}
self.remaining_input_bytes -= bytes;
if self.remaining_input_bytes == 0 {
self.input_exhausted = true;
}
true
}
/// Atomically charges either a complete fragment or its terminal
/// fallback.
pub(crate) fn charge_output_or_fallback(
&mut self,
bytes: usize,
fallback_bytes: usize,
) -> OutputCharge {
if !self.output_exhausted && bytes <= self.remaining_output_bytes {
self.remaining_output_bytes -= bytes;
self.output_exhausted = self.remaining_output_bytes == 0;
return OutputCharge::Complete;
}
if !self.output_exhausted
&& fallback_bytes <= self.remaining_output_bytes
{
self.remaining_output_bytes = 0;
self.output_exhausted = true;
return OutputCharge::Fallback;
}
self.remaining_output_bytes = 0;
self.output_exhausted = true;
OutputCharge::Exhausted
}
/// Returns the input bytes still available for inspection.
#[must_use]
#[inline]
pub(crate) const fn remaining_input_bytes(&self) -> usize {
self.remaining_input_bytes
}
/// Returns the output bytes still available for rendering.
#[must_use]
#[inline]
pub(crate) const fn remaining_output_bytes(&self) -> usize {
self.remaining_output_bytes
}
/// Returns whether this event can no longer accept input or output.
#[must_use]
#[inline]
pub(crate) const fn is_exhausted(&self) -> bool {
self.input_exhausted || self.output_exhausted
}
}
}
pub(crate) use budget::{
DiagnosticBudget,
OutputCharge,
};
mod session_kind {
/// Identifies whether a session is an ordinary operation or a diagnostic
/// event.
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RedactionSessionKind {
/// An independent ordinary redaction operation.
Operation,
/// A diagnostic representation with bounded output.
Diagnostic,
}
}
pub use session_kind::RedactionSessionKind;
/// Carries one immutable policy and one mutable budget through a redaction
/// operation.
#[must_use]
#[derive(Debug)]
pub struct RedactionSession<'policy> {
policy: &'policy RedactionPolicy,
budget: RefCell<DiagnosticBudget>,
kind: RedactionSessionKind,
}
impl<'policy> RedactionSession<'policy> {
/// Creates an ordinary operation session from `policy`.
#[must_use = "retain the operation session for redaction"]
#[inline]
pub fn operation(policy: &'policy RedactionPolicy) -> Self {
Self {
policy,
budget: RefCell::new(DiagnosticBudget::new(
policy.limits().ordinary_operation(),
)),
kind: RedactionSessionKind::Operation,
}
}
/// Creates a diagnostic session from `policy`.
#[must_use = "retain the diagnostic session for redaction"]
#[inline]
pub fn diagnostic(policy: &'policy RedactionPolicy) -> Self {
Self {
policy,
budget: RefCell::new(DiagnosticBudget::new(
policy.limits().diagnostic_event(),
)),
kind: RedactionSessionKind::Diagnostic,
}
}
/// Returns the immutable policy snapshot used by this session.
#[must_use = "use the policy snapshot for redaction"]
#[inline]
pub const fn policy(&self) -> &'policy RedactionPolicy {
self.policy
}
/// Returns the kind of operation represented by this session.
#[must_use = "use the session kind when selecting operation behavior"]
#[inline]
pub const fn kind(&self) -> RedactionSessionKind {
self.kind
}
/// Reserves input bytes in the shared event budget.
#[inline]
pub fn consume_input(&self, bytes: usize) -> bool {
self.budget.borrow_mut().consume_input(bytes)
}
/// Charges an eager fragment or, if it does not fit, one terminal marker.
pub(crate) fn charge_output_or_fallback(
&self,
bytes: usize,
fallback_bytes: usize,
) -> OutputCharge {
self.budget
.borrow_mut()
.charge_output_or_fallback(bytes, fallback_bytes)
}
/// Returns the remaining input allowance.
#[must_use]
#[inline]
pub fn remaining_input_bytes(&self) -> usize {
self.budget.borrow().remaining_input_bytes()
}
/// Returns the remaining output allowance.
#[must_use]
#[inline]
pub fn remaining_output_bytes(&self) -> usize {
self.budget.borrow().remaining_output_bytes()
}
/// Returns whether this session is exhausted.
#[must_use]
#[inline]
pub fn is_exhausted(&self) -> bool {
self.budget.borrow().is_exhausted()
}
}