1use std::fmt;
11
12use serde_json::Value;
13
14#[cfg(feature = "serde")]
15use serde::ser::{
16 SerializeMap as _,
17 SerializeSeq as _,
18};
19
20use crate::{
21 RedactValue as _,
22 RedactedValue,
23 RedactionPolicy,
24 policy::ResolvedField,
25};
26
27#[must_use = "format or serialize the redacted JSON view"]
29pub struct RedactedJson<'value, 'policy> {
30 value: &'value Value,
32 policy: &'policy RedactionPolicy,
34 depth: usize,
36 unkeyed: bool,
38}
39
40impl<'value, 'policy> RedactedJson<'value, 'policy> {
41 #[inline(always)]
52 pub const fn new(
53 value: &'value Value,
54 policy: &'policy RedactionPolicy,
55 ) -> Self {
56 Self {
57 value,
58 policy,
59 depth: 0,
60 unkeyed: true,
61 }
62 }
63
64 #[inline(always)]
74 fn nested<'nested>(
75 &self,
76 value: &'nested Value,
77 unkeyed: bool,
78 ) -> RedactedJson<'nested, 'policy> {
79 RedactedJson {
80 value,
81 policy: self.policy,
82 depth: self.depth.saturating_add(1),
83 unkeyed,
84 }
85 }
86
87 #[inline(always)]
94 fn depth_limit_reached(&self) -> bool {
95 self.depth >= self.policy.json_depth_budget().max_depth()
96 && matches!(self.value, Value::Object(_) | Value::Array(_))
97 }
98
99 #[inline(always)]
105 fn depth_limit_mask(&self) -> RedactedValue<'_> {
106 RedactedValue::opaque(crate::Sensitivity::Secret, self.policy.masking())
107 }
108
109 #[inline(always)]
111 fn redact_unkeyed_scalar(&self) -> bool {
112 self.unkeyed
113 && self.policy.unkeyed_json_value_policy()
114 == crate::UnkeyedJsonValuePolicy::Redact
115 && !matches!(self.value, Value::Array(_) | Value::Object(_))
116 }
117}
118
119impl fmt::Debug for RedactedJson<'_, '_> {
120 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134 fmt_json(self, formatter)
135 }
136}
137
138#[cfg(feature = "serde")]
139impl serde::Serialize for RedactedJson<'_, '_> {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158 where
159 S: serde::Serializer,
160 {
161 if self.depth_limit_reached() {
162 return serde::Serialize::serialize(
163 &self.depth_limit_mask(),
164 serializer,
165 );
166 }
167 match self.value {
168 Value::Array(values) => {
169 let mut output =
170 serializer.serialize_seq(Some(values.len()))?;
171 for value in values {
172 output.serialize_element(&self.nested(value, true))?;
173 }
174 output.end()
175 }
176 Value::Object(values) => {
177 let mut output =
178 serializer.serialize_map(Some(values.len()))?;
179 for (key, value) in values {
180 let resolved = self.policy.resolve_field(key);
181 match resolved {
182 ResolvedField::Sensitive { sensitivity } => match value
183 {
184 Value::String(text) => {
185 let redacted = text.redact_value(
186 sensitivity,
187 self.policy.masking(),
188 );
189 output.serialize_entry(key, &redacted)?;
190 }
191 _ => {
192 let redacted = RedactedValue::opaque(
193 sensitivity,
194 self.policy.masking(),
195 );
196 output.serialize_entry(key, &redacted)?;
197 }
198 },
199 ResolvedField::PassThrough => {
200 output.serialize_entry(
201 key,
202 &self.nested(value, false),
203 )?;
204 }
205 }
206 }
207 output.end()
208 }
209 _ if self.redact_unkeyed_scalar() => serde::Serialize::serialize(
210 &self.depth_limit_mask(),
211 serializer,
212 ),
213 value => serde::Serialize::serialize(value, serializer),
214 }
215 }
216}
217
218mod session_view {
219 use std::fmt::{
220 self,
221 Write as _,
222 };
223
224 use serde_json::Value;
225
226 use crate::{
227 LogOutputLimit,
228 RedactedJson,
229 RedactionSession,
230 policy::OutputCharge,
231 text::internal::{
232 BoundedLogEscapeWriter,
233 LogEscapeWriter,
234 },
235 };
236
237 #[must_use = "format the nested redacted JSON view"]
239 pub struct RedactedJsonSession<'value, 'session, 'policy> {
240 value: &'value Value,
242 session: &'session RedactionSession<'policy>,
244 }
245
246 impl<'value, 'session, 'policy> RedactedJsonSession<'value, 'session, 'policy> {
247 #[inline(always)]
249 pub fn new(
250 value: &'value Value,
251 session: &'session RedactionSession<'policy>,
252 ) -> Self {
253 Self { value, session }
254 }
255
256 #[inline]
259 fn opaque(&self) -> &str {
260 self.session
261 .policy()
262 .masking()
263 .mask_opaque(crate::Sensitivity::Secret)
264 }
265
266 fn render(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269 let remaining = self.session.remaining_output_bytes();
270 let Some(limit) = LogOutputLimit::new(remaining).ok() else {
271 return self.write_fallback(formatter);
272 };
273 let mut writer = BoundedLogEscapeWriter::new(limit);
274 let view = RedactedJson::new(self.value, self.session.policy());
275 let _ = if formatter.alternate() {
276 write!(&mut writer, "{view:#?}")
277 } else {
278 write!(&mut writer, "{view:?}")
279 };
280 let rendered = writer.finish();
281 match self
282 .session
283 .charge_output_or_fallback(rendered.len(), self.opaque().len())
284 {
285 OutputCharge::Complete => formatter.write_str(&rendered),
286 OutputCharge::Fallback => formatter.write_str(self.opaque()),
287 OutputCharge::Exhausted => Ok(()),
288 }
289 }
290
291 fn write_fallback(
294 &self,
295 formatter: &mut fmt::Formatter<'_>,
296 ) -> fmt::Result {
297 let fallback = self.opaque();
298 match self
299 .session
300 .charge_output_or_fallback(fallback.len(), fallback.len())
301 {
302 OutputCharge::Complete => formatter.write_str(fallback),
303 OutputCharge::Fallback | OutputCharge::Exhausted => Ok(()),
304 }
305 }
306 }
307
308 impl fmt::Debug for RedactedJsonSession<'_, '_, '_> {
309 #[inline]
311 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
312 self.render(formatter)
313 }
314 }
315
316 impl fmt::Display for RedactedJsonSession<'_, '_, '_> {
317 #[inline]
319 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
320 let mut writer = LogEscapeWriter::new(formatter);
321 write!(&mut writer, "{self:?}")
322 }
323 }
324}
325
326pub use session_view::RedactedJsonSession;
327
328fn fmt_json(
343 view: &RedactedJson<'_, '_>,
344 formatter: &mut fmt::Formatter<'_>,
345) -> fmt::Result {
346 if view.depth_limit_reached() {
347 return fmt::Debug::fmt(&view.depth_limit_mask(), formatter);
348 }
349 match view.value {
350 Value::Array(values) => {
351 let mut output = formatter.debug_list();
352 for value in values {
353 output.entry(&view.nested(value, true));
354 }
355 output.finish()
356 }
357 Value::Object(values) => {
358 let mut output = formatter.debug_map();
359 for (key, value) in values {
360 let resolved = view.policy.resolve_field(key);
361 match resolved {
362 ResolvedField::Sensitive { sensitivity } => {
363 fmt_masked_entry(
364 &mut output,
365 key,
366 value,
367 sensitivity,
368 view.policy.masking(),
369 );
370 }
371 ResolvedField::PassThrough => {
372 output.entry(key, &view.nested(value, false));
373 }
374 }
375 }
376 output.finish()
377 }
378 _ if view.redact_unkeyed_scalar() => {
379 fmt::Debug::fmt(&view.depth_limit_mask(), formatter)
380 }
381 value => fmt::Debug::fmt(value, formatter),
382 }
383}
384
385fn fmt_masked_entry(
395 output: &mut fmt::DebugMap<'_, '_>,
396 key: &str,
397 value: &Value,
398 sensitivity: crate::Sensitivity,
399 masking: &crate::MaskingPolicy,
400) {
401 match value {
402 Value::String(text) => {
403 let redacted = text.redact_value(sensitivity, masking);
404 output.entry(&key, &redacted);
405 }
406 _ => {
407 let redacted = RedactedValue::opaque(sensitivity, masking);
408 output.entry(&key, &redacted);
409 }
410 };
411}